[automerger skipped] Merge "Removes app access to BluetoothAdapter#setDiscoverableTimeout by requiring BLUETOOTH_PRIVILEGED permission." into qt-dev am: db3923e3e8 am: 46894f70cd -s ours am: 7d6de2c810 -s ours am: 234a98b8e8 -s ours

am skip reason: Merged-In I73288f495d35280a5724d070248db54e2fe537fd with SHA-1 f9ac0a0925 is already in history

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

Change-Id: I242bc13408c851b5d2e334ea8c551ea41ce4aa81
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
diff --git a/Android.bp b/Android.bp
index 08e0ee1..66e2eb5 100644
--- a/Android.bp
+++ b/Android.bp
@@ -1,5 +1,33 @@
 // MAP API module
 
+package {
+    default_applicable_licenses: ["packages_apps_Bluetooth_license"],
+}
+
+// Added automatically by a large-scale-change that took the approach of
+// 'apply every license found to every target'. While this makes sure we respect
+// every license restriction, it may not be entirely correct.
+//
+// e.g. GPL in an MIT project might only apply to the contrib/ directory.
+//
+// Please consider splitting the single license below into multiple licenses,
+// taking care not to lose any license_kind information, and overriding the
+// default license using the 'licenses: [...]' property on targets as needed.
+//
+// For unused files, consider creating a 'fileGroup' with "//visibility:private"
+// to attach the license to, and including a comment whether the files may be
+// used in the current project.
+// See: http://go/android-license-faq
+license {
+    name: "packages_apps_Bluetooth_license",
+    visibility: [":__subpackages__"],
+    license_kinds: [
+        "SPDX-license-identifier-Apache-2.0",
+        "SPDX-license-identifier-BSD",
+    ],
+    // large-scale-change unable to identify any license_text files
+}
+
 java_library {
     name: "bluetooth.mapsapi",
 
@@ -11,7 +39,10 @@
 cc_library_shared {
     name: "libbluetooth_jni",
     srcs: ["jni/**/*.cpp"],
-    header_libs: ["libbluetooth_headers"],
+    header_libs: [
+        "jni_headers",
+        "libbluetooth_headers",
+    ],
     include_dirs: [
         "system/bt/types",
     ],
@@ -19,11 +50,10 @@
         "libbase",
         "libchrome",
         "liblog",
+        "libnativehelper",
     ],
     static_libs: [
         "libbluetooth-types",
-        // TODO(b/148645937) move this back to shared_libs
-        "libnativehelper",
     ],
     cflags: [
         "-Wall",
@@ -40,6 +70,7 @@
 
 android_app {
     name: "Bluetooth",
+    defaults: ["platform_app_defaults"],
 
     srcs: [
         "src/**/*.java",
@@ -82,6 +113,11 @@
         "//apex_available:platform",
         "com.android.bluetooth.updatable",
     ],
+    errorprone: {
+        javacflags: [
+            // "-Xep:AndroidFrameworkRequiresPermission:ERROR",
+        ],
+    },
 }
 
 genrule {
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 4683794..bb48db5 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -1,426 +1,449 @@
 <?xml version="1.0" encoding="utf-8"?>
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
-  package="com.android.bluetooth"
-  android:sharedUserId="android.uid.bluetooth">
 
-    <original-package android:name="com.android.bluetooth" />
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+     package="com.android.bluetooth"
+     android:sharedUserId="android.uid.bluetooth">
+
+    <original-package android:name="com.android.bluetooth"/>
 
     <!-- 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" />
+         android:label="@string/permlab_bluetoothShareManager"
+         android:description="@string/permdesc_bluetoothShareManager"
+         android:protectionLevel="signature"/>
 
-    <!--  Allows temporarily whitelisting Bluetooth addresses for sharing -->
-    <permission android:name="com.android.permission.WHITELIST_BLUETOOTH_DEVICE"
-        android:label="@string/permlab_bluetoothWhitelist"
-        android:description="@string/permdesc_bluetoothWhitelist"
-        android:protectionLevel="signature" />
+    <!--  Allows temporarily acceptlisting Bluetooth addresses for sharing -->
+    <permission android:name="com.android.permission.ALLOWLIST_BLUETOOTH_DEVICE"
+         android:label="@string/permlab_bluetoothAcceptlist"
+         android:description="@string/permdesc_bluetoothAcceptlist"
+         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.ACCESS_COARSE_LOCATION" />
-    <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.BLUETOOTH_PRIVILEGED" />
-    <uses-permission android:name="android.permission.BLUETOOTH_MAP" />
-    <uses-permission android:name="android.permission.DUMP" />
-    <uses-permission android:name="android.permission.WAKE_LOCK" />
-    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
-    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
-    <uses-permission android:name="android.permission.READ_CONTACTS" />
-    <uses-permission android:name="android.permission.WRITE_CONTACTS" />
-    <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" />
-    <uses-permission android:name="android.permission.WRITE_APN_SETTINGS" />
-    <uses-permission android:name="android.permission.NET_ADMIN" />
-    <uses-permission android:name="android.permission.CALL_PRIVILEGED" />
-    <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
-    <uses-permission android:name="android.permission.NET_TUNNELING" />
-    <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" />
-    <uses-permission android:name="android.permission.BLUETOOTH_STACK" />
+    <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
+    <uses-permission android:name="android.permission.ACCESS_BLUETOOTH_SHARE"/>
+    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
+    <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.BLUETOOTH_ADVERTISE"/>
+    <uses-permission android:name="android.permission.BLUETOOTH_CONNECT"/>
+    <uses-permission android:name="android.permission.BLUETOOTH_SCAN"/>
+    <uses-permission android:name="android.permission.BLUETOOTH_PRIVILEGED"/>
+    <uses-permission android:name="android.permission.BLUETOOTH_MAP"/>
+    <uses-permission android:name="android.permission.CONTROL_INCALL_EXPERIENCE" />
+    <uses-permission android:name="android.permission.DUMP"/>
+    <uses-permission android:name="android.permission.WAKE_LOCK"/>
+    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
+    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
+    <uses-permission android:name="android.permission.READ_CONTACTS"/>
+    <uses-permission android:name="android.permission.WRITE_CONTACTS"/>
+    <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"/>
+    <uses-permission android:name="android.permission.WRITE_APN_SETTINGS"/>
+    <uses-permission android:name="android.permission.NET_ADMIN"/>
+    <uses-permission android:name="android.permission.CALL_PRIVILEGED"/>
+    <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS"/>
+    <uses-permission android:name="android.permission.NET_TUNNELING"/>
+    <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"/>
+    <uses-permission android:name="android.permission.BLUETOOTH_STACK"/>
     <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS"/>
     <uses-permission android:name="android.permission.MANAGE_USERS"/>
     <uses-permission android:name="android.permission.GET_ACCOUNTS"/>
-    <uses-permission android:name="android.permission.RECEIVE_SMS" />
-    <uses-permission android:name="android.permission.SEND_SMS" />
-    <uses-permission android:name="android.permission.READ_SMS" />
-    <uses-permission android:name="android.permission.WRITE_SMS" />
-    <uses-permission android:name="android.permission.MEDIA_CONTENT_CONTROL" />
-    <uses-permission android:name="android.permission.UPDATE_APP_OPS_STATS" />
-    <uses-permission android:name="android.permission.MANAGE_APP_OPS_MODES" />
-    <uses-permission android:name="android.permission.VIBRATE" />
-    <uses-permission android:name="android.permission.DEVICE_POWER" />
-    <uses-permission android:name="android.permission.REAL_GET_TASKS" />
-    <uses-permission android:name="android.permission.MODIFY_AUDIO_ROUTING" />
-    <uses-permission android:name="android.permission.UPDATE_DEVICE_STATS" />
-    <uses-permission android:name="android.permission.PACKAGE_USAGE_STATS" />
-    <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
+    <uses-permission android:name="android.permission.RECEIVE_SMS"/>
+    <uses-permission android:name="android.permission.SEND_SMS"/>
+    <uses-permission android:name="android.permission.READ_SMS"/>
+    <uses-permission android:name="android.permission.WRITE_SMS"/>
+    <uses-permission android:name="android.permission.MEDIA_CONTENT_CONTROL"/>
+    <uses-permission android:name="android.permission.UPDATE_APP_OPS_STATS"/>
+    <uses-permission android:name="android.permission.MANAGE_APP_OPS_MODES"/>
+    <uses-permission android:name="android.permission.VIBRATE"/>
+    <uses-permission android:name="android.permission.DEVICE_POWER"/>
+    <uses-permission android:name="android.permission.REAL_GET_TASKS"/>
+    <uses-permission android:name="android.permission.MODIFY_AUDIO_ROUTING"/>
+    <uses-permission android:name="android.permission.UPDATE_DEVICE_STATS"/>
+    <uses-permission android:name="android.permission.PACKAGE_USAGE_STATS"/>
+    <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES"/>
     <uses-permission android:name="android.permission.MANAGE_COMPANION_DEVICES"/>
+    <uses-permission android:name="android.permission.START_FOREGROUND_SERVICES_FROM_BACKGROUND"/>
+    <uses-permission android:name="android.permission.HIDE_OVERLAY_WINDOWS"/>
 
     <uses-sdk android:minSdkVersion="14"/>
 
     <!-- For PBAP Owner Vcard Info -->
     <uses-permission android:name="android.permission.READ_PROFILE"/>
-    <application
-        android:name=".btservice.AdapterApp"
-        android:icon="@mipmap/bt_share"
-        android:persistent="false"
-        android:label="@string/app_name"
-        android:supportsRtl="true"
-        android:usesCleartextTraffic="false"
-        android:directBootAware="true"
-        android:defaultToDeviceProtectedStorage="true">
-        <uses-library android:name="javax.obex" />
+    <application android:name=".btservice.AdapterApp"
+         android:icon="@mipmap/bt_share"
+         android:persistent="false"
+         android:label="@string/app_name"
+         android:supportsRtl="true"
+         android:usesCleartextTraffic="false"
+         android:directBootAware="true"
+         android:defaultToDeviceProtectedStorage="true"
+         android:memtagMode="async">
+        <uses-library android:name="javax.obex"/>
         <provider android:name=".opp.BluetoothOppProvider"
-            android:authorities="com.android.bluetooth.opp"
-            android:exported="true"
-            android:process="@string/process">
-            <path-permission
-                    android:pathPrefix="/btopp"
-                    android:permission="android.permission.ACCESS_BLUETOOTH_SHARE" />
+             android:authorities="com.android.bluetooth.opp"
+             android:exported="true"
+             android:process="@string/process">
+            <path-permission android:pathPrefix="/btopp"
+                 android:permission="android.permission.ACCESS_BLUETOOTH_SHARE"/>
         </provider>
         <provider android:name=".opp.BluetoothOppFileProvider"
-            android:authorities="com.android.bluetooth.opp.fileprovider"
-            android:grantUriPermissions="true"
-            android:exported="false">
-            <meta-data
-                android:name="android.support.FILE_PROVIDER_PATHS"
-                android:resource="@xml/file_paths" />
+             android:authorities="com.android.bluetooth.opp.fileprovider"
+             android:grantUriPermissions="true"
+             android:exported="false">
+            <meta-data android:name="android.support.FILE_PROVIDER_PATHS"
+                 android:resource="@xml/file_paths"/>
         </provider>
-        <service
-            android:process="@string/process"
-            android:name = ".btservice.AdapterService">
+        <service android:process="@string/process"
+             android:name=".btservice.AdapterService"
+             android:exported="true"
+             android:permission="android.permission.ACCESS_BLUETOOTH_SHARE">
             <intent-filter>
-                <action android:name="android.bluetooth.IBluetooth" />
+                <action android:name="android.bluetooth.IBluetooth"/>
             </intent-filter>
         </service>
-        <service
-            android:process="@string/process"
-            android:name=".opp.BluetoothOppService"
-            android:permission="android.permission.ACCESS_BLUETOOTH_SHARE"
-            android:enabled="@bool/profile_supported_opp"/>
-        <receiver
-            android:process="@string/process"
-            android:exported="true"
-            android:name=".opp.BluetoothOppReceiver"
-            android:enabled="@bool/profile_supported_opp">
+        <service android:process="@string/process"
+             android:name=".opp.BluetoothOppService"
+             android:permission="android.permission.ACCESS_BLUETOOTH_SHARE"
+             android:enabled="@bool/profile_supported_opp"/>
+        <receiver android:process="@string/process"
+             android:exported="true"
+             android:name=".opp.BluetoothOppReceiver"
+             android:enabled="@bool/profile_supported_opp">
             <intent-filter>
-                <action android:name="android.btopp.intent.action.OPEN_RECEIVED_FILES" />
+                <action android:name="android.btopp.intent.action.OPEN_RECEIVED_FILES"/>
             </intent-filter>
         </receiver>
-         <receiver
-            android:process="@string/process"
-            android:name=".opp.BluetoothOppHandoverReceiver"
-            android:permission="com.android.permission.WHITELIST_BLUETOOTH_DEVICE">
+         <receiver android:process="@string/process"
+              android:name=".opp.BluetoothOppHandoverReceiver"
+              android:permission="com.android.permission.ALLOWLIST_BLUETOOTH_DEVICE"
+              android:exported="true">
             <intent-filter>
-                <action android:name="android.btopp.intent.action.WHITELIST_DEVICE" />
-                <action android:name="android.btopp.intent.action.STOP_HANDOVER_TRANSFER" />
+                <action android:name="android.btopp.intent.action.ACCEPTLIST_DEVICE"/>
+                <action android:name="android.btopp.intent.action.STOP_HANDOVER_TRANSFER"/>
             </intent-filter>
             <intent-filter>
-                <action android:name="android.nfc.handover.intent.action.HANDOVER_SEND" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <data android:mimeType="*/*" />
+                <action android:name="android.nfc.handover.intent.action.HANDOVER_SEND"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <data android:mimeType="*/*"/>
             </intent-filter>
             <intent-filter>
-                <action android:name="android.nfc.handover.intent.action.HANDOVER_SEND_MULTIPLE" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <data android:mimeType="*/*" />
+                <action android:name="android.nfc.handover.intent.action.HANDOVER_SEND_MULTIPLE"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <data android:mimeType="*/*"/>
             </intent-filter>
         </receiver>
         <activity android:name=".opp.BluetoothOppLauncherActivity"
-            android:process="@string/process"
-            android:theme="@android:style/Theme.Material.Light.Dialog"
-            android:label="@string/bt_share_picker_label"
-            android:enabled="@bool/profile_supported_opp">
+             android:process="@string/process"
+             android:theme="@android:style/Theme.Material.Light.Dialog"
+             android:label="@string/bt_share_picker_label"
+             android:enabled="@bool/profile_supported_opp"
+             android:exported="true">
             <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/*" />
-                <data android:mimeType="text/x-vcard" />
-                <data android:mimeType="text/x-vcalendar" />
-                <data android:mimeType="text/calendar" />
-                <data android:mimeType="text/plain" />
-                <data android:mimeType="text/html" />
-                <data android:mimeType="text/xml" />
-                <data android:mimeType="application/zip" />
-                <data android:mimeType="application/vnd.ms-excel" />
-                <data android:mimeType="application/msword" />
-                <data android:mimeType="application/vnd.ms-powerpoint" />
-                <data android:mimeType="application/pdf" />
-                <data android:mimeType="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" />
-                <data android:mimeType="application/vnd.openxmlformats-officedocument.wordprocessingml.document" />
-                <data android:mimeType="application/vnd.openxmlformats-officedocument.presentationml.presentation" />
-                <data android:mimeType="application/x-hwp" />
+                <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/*"/>
+                <data android:mimeType="text/x-vcard"/>
+                <data android:mimeType="text/x-vcalendar"/>
+                <data android:mimeType="text/calendar"/>
+                <data android:mimeType="text/plain"/>
+                <data android:mimeType="text/html"/>
+                <data android:mimeType="text/xml"/>
+                <data android:mimeType="application/zip"/>
+                <data android:mimeType="application/vnd.ms-excel"/>
+                <data android:mimeType="application/msword"/>
+                <data android:mimeType="application/vnd.ms-powerpoint"/>
+                <data android:mimeType="application/pdf"/>
+                <data android:mimeType="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"/>
+                <data android:mimeType="application/vnd.openxmlformats-officedocument.wordprocessingml.document"/>
+                <data android:mimeType="application/vnd.openxmlformats-officedocument.presentationml.presentation"/>
+                <data android:mimeType="application/x-hwp"/>
             </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/*" />
-                <data android:mimeType="text/x-vcard" />
+                <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/*"/>
+                <data android:mimeType="text/x-vcard"/>
             </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" />
+                <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"
-                  android:excludeFromRecents="true"
-                  android:theme="@android:style/Theme.Material.Light.Dialog.Alert"
-                  android:enabled="@bool/profile_supported_opp">
+             android:process="@string/process"
+             android:excludeFromRecents="true"
+             android:theme="@style/dialog"
+             android:enabled="@bool/profile_supported_opp">
         </activity>
         <activity android:name=".opp.BluetoothOppBtErrorActivity"
-                  android:process="@string/process"
-                  android:excludeFromRecents="true"
-                  android:theme="@android:style/Theme.Material.Light.Dialog.Alert">
+             android:process="@string/process"
+             android:excludeFromRecents="true"
+             android:theme="@style/dialog">
         </activity>
         <activity android:name=".opp.BluetoothOppBtEnablingActivity"
-                  android:process="@string/process"
-                  android:excludeFromRecents="true"
-                  android:theme="@android:style/Theme.Material.Light.Dialog.Alert"
-                  android:enabled="@bool/profile_supported_opp">
+             android:process="@string/process"
+             android:excludeFromRecents="true"
+             android:theme="@style/dialog"
+             android:enabled="@bool/profile_supported_opp">
         </activity>
         <activity android:name=".opp.BluetoothOppIncomingFileConfirmActivity"
-                  android:process="@string/process"
-                  android:excludeFromRecents="true"
-                  android:theme="@android:style/Theme.Material.Light.Dialog.Alert"
-                  android:enabled="@bool/profile_supported_opp">
+             android:process="@string/process"
+             android:excludeFromRecents="true"
+             android:theme="@style/dialog"
+             android:enabled="@bool/profile_supported_opp">
         </activity>
         <activity android:name=".opp.BluetoothOppTransferActivity"
-                  android:process="@string/process"
-                  android:excludeFromRecents="true"
-                  android:theme="@android:style/Theme.Material.Light.Dialog.Alert"
-                  android:enabled="@bool/profile_supported_opp">
+             android:process="@string/process"
+             android:excludeFromRecents="true"
+             android:theme="@style/dialog"
+             android:enabled="@bool/profile_supported_opp">
         </activity>
         <activity android:name=".opp.BluetoothOppTransferHistory"
-                  android:process="@string/process"
-                  android:label=""
-                  android:excludeFromRecents="true"
-                  android:configChanges="orientation|keyboardHidden"
-                  android:enabled="@bool/profile_supported_opp"
-                  android:theme="@android:style/Theme.DeviceDefault.Settings">
+             android:process="@string/process"
+             android:label=""
+             android:excludeFromRecents="true"
+             android:configChanges="orientation|keyboardHidden"
+             android:enabled="@bool/profile_supported_opp"
+             android:theme="@android:style/Theme.DeviceDefault.Settings"
+             android:exported="true">
             <intent-filter>
-                <action android:name="com.android.bluetooth.action.TransferHistory" />
-                <category android:name="android.intent.category.DEFAULT" />
+                <action android:name="com.android.bluetooth.action.TransferHistory"/>
+                <category android:name="android.intent.category.DEFAULT"/>
             </intent-filter>
         </activity>
         <activity android:name=".pbap.BluetoothPbapActivity"
-            android:process="@string/process"
-            android:excludeFromRecents="true"
-            android:theme="@android:style/Theme.Material.Light.Dialog.Alert"
-            android:enabled="@bool/profile_supported_pbap">
+             android:process="@string/process"
+             android:excludeFromRecents="true"
+             android:theme="@style/dialog"
+             android:enabled="@bool/profile_supported_pbap">
         </activity>
-        <service
-            android:process="@string/process"
-            android:permission="android.permission.BLUETOOTH_PRIVILEGED"
-            android:name=".pbap.BluetoothPbapService"
-            android:enabled="@bool/profile_supported_pbap" >
+        <service android:process="@string/process"
+             android:permission="android.permission.BLUETOOTH_PRIVILEGED"
+             android:name=".pbap.BluetoothPbapService"
+             android:enabled="@bool/profile_supported_pbap"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.bluetooth.IBluetoothPbap" />
+                <action android:name="android.bluetooth.IBluetoothPbap"/>
             </intent-filter>
         </service>
-        <service
-            android:process="@string/process"
-            android:name=".map.BluetoothMapService"
-            android:enabled="@bool/profile_supported_map" >
+        <service android:process="@string/process"
+             android:name=".map.BluetoothMapService"
+             android:enabled="@bool/profile_supported_map"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.bluetooth.IBluetoothMap" />
-                <action android:name="android.btmap.intent.action.SHOW_MAPS_SETTINGS" />
+                <action android:name="android.bluetooth.IBluetoothMap"/>
+                <action android:name="android.btmap.intent.action.SHOW_MAPS_SETTINGS"/>
                 <action android:name="com.android.bluetooth.map.USER_CONFIRM_TIMEOUT"/>
             </intent-filter>
         </service>
          <activity android:name=".map.BluetoothMapSettings"
-                  android:process="@string/process"
-                  android:label="@string/bluetooth_map_settings_title"
-                  android:excludeFromRecents="true"
-                  android:configChanges="orientation|keyboardHidden"
-                  android:enabled="@bool/profile_supported_map">
+              android:process="@string/process"
+              android:label="@string/bluetooth_map_settings_title"
+              android:excludeFromRecents="true"
+              android:configChanges="orientation|keyboardHidden"
+              android:enabled="@bool/profile_supported_map">
         </activity>
         <provider android:name=".map.MmsFileProvider"
-                  android:authorities="com.android.bluetooth.map.MmsFileProvider"
-                  android:enabled="true"
-                  android:grantUriPermissions="true"
-                  android:exported="false">
+             android:authorities="com.android.bluetooth.map.MmsFileProvider"
+             android:enabled="true"
+             android:grantUriPermissions="true"
+             android:exported="false">
         </provider>
-        <service
-            android:process="@string/process"
-            android:name=".mapclient.MapClientService"
-            android:enabled="@bool/profile_supported_mapmce" >
+        <service android:process="@string/process"
+             android:name=".mapclient.MapClientService"
+             android:enabled="@bool/profile_supported_mapmce"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.bluetooth.IBluetoothMapClient" />
+                <action android:name="android.bluetooth.IBluetoothMapClient"/>
             </intent-filter>
         </service>
-        <service
-            android:process="@string/process"
-            android:name=".sap.SapService"
-            android:enabled="@bool/profile_supported_sap" >
+        <service android:process="@string/process"
+             android:name=".sap.SapService"
+             android:enabled="@bool/profile_supported_sap"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.bluetooth.IBluetoothSap" />
+                <action android:name="android.bluetooth.IBluetoothSap"/>
             </intent-filter>
         </service>
-        <service
-            android:process="@string/process"
-            android:name = ".gatt.GattService"
-            android:enabled="@bool/profile_supported_gatt">
+        <service android:process="@string/process"
+             android:name=".gatt.GattService"
+             android:enabled="@bool/profile_supported_gatt"
+             android:exported="true"
+             android:permission="android.permission.ACCESS_BLUETOOTH_SHARE">
             <intent-filter>
-                <action android:name="android.bluetooth.IBluetoothGatt" />
+                <action android:name="android.bluetooth.IBluetoothGatt"/>
             </intent-filter>
         </service>
-        <service
-            android:process="@string/process"
-            android:name = ".hfp.HeadsetService"
-            android:enabled="@bool/profile_supported_hs_hfp">
+        <service android:process="@string/process"
+             android:name=".hfp.HeadsetService"
+             android:enabled="@bool/profile_supported_hs_hfp"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.bluetooth.IBluetoothHeadset" />
+                <action android:name="android.bluetooth.IBluetoothHeadset"/>
             </intent-filter>
         </service>
-        <service
-            android:process="@string/process"
-            android:name = ".a2dp.A2dpService"
-            android:enabled="@bool/profile_supported_a2dp">
+        <service android:process="@string/process"
+             android:name=".a2dp.A2dpService"
+             android:enabled="@bool/profile_supported_a2dp"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.bluetooth.IBluetoothA2dp" />
+                <action android:name="android.bluetooth.IBluetoothA2dp"/>
             </intent-filter>
         </service>
-        <service
-            android:process="@string/process"
-            android:name = ".a2dpsink.A2dpSinkService"
-            android:enabled="@bool/profile_supported_a2dp_sink">
+        <service android:process="@string/process"
+             android:name=".a2dpsink.A2dpSinkService"
+             android:enabled="@bool/profile_supported_a2dp_sink"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.bluetooth.IBluetoothA2dpSink" />
+                <action android:name="android.bluetooth.IBluetoothA2dpSink"/>
             </intent-filter>
         </service>
-        <service
-            android:process="@string/process"
-            android:name=".avrcpcontroller.BluetoothMediaBrowserService"
-            android:exported="true"
-            android:enabled="@bool/profile_supported_a2dp_sink"
-            android:label="@string/a2dp_sink_mbs_label">
+        <service android:process="@string/process"
+             android:name=".avrcpcontroller.BluetoothMediaBrowserService"
+             android:exported="true"
+             android:enabled="@bool/profile_supported_a2dp_sink"
+             android:label="@string/a2dp_sink_mbs_label">
             <intent-filter>
-                <action android:name="android.media.browse.MediaBrowserService" />
+                <action android:name="android.media.browse.MediaBrowserService"/>
             </intent-filter>
         </service>
 
-        <activity
-            android:name=".BluetoothPrefs"
-            android:exported="@bool/profile_supported_a2dp_sink"
-            android:enabled="@bool/profile_supported_a2dp_sink">
+        <activity android:name=".BluetoothPrefs"
+             android:exported="@bool/profile_supported_a2dp_sink"
+             android:enabled="@bool/profile_supported_a2dp_sink">
             <intent-filter>
                 <action android:name="android.intent.action.APPLICATION_PREFERENCES"/>
             </intent-filter>
         </activity>
 
-        <service
-            android:process="@string/process"
-            android:name = ".avrcp.AvrcpTargetService"
-            android:enabled = "@bool/profile_supported_avrcp_target" >
+        <service android:process="@string/process"
+             android:name=".avrcp.AvrcpTargetService"
+             android:enabled="@bool/profile_supported_avrcp_target"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.bluetooth.IBluetoothAvrcp" />
+                <action android:name="android.bluetooth.IBluetoothAvrcp"/>
             </intent-filter>
         </service>
-        <service
-            android:process="@string/process"
-            android:name = ".avrcpcontroller.AvrcpControllerService"
-            android:enabled="@bool/profile_supported_avrcp_controller">
+        <service android:process="@string/process"
+             android:name=".avrcpcontroller.AvrcpControllerService"
+             android:enabled="@bool/profile_supported_avrcp_controller"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.bluetooth.IBluetoothAvrcpController" />
+                <action android:name="android.bluetooth.IBluetoothAvrcpController"/>
             </intent-filter>
         </service>
         <provider android:process="@string/process"
-            android:name=".avrcpcontroller.AvrcpCoverArtProvider"
-            android:authorities="com.android.bluetooth.avrcpcontroller.AvrcpCoverArtProvider"
-            android:enabled="@bool/avrcp_controller_enable_cover_art"
-            android:grantUriPermissions="true"
-            android:exported="true">
+             android:name=".avrcpcontroller.AvrcpCoverArtProvider"
+             android:authorities="com.android.bluetooth.avrcpcontroller.AvrcpCoverArtProvider"
+             android:enabled="@bool/avrcp_controller_enable_cover_art"
+             android:grantUriPermissions="true"
+             android:exported="true">
         </provider>
-        <service
-            android:process="@string/process"
-            android:name = ".hid.HidHostService"
-            android:enabled="@bool/profile_supported_hid_host">
+        <service android:process="@string/process"
+             android:name=".hid.HidHostService"
+             android:enabled="@bool/profile_supported_hid_host"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.bluetooth.IBluetoothHidHost" />
+                <action android:name="android.bluetooth.IBluetoothHidHost"/>
             </intent-filter>
         </service>
-        <service
-            android:process="@string/process"
-            android:name = ".hid.HidDeviceService"
-            android:enabled="@bool/profile_supported_hid_device">
+        <service android:process="@string/process"
+             android:name=".hid.HidDeviceService"
+             android:enabled="@bool/profile_supported_hid_device"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.bluetooth.IBluetoothHidDevice" />
+                <action android:name="android.bluetooth.IBluetoothHidDevice"/>
             </intent-filter>
         </service>
-        <service
-            android:process="@string/process"
-            android:name = ".pan.PanService"
-            android:enabled="@bool/profile_supported_pan">
+        <service android:process="@string/process"
+             android:name=".pan.PanService"
+             android:enabled="@bool/profile_supported_pan"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.bluetooth.IBluetoothPan" />
+                <action android:name="android.bluetooth.IBluetoothPan"/>
             </intent-filter>
         </service>
-        <service
-            android:process="@string/process"
-            android:name = ".hfpclient.HeadsetClientService"
-            android:enabled="@bool/profile_supported_hfpclient">
+        <service android:process="@string/process"
+             android:name=".hfpclient.HeadsetClientService"
+             android:enabled="@bool/profile_supported_hfpclient"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.bluetooth.IBluetoothHeadsetClient" />
+                <action android:name="android.bluetooth.IBluetoothHeadsetClient"/>
             </intent-filter>
         </service>
-        <service
-            android:process="@string/process"
-            android:name=".hfpclient.connserv.HfpClientConnectionService"
-            android:permission="android.permission.BIND_CONNECTION_SERVICE"
-            android:enabled="@bool/hfp_client_connection_service_enabled">
+        <service android:process="@string/process"
+             android:name=".hfpclient.connserv.HfpClientConnectionService"
+             android:permission="android.permission.BIND_CONNECTION_SERVICE"
+             android:enabled="@bool/hfp_client_connection_service_enabled"
+             android:exported="true">
             <intent-filter>
                 <!-- Mechanism for Telecom stack to connect -->
-                <action android:name="android.telecom.ConnectionService" />
+                <action android:name="android.telecom.ConnectionService"/>
+            </intent-filter>
+        </service>
+        <service android:process="@string/process"
+             android:name=".pbapclient.PbapClientService"
+             android:enabled="@bool/profile_supported_pbapclient"
+             android:exported="true">
+            <intent-filter>
+                <action android:name="android.bluetooth.IBluetoothPbapClient"/>
+            </intent-filter>
+        </service>
+        <service android:process="@string/process"
+             android:name=".hearingaid.HearingAidService"
+             android:exported="true">
+            <intent-filter>
+                <action android:name="android.bluetooth.IBluetoothHearingAid"/>
             </intent-filter>
         </service>
         <service
             android:process="@string/process"
-            android:name = ".pbapclient.PbapClientService"
-            android:enabled="@bool/profile_supported_pbapclient">
+            android:name = ".le_audio.LeAudioService"
+            android:exported = "true">
             <intent-filter>
-                <action android:name="android.bluetooth.IBluetoothPbapClient" />
-            </intent-filter>
-        </service>
-        <service
-            android:process="@string/process"
-            android:name = ".hearingaid.HearingAidService">
-            <intent-filter>
-                <action android:name="android.bluetooth.IBluetoothHearingAid" />
+                <action android:name="android.bluetooth.IBluetoothLeAudio" />
             </intent-filter>
         </service>
         <!-- Authenticator for PBAP account. -->
-        <service
-            android:process="@string/process"
-            android:name=".pbapclient.AuthenticationService"
-            android:exported="true"
-            android:enabled="@bool/profile_supported_pbapclient">
+        <service android:process="@string/process"
+             android:name=".pbapclient.AuthenticationService"
+             android:exported="true"
+             android:enabled="@bool/profile_supported_pbapclient">
             <intent-filter>
-                <action android:name="android.accounts.AccountAuthenticator" />
+                <action android:name="android.accounts.AccountAuthenticator"/>
             </intent-filter>
-            <meta-data
-                android:name="android.accounts.AccountAuthenticator"
-                android:resource="@xml/authenticator" />
+            <meta-data android:name="android.accounts.AccountAuthenticator"
+                 android:resource="@xml/authenticator"/>
         </service>
+        <service
+            android:name=".telephony.BluetoothInCallService"
+            android:permission="android.permission.BIND_INCALL_SERVICE"
+            android:process="@string/process"
+            android:enabled="@bool/profile_supported_hfp_incallservice"
+            android:exported="true">
+            <meta-data android:name="android.telecom.INCLUDE_SELF_MANAGED_CALLS"
+                       android:value="true" />
+            <intent-filter>
+              <action android:name="android.telecom.InCallService"/>
+            </intent-filter>
+         </service>
     </application>
 </manifest>
diff --git a/jni/com_android_bluetooth.h b/jni/com_android_bluetooth.h
index f4cda15..03337fb 100644
--- a/jni/com_android_bluetooth.h
+++ b/jni/com_android_bluetooth.h
@@ -148,13 +148,17 @@
 
 int register_com_android_bluetooth_pan(JNIEnv* env);
 
-int register_com_android_bluetooth_gatt (JNIEnv* env);
+int register_com_android_bluetooth_gatt(JNIEnv* env);
 
-int register_com_android_bluetooth_sdp (JNIEnv* env);
+int register_com_android_bluetooth_sdp(JNIEnv* env);
 
 int register_com_android_bluetooth_hearing_aid(JNIEnv* env);
 
 int register_com_android_bluetooth_btservice_BluetoothKeystore(JNIEnv* env);
-}
+
+int register_com_android_bluetooth_btservice_activity_attribution(JNIEnv* env);
+
+int register_com_android_bluetooth_le_audio(JNIEnv* env);
+}  // namespace android
 
 #endif /* COM_ANDROID_BLUETOOTH_H */
diff --git a/jni/com_android_bluetooth_a2dp_sink.cpp b/jni/com_android_bluetooth_a2dp_sink.cpp
index 87668ff..d7cbeb7 100644
--- a/jni/com_android_bluetooth_a2dp_sink.cpp
+++ b/jni/com_android_bluetooth_a2dp_sink.cpp
@@ -109,7 +109,8 @@
   ALOGI("%s: succeeds", __func__);
 }
 
-static void initNative(JNIEnv* env, jobject object) {
+static void initNative(JNIEnv* env, jobject object,
+                       jint maxConnectedAudioDevices) {
   const bt_interface_t* btInf = getBluetoothInterface();
   if (btInf == NULL) {
     ALOGE("Bluetooth module is not loaded");
@@ -136,7 +137,8 @@
     return;
   }
 
-  bt_status_t status = sBluetoothA2dpInterface->init(&sBluetoothA2dpCallbacks);
+  bt_status_t status = sBluetoothA2dpInterface->init(&sBluetoothA2dpCallbacks,
+                                                     maxConnectedAudioDevices);
   if (status != BT_STATUS_SUCCESS) {
     ALOGE("Failed to initialize Bluetooth A2DP Sink, status: %d", status);
     sBluetoothA2dpInterface = NULL;
@@ -243,7 +245,7 @@
 
 static JNINativeMethod sMethods[] = {
     {"classInitNative", "()V", (void*)classInitNative},
-    {"initNative", "()V", (void*)initNative},
+    {"initNative", "(I)V", (void*)initNative},
     {"cleanupNative", "()V", (void*)cleanupNative},
     {"connectA2dpNative", "([B)Z", (void*)connectA2dpNative},
     {"disconnectA2dpNative", "([B)Z", (void*)disconnectA2dpNative},
diff --git a/jni/com_android_bluetooth_avrcp_target.cpp b/jni/com_android_bluetooth_avrcp_target.cpp
index 22c954c..fb8c560 100644
--- a/jni/com_android_bluetooth_avrcp_target.cpp
+++ b/jni/com_android_bluetooth_avrcp_target.cpp
@@ -172,10 +172,10 @@
 
 static void classInitNative(JNIEnv* env, jclass clazz) {
   method_getCurrentSongInfo = env->GetMethodID(
-      clazz, "getCurrentSongInfo", "()Lcom/android/bluetooth/avrcp/Metadata;");
+      clazz, "getCurrentSongInfo", "()Lcom/android/bluetooth/audio_util/Metadata;");
 
   method_getPlaybackStatus = env->GetMethodID(
-      clazz, "getPlayStatus", "()Lcom/android/bluetooth/avrcp/PlayStatus;");
+      clazz, "getPlayStatus", "()Lcom/android/bluetooth/audio_util/PlayStatus;");
 
   method_sendMediaKeyEvent =
       env->GetMethodID(clazz, "sendMediaKeyEvent", "(IZ)V");
@@ -225,6 +225,27 @@
   sServiceInterface->Init(&mAvrcpInterface, &mVolumeInterface);
 }
 
+static void registerBipServerNative(JNIEnv* env, jobject object,
+                                    jint l2cap_psm) {
+  ALOGD("%s: l2cap_psm=%d", __func__, (int)l2cap_psm);
+  std::unique_lock<std::shared_timed_mutex> interface_lock(interface_mutex);
+  if (sServiceInterface == nullptr) {
+    ALOGW("%s: Service not loaded.", __func__);
+    return;
+  }
+  sServiceInterface->RegisterBipServer((int)l2cap_psm);
+}
+
+static void unregisterBipServerNative(JNIEnv* env, jobject object) {
+  ALOGD("%s", __func__);
+  std::unique_lock<std::shared_timed_mutex> interface_lock(interface_mutex);
+  if (sServiceInterface == nullptr) {
+    ALOGW("%s: Service not loaded.", __func__);
+    return;
+  }
+  sServiceInterface->UnregisterBipServer();
+}
+
 static void sendMediaUpdateNative(JNIEnv* env, jobject object,
                                   jboolean metadata, jboolean state,
                                   jboolean queue) {
@@ -316,6 +337,27 @@
       state == KeyState::PUSHED ? JNI_TRUE : JNI_FALSE);
 }
 
+static std::string getImageHandleFromJavaObj(JNIEnv* env, jobject image) {
+  std::string handle;
+
+  if (image == nullptr) return handle;
+
+  jclass class_image = env->GetObjectClass(image);
+  jmethodID method_getImageHandle =
+      env->GetMethodID(class_image, "getImageHandle", "()Ljava/lang/String;");
+  jstring imageHandle = (jstring) env->CallObjectMethod(
+      image, method_getImageHandle);
+  if (imageHandle == nullptr) {
+    return handle;
+  }
+
+  const char* value = env->GetStringUTFChars(imageHandle, nullptr);
+  handle = std::string(value);
+  env->ReleaseStringUTFChars(imageHandle, value);
+  env->DeleteLocalRef(imageHandle);
+  return handle;
+}
+
 static SongInfo getSongInfoFromJavaObj(JNIEnv* env, jobject metadata) {
   SongInfo info;
 
@@ -338,6 +380,8 @@
       env->GetFieldID(class_metadata, "genre", "Ljava/lang/String;");
   jfieldID field_playingTime =
       env->GetFieldID(class_metadata, "duration", "Ljava/lang/String;");
+  jfieldID field_image =
+      env->GetFieldID(class_metadata, "image", "Lcom/android/bluetooth/audio_util/Image;");
 
   jstring jstr = (jstring)env->GetObjectField(metadata, field_mediaId);
   if (jstr != nullptr) {
@@ -410,6 +454,16 @@
     env->DeleteLocalRef(jstr);
   }
 
+  jobject object_image = env->GetObjectField(metadata, field_image);
+  if (object_image != nullptr) {
+    std::string imageHandle = getImageHandleFromJavaObj(env, object_image);
+    if (!imageHandle.empty()) {
+      info.attributes.insert(
+          AttributeEntry(Attribute::DEFAULT_COVER_ART, imageHandle));
+    }
+    env->DeleteLocalRef(object_image);
+  }
+
   return info;
 }
 
@@ -428,6 +482,7 @@
     const char* value = env->GetStringUTFChars(jstr, nullptr);
     info.media_id = std::string(value);
     env->ReleaseStringUTFChars(jstr, value);
+    env->DeleteLocalRef(jstr);
   }
 
   info.is_playable = env->GetBooleanField(folder, field_isPlayable) == JNI_TRUE;
@@ -437,6 +492,7 @@
     const char* value = env->GetStringUTFChars(jstr, nullptr);
     info.name = std::string(value);
     env->ReleaseStringUTFChars(jstr, value);
+    env->DeleteLocalRef(jstr);
   }
 
   return info;
@@ -450,7 +506,9 @@
 
   jobject metadata =
       sCallbackEnv->CallObjectMethod(mJavaInterface, method_getCurrentSongInfo);
-  return getSongInfoFromJavaObj(sCallbackEnv.get(), metadata);
+  SongInfo info = getSongInfoFromJavaObj(sCallbackEnv.get(), metadata);
+  sCallbackEnv->DeleteLocalRef(metadata);
+  return info;
 }
 
 static PlayStatus getCurrentPlayStatus() {
@@ -480,6 +538,8 @@
   status.duration = sCallbackEnv->GetLongField(playStatus, field_duration);
   status.state = (PlayState)sCallbackEnv->GetByteField(playStatus, field_state);
 
+  sCallbackEnv->DeleteLocalRef(playStatus);
+
   return status;
 }
 
@@ -499,6 +559,7 @@
   const char* value = sCallbackEnv->GetStringUTFChars(media_id, nullptr);
   std::string ret(value);
   sCallbackEnv->ReleaseStringUTFChars(media_id, value);
+  sCallbackEnv->DeleteLocalRef(media_id);
   return ret;
 }
 
@@ -521,7 +582,10 @@
   jmethodID method_size = sCallbackEnv->GetMethodID(class_list, "size", "()I");
 
   auto size = sCallbackEnv->CallIntMethod(song_list, method_size);
-  if (size == 0) return std::vector<SongInfo>();
+  if (size == 0) {
+    sCallbackEnv->DeleteLocalRef(song_list);
+    return std::vector<SongInfo>();
+  }
   std::vector<SongInfo> ret;
   for (int i = 0; i < size; i++) {
     jobject song = sCallbackEnv->CallObjectMethod(song_list, method_get, i);
@@ -529,6 +593,8 @@
     sCallbackEnv->DeleteLocalRef(song);
   }
 
+  sCallbackEnv->DeleteLocalRef(song_list);
+
   return ret;
 }
 
@@ -566,11 +632,13 @@
 
   jint list_size = sCallbackEnv->CallIntMethod(player_list, method_size);
   if (list_size == 0) {
+    sCallbackEnv->DeleteLocalRef(player_list);
     return std::vector<MediaPlayerInfo>();
   }
 
-  jclass class_playerInfo = sCallbackEnv->GetObjectClass(
-      sCallbackEnv->CallObjectMethod(player_list, method_get, 0));
+  jobject player_info =
+      sCallbackEnv->CallObjectMethod(player_list, method_get, 0);
+  jclass class_playerInfo = sCallbackEnv->GetObjectClass(player_info);
   jfieldID field_playerId =
       sCallbackEnv->GetFieldID(class_playerInfo, "id", "I");
   jfieldID field_name =
@@ -602,6 +670,9 @@
     sCallbackEnv->DeleteLocalRef(player);
   }
 
+  sCallbackEnv->DeleteLocalRef(player_info);
+  sCallbackEnv->DeleteLocalRef(player_list);
+
   return ret_list;
 }
 
@@ -672,13 +743,13 @@
     return;
   }
 
-  jclass class_listItem =
-      env->GetObjectClass(env->CallObjectMethod(list, method_get, 0));
+  jobject list_item = env->CallObjectMethod(list, method_get, 0);
+  jclass class_listItem = env->GetObjectClass(list_item);
   jfieldID field_isFolder = env->GetFieldID(class_listItem, "isFolder", "Z");
   jfieldID field_folder = env->GetFieldID(
-      class_listItem, "folder", "Lcom/android/bluetooth/avrcp/Folder;");
+      class_listItem, "folder", "Lcom/android/bluetooth/audio_util/Folder;");
   jfieldID field_song = env->GetFieldID(
-      class_listItem, "song", "Lcom/android/bluetooth/avrcp/Metadata;");
+      class_listItem, "song", "Lcom/android/bluetooth/audio_util/Metadata;");
 
   std::vector<ListItem> ret_list;
   for (jsize i = 0; i < list_size; i++) {
@@ -687,22 +758,24 @@
     bool is_folder = env->GetBooleanField(item, field_isFolder) == JNI_TRUE;
 
     if (is_folder) {
+      jobject folder = env->GetObjectField(item, field_folder);
       ListItem temp = {ListItem::FOLDER,
-                       getFolderInfoFromJavaObj(
-                           env, env->GetObjectField(item, field_folder)),
+                       getFolderInfoFromJavaObj(env, folder),
                        SongInfo()};
-
       ret_list.push_back(temp);
+      env->DeleteLocalRef(folder);
     } else {
-      ListItem temp = {
-          ListItem::SONG, FolderInfo(),
-          getSongInfoFromJavaObj(env, env->GetObjectField(item, field_song))};
-
+      jobject song = env->GetObjectField(item, field_song);
+      ListItem temp = {ListItem::SONG, FolderInfo(),
+                       getSongInfoFromJavaObj(env, song)};
       ret_list.push_back(temp);
+      env->DeleteLocalRef(song);
     }
     env->DeleteLocalRef(item);
   }
 
+  env->DeleteLocalRef(list_item);
+
   callback.Run(std::move(ret_list));
 }
 
@@ -808,9 +881,30 @@
   sCallbackEnv->CallVoidMethod(mJavaInterface, method_setVolume, volume);
 }
 
+static void setBipClientStatusNative(JNIEnv* env, jobject object,
+                                    jstring address, jboolean connected) {
+  std::unique_lock<std::shared_timed_mutex> interface_lock(interface_mutex);
+  if (mServiceCallbacks == nullptr) {
+    ALOGW("%s: Service not loaded.", __func__);
+    return;
+  }
+
+  const char* tmp_addr = env->GetStringUTFChars(address, 0);
+  RawAddress bdaddr;
+  bool success = RawAddress::FromString(tmp_addr, bdaddr);
+  env->ReleaseStringUTFChars(address, tmp_addr);
+
+  if (!success) return;
+
+  bool status = (connected == JNI_TRUE);
+  sServiceInterface->SetBipClientStatus(bdaddr, status);
+}
+
 static JNINativeMethod sMethods[] = {
     {"classInitNative", "()V", (void*)classInitNative},
     {"initNative", "()V", (void*)initNative},
+    {"registerBipServerNative", "(I)V", (void*)registerBipServerNative},
+    {"unregisterBipServerNative", "()V", (void*)unregisterBipServerNative},
     {"sendMediaUpdateNative", "(ZZZ)V", (void*)sendMediaUpdateNative},
     {"sendFolderUpdateNative", "(ZZZ)V", (void*)sendFolderUpdateNative},
     {"setBrowsedPlayerResponseNative", "(IZLjava/lang/String;I)V",
@@ -824,6 +918,8 @@
      (void*)disconnectDeviceNative},
     {"sendVolumeChangedNative", "(Ljava/lang/String;I)V",
      (void*)sendVolumeChangedNative},
+    {"setBipClientStatusNative", "(Ljava/lang/String;Z)V",
+     (void*)setBipClientStatusNative},
 };
 
 int register_com_android_bluetooth_avrcp_target(JNIEnv* env) {
diff --git a/jni/com_android_bluetooth_btservice_ActivityAttribution.cpp b/jni/com_android_bluetooth_btservice_ActivityAttribution.cpp
new file mode 100644
index 0000000..791e42a
--- /dev/null
+++ b/jni/com_android_bluetooth_btservice_ActivityAttribution.cpp
@@ -0,0 +1,174 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "BluetoothActivityAttributionJni"
+
+#include <string.h>
+
+#include <shared_mutex>
+
+#include "base/logging.h"
+#include "com_android_bluetooth.h"
+#include "hardware/bt_activity_attribution.h"
+
+using bluetooth::activity_attribution::ActivityAttributionCallbacks;
+using bluetooth::activity_attribution::ActivityAttributionInterface;
+
+namespace android {
+static jmethodID method_onWakeup;
+static jmethodID method_onActivityLogsReady;
+
+static ActivityAttributionInterface* sActivityAttributionInterface = nullptr;
+static std::shared_timed_mutex interface_mutex;
+
+static jobject mCallbacksObj = nullptr;
+static std::shared_timed_mutex callbacks_mutex;
+
+class ActivityAttributionCallbacksImpl : public ActivityAttributionCallbacks {
+ public:
+  ~ActivityAttributionCallbacksImpl() = default;
+
+  void OnWakeup(const Activity activity, const RawAddress& bd_addr) override {
+    LOG(INFO) << __func__;
+
+    std::shared_lock<std::shared_timed_mutex> lock(callbacks_mutex);
+    CallbackEnv sCallbackEnv(__func__);
+    if (!sCallbackEnv.valid() || mCallbacksObj == nullptr) return;
+
+    ScopedLocalRef<jbyteArray> addr(
+        sCallbackEnv.get(), sCallbackEnv->NewByteArray(sizeof(RawAddress)));
+    if (!addr.get()) {
+      LOG(ERROR)
+          << "Failed to allocate jbyteArray for bd_addr of wakeup callback";
+      return;
+    }
+
+    sCallbackEnv->SetByteArrayRegion(addr.get(), 0, sizeof(RawAddress),
+                                     (jbyte*)&bd_addr);
+    sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onWakeup, (jint)activity,
+                                 addr.get());
+  }
+
+  void OnActivityLogsReady(
+      const std::vector<BtaaAggregationEntry> logs) override {
+    LOG(INFO) << __func__;
+
+    std::shared_lock<std::shared_timed_mutex> lock(callbacks_mutex);
+    CallbackEnv sCallbackEnv(__func__);
+    if (!sCallbackEnv.valid() || mCallbacksObj == nullptr) return;
+
+    jsize logs_size = logs.size() * sizeof(BtaaAggregationEntry);
+    ScopedLocalRef<jbyteArray> addr(sCallbackEnv.get(),
+                                    sCallbackEnv->NewByteArray(logs_size));
+    if (!addr.get()) {
+      LOG(ERROR) << "Failed to allocate jbyteArray for logs from activity "
+                    "logging callback";
+      return;
+    }
+
+    sCallbackEnv->SetByteArrayRegion(addr.get(), 0, logs_size,
+                                     (jbyte*)logs.data());
+    sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onActivityLogsReady,
+                                 addr.get());
+  }
+};
+
+static ActivityAttributionCallbacksImpl sActivityAttributionCallbacks;
+
+static void classInitNative(JNIEnv* env, jclass clazz) {
+  method_onWakeup = env->GetMethodID(clazz, "onWakeup", "(I[B)V");
+  method_onActivityLogsReady =
+      env->GetMethodID(clazz, "onActivityLogsReady", "([B)V");
+
+  LOG(INFO) << __func__ << ": succeeds";
+}
+
+static void initNative(JNIEnv* env, jobject object) {
+  std::unique_lock<std::shared_timed_mutex> interface_lock(interface_mutex);
+  std::unique_lock<std::shared_timed_mutex> callbacks_lock(callbacks_mutex);
+  const bt_interface_t* btInf = getBluetoothInterface();
+  if (btInf == nullptr) {
+    LOG(ERROR) << "Bluetooth module is not loaded";
+    return;
+  }
+
+  if (sActivityAttributionInterface != nullptr) {
+    LOG(INFO)
+        << "Cleaning up ActivityAttribution Interface before initializing...";
+    sActivityAttributionInterface->Cleanup();
+    sActivityAttributionInterface = nullptr;
+  }
+
+  if (mCallbacksObj != nullptr) {
+    LOG(INFO) << "Cleaning up ActivityAttribution callback object";
+    env->DeleteGlobalRef(mCallbacksObj);
+    mCallbacksObj = nullptr;
+  }
+
+  if ((mCallbacksObj = env->NewGlobalRef(object)) == nullptr) {
+    LOG(ERROR)
+        << "Failed to allocate Global Ref for ActivityAttribution Callbacks";
+    return;
+  }
+
+  sActivityAttributionInterface =
+      (ActivityAttributionInterface*)btInf->get_profile_interface(
+          BT_ACTIVITY_ATTRIBUTION_ID);
+  if (sActivityAttributionInterface == nullptr) {
+    LOG(ERROR) << "Failed to get ActivityAttribution Interface";
+    return;
+  }
+
+  sActivityAttributionInterface->RegisterCallbacks(
+      &sActivityAttributionCallbacks);
+}
+
+static void cleanupNative(JNIEnv* env, jobject object) {
+  std::unique_lock<std::shared_timed_mutex> interface_lock(interface_mutex);
+  std::unique_lock<std::shared_timed_mutex> callbacks_lock(callbacks_mutex);
+
+  const bt_interface_t* btInf = getBluetoothInterface();
+  if (btInf == nullptr) {
+    LOG(ERROR) << "Bluetooth module is not loaded";
+    return;
+  }
+
+  if (sActivityAttributionInterface != nullptr) {
+    sActivityAttributionInterface->Cleanup();
+    sActivityAttributionInterface = nullptr;
+  }
+
+  if (mCallbacksObj != nullptr) {
+    env->DeleteGlobalRef(mCallbacksObj);
+    mCallbacksObj = nullptr;
+  }
+}
+
+static JNINativeMethod sMethods[] = {
+    {"classInitNative", "()V", (void*)classInitNative},
+    {"initNative", "()V", (void*)initNative},
+    {"cleanupNative", "()V", (void*)cleanupNative},
+};
+
+int register_com_android_bluetooth_btservice_activity_attribution(JNIEnv* env) {
+  return jniRegisterNativeMethods(
+      env,
+      "com/android/bluetooth/btservice/activityattribution/"
+      "ActivityAttributionNativeInterface",
+      sMethods, NELEM(sMethods));
+}
+
+}  // namespace android
diff --git a/jni/com_android_bluetooth_btservice_AdapterService.cpp b/jni/com_android_bluetooth_btservice_AdapterService.cpp
index 3e4d272..62c7d52 100644
--- a/jni/com_android_bluetooth_btservice_AdapterService.cpp
+++ b/jni/com_android_bluetooth_btservice_AdapterService.cpp
@@ -30,6 +30,7 @@
 #include <sys/stat.h>
 
 #include <hardware/bluetooth.h>
+#include <nativehelper/JNIPlatformHelp.h>
 #include <mutex>
 
 #include <pthread.h>
@@ -37,14 +38,28 @@
 using bluetooth::Uuid;
 
 namespace android {
-// OOB_LE_BD_ADDR_SIZE is 6 bytes addres + 1 byte address type
-#define OOB_LE_BD_ADDR_SIZE 7
+// Both
+// OOB_ADDRESS_SIZE is 6 bytes address + 1 byte address type
+#define OOB_ADDRESS_SIZE 7
+#define OOB_C_SIZE 16
+#define OOB_R_SIZE 16
+#define OOB_NAME_MAX_SIZE 256
+// Classic
+#define OOB_DATA_LEN_SIZE 2
+#define OOB_COD_SIZE 3
+// LE
 #define OOB_TK_SIZE 16
-#define OOB_LE_SC_C_SIZE 16
-#define OOB_LE_SC_R_SIZE 16
+#define OOB_LE_FLAG_SIZE 1
+#define OOB_LE_ROLE_SIZE 1
+#define OOB_LE_APPEARANCE_SIZE 2
+
+#define TRANSPORT_AUTO 0
+#define TRANSPORT_BREDR 1
+#define TRANSPORT_LE 2
 
 const jint INVALID_FD = -1;
 
+static jmethodID method_oobDataReceivedCallback;
 static jmethodID method_stateChangeCallback;
 static jmethodID method_adapterPropertyChangedCallback;
 static jmethodID method_devicePropertyChangedCallback;
@@ -54,6 +69,7 @@
 static jmethodID method_bondStateChangeCallback;
 static jmethodID method_aclStateChangeCallback;
 static jmethodID method_discoveryStateChangeCallback;
+static jmethodID method_linkQualityReportCallback;
 static jmethodID method_setWakeAlarm;
 static jmethodID method_acquireWakeLock;
 static jmethodID method_releaseWakeLock;
@@ -285,7 +301,7 @@
 }
 
 static void acl_state_changed_callback(bt_status_t status, RawAddress* bd_addr,
-                                       bt_acl_state_t state) {
+                                       bt_acl_state_t state, bt_hci_error_code_t hci_reason) {
   if (!bd_addr) {
     ALOGE("Address is null in %s", __func__);
     return;
@@ -304,7 +320,7 @@
                                    (jbyte*)bd_addr);
 
   sCallbackEnv->CallVoidMethod(sJniCallbacksObj, method_aclStateChangeCallback,
-                               (jint)status, addr.get(), (jint)state);
+                               (jint)status, addr.get(), (jint)state, (jint)hci_reason);
 }
 
 static void discovery_state_changed_callback(bt_discovery_state_t state) {
@@ -386,6 +402,168 @@
                                (jint)pairing_variant, pass_key);
 }
 
+static jobject createClassicOobDataObject(JNIEnv* env, bt_oob_data_t oob_data) {
+  ALOGV("%s", __func__);
+  jclass classicBuilderClass =
+      env->FindClass("android/bluetooth/OobData$ClassicBuilder");
+
+  jbyteArray confirmationHash = env->NewByteArray(OOB_C_SIZE);
+  env->SetByteArrayRegion(confirmationHash, 0, OOB_C_SIZE,
+                          reinterpret_cast<jbyte*>(oob_data.c));
+
+  jbyteArray oobDataLength = env->NewByteArray(OOB_DATA_LEN_SIZE);
+  env->SetByteArrayRegion(oobDataLength, 0, OOB_DATA_LEN_SIZE,
+                          reinterpret_cast<jbyte*>(oob_data.oob_data_length));
+
+  jbyteArray address = env->NewByteArray(OOB_ADDRESS_SIZE);
+  env->SetByteArrayRegion(address, 0, OOB_ADDRESS_SIZE,
+                          reinterpret_cast<jbyte*>(oob_data.address));
+
+  jmethodID classicBuilderConstructor =
+      env->GetMethodID(classicBuilderClass, "<init>", "([B[B[B)V");
+
+  jobject oobDataClassicBuilder =
+      env->NewObject(classicBuilderClass, classicBuilderConstructor,
+                     confirmationHash, oobDataLength, address);
+
+  jmethodID setRMethod =
+      env->GetMethodID(classicBuilderClass, "setRandomizerHash",
+                       "([B)Landroid/bluetooth/OobData$ClassicBuilder;");
+  jbyteArray randomizerHash = env->NewByteArray(OOB_R_SIZE);
+  env->SetByteArrayRegion(randomizerHash, 0, OOB_R_SIZE,
+                          reinterpret_cast<jbyte*>(oob_data.r));
+
+  oobDataClassicBuilder =
+      env->CallObjectMethod(oobDataClassicBuilder, setRMethod, randomizerHash);
+
+  jmethodID setNameMethod =
+      env->GetMethodID(classicBuilderClass, "setDeviceName",
+                       "([B)Landroid/bluetooth/OobData$ClassicBuilder;");
+
+  int name_char_count = 0;
+  for (int i = 0; i < OOB_NAME_MAX_SIZE; i++) {
+    if (oob_data.device_name[i] == 0) {
+      name_char_count = i;
+      break;
+    }
+  }
+
+  jbyteArray deviceName = env->NewByteArray(name_char_count);
+  env->SetByteArrayRegion(deviceName, 0, name_char_count,
+                          reinterpret_cast<jbyte*>(oob_data.device_name));
+
+  oobDataClassicBuilder =
+      env->CallObjectMethod(oobDataClassicBuilder, setNameMethod, deviceName);
+
+  jmethodID buildMethod = env->GetMethodID(classicBuilderClass, "build",
+                                           "()Landroid/bluetooth/OobData;");
+
+  return env->CallObjectMethod(oobDataClassicBuilder, buildMethod);
+}
+
+static jobject createLeOobDataObject(JNIEnv* env, bt_oob_data_t oob_data) {
+  ALOGV("%s", __func__);
+
+  jclass leBuilderClass = env->FindClass("android/bluetooth/OobData$LeBuilder");
+
+  jbyteArray confirmationHash = env->NewByteArray(OOB_C_SIZE);
+  env->SetByteArrayRegion(confirmationHash, 0, OOB_C_SIZE,
+                          reinterpret_cast<jbyte*>(oob_data.c));
+
+  jbyteArray address = env->NewByteArray(OOB_ADDRESS_SIZE);
+  env->SetByteArrayRegion(address, 0, OOB_ADDRESS_SIZE,
+                          reinterpret_cast<jbyte*>(oob_data.address));
+
+  jint le_role = (jint)oob_data.le_device_role;
+
+  jmethodID leBuilderConstructor =
+      env->GetMethodID(leBuilderClass, "<init>", "([B[BI)V");
+
+  jobject oobDataLeBuilder = env->NewObject(
+      leBuilderClass, leBuilderConstructor, confirmationHash, address, le_role);
+
+  jmethodID setRMethod =
+      env->GetMethodID(leBuilderClass, "setRandomizerHash",
+                       "([B)Landroid/bluetooth/OobData$LeBuilder;");
+  jbyteArray randomizerHash = env->NewByteArray(OOB_R_SIZE);
+  env->SetByteArrayRegion(randomizerHash, 0, OOB_R_SIZE,
+                          reinterpret_cast<jbyte*>(oob_data.r));
+
+  oobDataLeBuilder =
+      env->CallObjectMethod(oobDataLeBuilder, setRMethod, randomizerHash);
+
+  jmethodID setNameMethod =
+      env->GetMethodID(leBuilderClass, "setDeviceName",
+                       "([B)Landroid/bluetooth/OobData$LeBuilder;");
+
+  int name_char_count = 0;
+  for (int i = 0; i < OOB_NAME_MAX_SIZE; i++) {
+    if (oob_data.device_name[i] == 0) {
+      name_char_count = i;
+      break;
+    }
+  }
+
+  jbyteArray deviceName = env->NewByteArray(name_char_count);
+  env->SetByteArrayRegion(deviceName, 0, name_char_count,
+                          reinterpret_cast<jbyte*>(oob_data.device_name));
+
+  oobDataLeBuilder =
+      env->CallObjectMethod(oobDataLeBuilder, setNameMethod, deviceName);
+
+  jmethodID buildMethod = env->GetMethodID(leBuilderClass, "build",
+                                           "()Landroid/bluetooth/OobData;");
+
+  return env->CallObjectMethod(oobDataLeBuilder, buildMethod);
+}
+
+static void generate_local_oob_data_callback(tBT_TRANSPORT transport,
+                                             bt_oob_data_t oob_data) {
+  ALOGV("%s", __func__);
+  CallbackEnv sCallbackEnv(__func__);
+  if (!sCallbackEnv.valid()) return;
+
+  if (transport == TRANSPORT_BREDR) {
+    sCallbackEnv->CallVoidMethod(
+        sJniCallbacksObj, method_oobDataReceivedCallback, (jint)transport,
+        ((oob_data.is_valid)
+             ? createClassicOobDataObject(sCallbackEnv.get(), oob_data)
+             : nullptr));
+  } else if (transport == TRANSPORT_LE) {
+    sCallbackEnv->CallVoidMethod(
+        sJniCallbacksObj, method_oobDataReceivedCallback, (jint)transport,
+        ((oob_data.is_valid)
+             ? createLeOobDataObject(sCallbackEnv.get(), oob_data)
+             : nullptr));
+  } else {
+    // TRANSPORT_AUTO is a concept, however, the host stack doesn't fully
+    // implement it So passing it from the java layer is currently useless until
+    // the implementation and concept of TRANSPORT_AUTO is fleshed out.
+    ALOGE("TRANSPORT: %d not implemented", transport);
+    sCallbackEnv->CallVoidMethod(sJniCallbacksObj,
+                                 method_oobDataReceivedCallback,
+                                 (jint)transport, nullptr);
+  }
+}
+
+static void link_quality_report_callback(
+    uint64_t timestamp, int report_id, int rssi, int snr,
+    int retransmission_count, int packets_not_receive_count,
+    int negative_acknowledgement_count) {
+  CallbackEnv sCallbackEnv(__func__);
+  if (!sCallbackEnv.valid()) return;
+
+  ALOGV("%s: LinkQualityReportCallback: %d %d %d %d %d %d", __func__,
+        report_id, rssi, snr, retransmission_count, packets_not_receive_count,
+        negative_acknowledgement_count);
+
+  sCallbackEnv->CallVoidMethod(
+      sJniCallbacksObj, method_linkQualityReportCallback,
+      (jlong)timestamp, (jint)report_id, (jint)rssi, (jint)snr,
+      (jint)retransmission_count, (jint)packets_not_receive_count,
+      (jint)negative_acknowledgement_count);
+}
+
 static void callback_thread_event(bt_cb_thread_evt event) {
   if (event == ASSOCIATE_JVM) {
     JavaVMAttachArgs args;
@@ -447,13 +625,14 @@
 }
 
 static bt_callbacks_t sBluetoothCallbacks = {
-    sizeof(sBluetoothCallbacks), adapter_state_change_callback,
-    adapter_properties_callback, remote_device_properties_callback,
-    device_found_callback,       discovery_state_changed_callback,
-    pin_request_callback,        ssp_request_callback,
-    bond_state_changed_callback, acl_state_changed_callback,
-    callback_thread_event,       dut_mode_recv_callback,
-    le_test_mode_recv_callback,  energy_info_recv_callback};
+    sizeof(sBluetoothCallbacks),  adapter_state_change_callback,
+    adapter_properties_callback,  remote_device_properties_callback,
+    device_found_callback,        discovery_state_changed_callback,
+    pin_request_callback,         ssp_request_callback,
+    bond_state_changed_callback,  acl_state_changed_callback,
+    callback_thread_event,        dut_mode_recv_callback,
+    le_test_mode_recv_callback,   energy_info_recv_callback,
+    link_quality_report_callback, generate_local_oob_data_callback};
 
 // The callback to call when the wake alarm fires.
 static alarm_cb sAlarmCallback;
@@ -639,6 +818,10 @@
   sJniCallbacksField = env->GetFieldID(
       clazz, "mJniCallbacks", "Lcom/android/bluetooth/btservice/JniCallbacks;");
 
+  method_oobDataReceivedCallback =
+      env->GetMethodID(jniCallbackClass, "oobDataReceivedCallback",
+                       "(ILandroid/bluetooth/OobData;)V");
+
   method_stateChangeCallback =
       env->GetMethodID(jniCallbackClass, "stateChangeCallback", "(I)V");
 
@@ -660,7 +843,10 @@
       env->GetMethodID(jniCallbackClass, "bondStateChangeCallback", "(I[BI)V");
 
   method_aclStateChangeCallback =
-      env->GetMethodID(jniCallbackClass, "aclStateChangeCallback", "(I[BI)V");
+      env->GetMethodID(jniCallbackClass, "aclStateChangeCallback", "(I[BII)V");
+
+  method_linkQualityReportCallback = env->GetMethodID(
+      jniCallbackClass, "linkQualityReportCallback", "(JIIIIII)V");
 
   method_setWakeAlarm = env->GetMethodID(clazz, "setWakeAlarm", "(JZ)Z");
   method_acquireWakeLock =
@@ -680,8 +866,8 @@
 }
 
 static bool initNative(JNIEnv* env, jobject obj, jboolean isGuest,
-                       jboolean isNiapMode, int configCompareResult,
-                       jboolean isAtvDevice) {
+                       jboolean isCommonCriteriaMode, int configCompareResult,
+                       jobjectArray initFlags, jboolean isAtvDevice) {
   ALOGV("%s", __func__);
 
   android_bluetooth_UidTraffic.clazz =
@@ -695,10 +881,31 @@
     return JNI_FALSE;
   }
 
+  int flagCount = env->GetArrayLength(initFlags);
+  jstring* flagObjs = new jstring[flagCount];
+  const char** flags = nullptr;
+  if (flagCount > 0) {
+    flags = new const char*[flagCount + 1];
+    flags[flagCount] = nullptr;
+  }
+
+  for (int i = 0; i < flagCount; i++) {
+    flagObjs[i] = (jstring)env->GetObjectArrayElement(initFlags, i);
+    flags[i] = env->GetStringUTFChars(flagObjs[i], NULL);
+  }
+
   int ret = sBluetoothInterface->init(
       &sBluetoothCallbacks, isGuest == JNI_TRUE ? 1 : 0,
-      isNiapMode == JNI_TRUE ? 1 : 0, configCompareResult,
+      isCommonCriteriaMode == JNI_TRUE ? 1 : 0, configCompareResult, flags,
       isAtvDevice == JNI_TRUE ? 1 : 0);
+
+  for (int i = 0; i < flagCount; i++) {
+    env->ReleaseStringUTFChars(flagObjs[i], flags[i]);
+  }
+
+  delete[] flags;
+  delete[] flagObjs;
+
   if (ret != BT_STATUS_SUCCESS) {
     ALOGE("Error while setting the callbacks: %d\n", ret);
     sBluetoothInterface = NULL;
@@ -812,106 +1019,267 @@
   return (jbyteArray)env->CallObjectMethod(object, myMethod);
 }
 
-static jboolean createBondOutOfBandNative(JNIEnv* env, jobject obj,
-                                          jbyteArray address, jint transport,
-                                          jobject oobData) {
-  bt_out_of_band_data_t oob_data;
+static jint callIntGetter(JNIEnv* env, jobject object, const char* className,
+                          const char* methodName) {
+  jclass myClass = env->FindClass(className);
+  jmethodID myMethod = env->GetMethodID(myClass, methodName, "()I");
+  return env->CallIntMethod(object, myMethod);
+}
+
+static jboolean set_data(JNIEnv* env, bt_oob_data_t& oob_data, jobject oobData,
+                         jint transport) {
+  // Need both arguments to be non NULL
+  if (oobData == NULL) {
+    ALOGE("%s: oobData is null! Nothing to do.", __func__);
+    return JNI_FALSE;
+  }
 
   memset(&oob_data, 0, sizeof(oob_data));
 
+  jbyteArray address = callByteArrayGetter(
+      env, oobData, "android/bluetooth/OobData", "getDeviceAddressWithType");
+
+  // Check the data
+  int len = env->GetArrayLength(address);
+  if (len != OOB_ADDRESS_SIZE) {
+    ALOGE("%s: addressBytes must be 7 bytes in length (address plus type) 6+1!",
+          __func__);
+    jniThrowIOException(env, EINVAL);
+    return JNI_FALSE;
+  }
+
+  // Convert the address from byte[]
+  jbyte* addressBytes = env->GetByteArrayElements(address, NULL);
+  if (addressBytes == NULL) {
+    ALOGE("%s: addressBytes cannot be null!", __func__);
+    jniThrowIOException(env, EINVAL);
+    return JNI_FALSE;
+  }
+  memcpy(oob_data.address, addressBytes, len);
+
+  // Get the device name byte[] java object
+  jbyteArray deviceName = callByteArrayGetter(
+      env, oobData, "android/bluetooth/OobData", "getDeviceName");
+
+  // Optional
+  // Convert it to a jbyte* and copy it to the struct
+  jbyte* deviceNameBytes = NULL;
+  if (deviceName != NULL) {
+    deviceNameBytes = env->GetByteArrayElements(deviceName, NULL);
+    int len = env->GetArrayLength(deviceName);
+    if (len > OOB_NAME_MAX_SIZE) {
+      ALOGI(
+          "%s: wrong length of deviceName, should be empty or less than or "
+          "equal to %d bytes.",
+          __func__, OOB_NAME_MAX_SIZE);
+      jniThrowIOException(env, EINVAL);
+      env->ReleaseByteArrayElements(deviceName, deviceNameBytes, 0);
+      return JNI_FALSE;
+    }
+    memcpy(oob_data.device_name, deviceNameBytes, len);
+    env->ReleaseByteArrayElements(deviceName, deviceNameBytes, 0);
+  }
+  // Used by both classic and LE
+  jbyteArray confirmation = callByteArrayGetter(
+      env, oobData, "android/bluetooth/OobData", "getConfirmationHash");
+  if (confirmation == NULL) {
+    ALOGE("%s: confirmation cannot be null!", __func__);
+    jniThrowIOException(env, EINVAL);
+    return JNI_FALSE;
+  }
+
+  // Confirmation is mandatory
+  jbyte* confirmationBytes = NULL;
+  confirmationBytes = env->GetByteArrayElements(confirmation, NULL);
+  len = env->GetArrayLength(confirmation);
+  if (confirmationBytes == NULL || len != OOB_C_SIZE) {
+    ALOGI(
+        "%s: wrong length of Confirmation, should be empty or %d "
+        "bytes.",
+        __func__, OOB_C_SIZE);
+    jniThrowIOException(env, EINVAL);
+    env->ReleaseByteArrayElements(confirmation, confirmationBytes, 0);
+    return JNI_FALSE;
+  }
+  memcpy(oob_data.c, confirmationBytes, len);
+  env->ReleaseByteArrayElements(confirmation, confirmationBytes, 0);
+
+  // Random is supposedly optional according to the specification
+  jbyteArray randomizer = callByteArrayGetter(
+      env, oobData, "android/bluetooth/OobData", "getRandomizerHash");
+  jbyte* randomizerBytes = NULL;
+  if (randomizer != NULL) {
+    randomizerBytes = env->GetByteArrayElements(randomizer, NULL);
+    int len = env->GetArrayLength(randomizer);
+    if (randomizerBytes == NULL || len != OOB_R_SIZE) {
+      ALOGI("%s: wrong length of Random, should be empty or %d bytes.",
+            __func__, OOB_R_SIZE);
+      jniThrowIOException(env, EINVAL);
+      env->ReleaseByteArrayElements(randomizer, randomizerBytes, 0);
+      return JNI_FALSE;
+    }
+    memcpy(oob_data.r, randomizerBytes, len);
+    env->ReleaseByteArrayElements(randomizer, randomizerBytes, 0);
+  }
+
+  // Transport specific data fetching/setting
+  if (transport == TRANSPORT_BREDR) {
+    // Classic
+    // Not optional
+    jbyteArray oobDataLength = callByteArrayGetter(
+        env, oobData, "android/bluetooth/OobData", "getClassicLength");
+    jbyte* oobDataLengthBytes = NULL;
+    if (oobDataLength == NULL ||
+        env->GetArrayLength(oobDataLength) != OOB_DATA_LEN_SIZE) {
+      ALOGI("%s: wrong length of oobDataLength, should be empty or %d bytes.",
+            __func__, OOB_DATA_LEN_SIZE);
+      jniThrowIOException(env, EINVAL);
+      env->ReleaseByteArrayElements(oobDataLength, oobDataLengthBytes, 0);
+      return JNI_FALSE;
+    }
+
+    oobDataLengthBytes = env->GetByteArrayElements(oobDataLength, NULL);
+    memcpy(oob_data.oob_data_length, oobDataLengthBytes, len);
+    env->ReleaseByteArrayElements(oobDataLength, oobDataLengthBytes, 0);
+
+    // Optional
+    jbyteArray classOfDevice = callByteArrayGetter(
+        env, oobData, "android/bluetooth/OobData", "getClassOfDevice");
+    jbyte* classOfDeviceBytes = NULL;
+    if (classOfDevice != NULL) {
+      classOfDeviceBytes = env->GetByteArrayElements(classOfDevice, NULL);
+      int len = env->GetArrayLength(classOfDevice);
+      if (len != OOB_COD_SIZE) {
+        ALOGI("%s: wrong length of classOfDevice, should be empty or %d bytes.",
+              __func__, OOB_COD_SIZE);
+        jniThrowIOException(env, EINVAL);
+        env->ReleaseByteArrayElements(classOfDevice, classOfDeviceBytes, 0);
+        return JNI_FALSE;
+      }
+      memcpy(oob_data.class_of_device, classOfDeviceBytes, len);
+      env->ReleaseByteArrayElements(classOfDevice, classOfDeviceBytes, 0);
+    }
+  } else if (transport == TRANSPORT_LE) {
+    // LE
+    jbyteArray temporaryKey = callByteArrayGetter(
+        env, oobData, "android/bluetooth/OobData", "getLeTemporaryKey");
+    jbyte* temporaryKeyBytes = NULL;
+    if (temporaryKey != NULL) {
+      temporaryKeyBytes = env->GetByteArrayElements(temporaryKey, NULL);
+      int len = env->GetArrayLength(temporaryKey);
+      if (len != OOB_TK_SIZE) {
+        ALOGI("%s: wrong length of temporaryKey, should be empty or %d bytes.",
+              __func__, OOB_TK_SIZE);
+        jniThrowIOException(env, EINVAL);
+        env->ReleaseByteArrayElements(temporaryKey, temporaryKeyBytes, 0);
+        return JNI_FALSE;
+      }
+      memcpy(oob_data.sm_tk, temporaryKeyBytes, len);
+      env->ReleaseByteArrayElements(temporaryKey, temporaryKeyBytes, 0);
+    }
+
+    jbyteArray leAppearance = callByteArrayGetter(
+        env, oobData, "android/bluetooth/OobData", "getLeAppearance");
+    jbyte* leAppearanceBytes = NULL;
+    if (leAppearance != NULL) {
+      leAppearanceBytes = env->GetByteArrayElements(leAppearance, NULL);
+      int len = env->GetArrayLength(leAppearance);
+      if (len != OOB_LE_APPEARANCE_SIZE) {
+        ALOGI("%s: wrong length of leAppearance, should be empty or %d bytes.",
+              __func__, OOB_LE_APPEARANCE_SIZE);
+        jniThrowIOException(env, EINVAL);
+        env->ReleaseByteArrayElements(leAppearance, leAppearanceBytes, 0);
+        return JNI_FALSE;
+      }
+      memcpy(oob_data.le_appearance, leAppearanceBytes, len);
+      env->ReleaseByteArrayElements(leAppearance, leAppearanceBytes, 0);
+    }
+
+    jint leRole = callIntGetter(env, oobData, "android/bluetooth/OobData",
+                                "getLeDeviceRole");
+    oob_data.le_device_role = leRole;
+
+    jint leFlag =
+        callIntGetter(env, oobData, "android/bluetooth/OobData", "getLeFlags");
+    oob_data.le_flags = leFlag;
+  }
+  return JNI_TRUE;
+}
+
+static void generateLocalOobDataNative(JNIEnv* env, jobject obj,
+                                       jint transport) {
+  // No BT interface? Can't do anything.
+  if (!sBluetoothInterface) return;
+
+  if (sBluetoothInterface->generate_local_oob_data(transport) !=
+      BT_STATUS_SUCCESS) {
+    ALOGE("%s: Call to generate_local_oob_data failed!", __func__);
+    bt_oob_data_t oob_data;
+    oob_data.is_valid = false;
+    generate_local_oob_data_callback(transport, oob_data);
+  }
+}
+
+static jboolean createBondOutOfBandNative(JNIEnv* env, jobject obj,
+                                          jbyteArray address, jint transport,
+                                          jobject p192Data, jobject p256Data) {
+  // No BT interface? Can't do anything.
   if (!sBluetoothInterface) return JNI_FALSE;
 
+  // No data? Can't do anything
+  if (p192Data == NULL && p256Data == NULL) {
+    ALOGE("%s: All OOB Data are null! Nothing to do.", __func__);
+    jniThrowIOException(env, EINVAL);
+    return JNI_FALSE;
+  }
+
+  // This address is already reversed which is why its being passed...
+  // In the future we want to remove this and just reverse the address
+  // for the oobdata in the host stack.
+  if (address == NULL) {
+    ALOGE("%s: Address cannot be null! Nothing to do.", __func__);
+    jniThrowIOException(env, EINVAL);
+    return JNI_FALSE;
+  }
+
+  // Check the data
+  int len = env->GetArrayLength(address);
+  if (len != 6) {
+    ALOGE("%s: addressBytes must be 6 bytes in length (address plus type) 6+1!",
+          __func__);
+    jniThrowIOException(env, EINVAL);
+    return JNI_FALSE;
+  }
+
   jbyte* addr = env->GetByteArrayElements(address, NULL);
   if (addr == NULL) {
     jniThrowIOException(env, EINVAL);
     return JNI_FALSE;
   }
 
-  jbyte* leBtDeviceAddressBytes = NULL;
-  jbyte* smTKBytes = NULL;
-  jbyte* leScCBytes = NULL;
-  jbyte* leScRBytes = NULL;
-  jbyteArray leBtDeviceAddress = NULL;
-  jbyteArray smTK = NULL;
-  jbyteArray leScC = NULL;
-  jbyteArray leScR = NULL;
-  int status = BT_STATUS_FAIL;
-
-  leBtDeviceAddress = callByteArrayGetter(
-      env, oobData, "android/bluetooth/OobData", "getLeBluetoothDeviceAddress");
-  if (leBtDeviceAddress != NULL) {
-    leBtDeviceAddressBytes = env->GetByteArrayElements(leBtDeviceAddress, NULL);
-    int len = env->GetArrayLength(leBtDeviceAddress);
-    if (len != OOB_LE_BD_ADDR_SIZE) {
-      ALOGI(
-          "%s: wrong length of leBtDeviceAddress, should be empty or %d bytes.",
-          __func__, OOB_LE_BD_ADDR_SIZE);
+  // Convert P192 data from Java POJO to C Struct
+  bt_oob_data_t p192_data;
+  if (p192Data != NULL) {
+    if (set_data(env, p192_data, p192Data, transport) == JNI_FALSE) {
       jniThrowIOException(env, EINVAL);
-      goto done;
+      return JNI_FALSE;
     }
-    memcpy(oob_data.le_bt_dev_addr, leBtDeviceAddressBytes, len);
   }
 
-  smTK = callByteArrayGetter(env, oobData, "android/bluetooth/OobData",
-                             "getSecurityManagerTk");
-  if (smTK != NULL) {
-    smTKBytes = env->GetByteArrayElements(smTK, NULL);
-    int len = env->GetArrayLength(smTK);
-    if (len != OOB_TK_SIZE) {
-      ALOGI("%s: wrong length of smTK, should be empty or %d bytes.", __func__,
-            OOB_TK_SIZE);
+  // Convert P256 data from Java POJO to C Struct
+  bt_oob_data_t p256_data;
+  if (p256Data != NULL) {
+    if (set_data(env, p256_data, p256Data, transport) == JNI_FALSE) {
       jniThrowIOException(env, EINVAL);
-      goto done;
+      return JNI_FALSE;
     }
-    memcpy(oob_data.sm_tk, smTKBytes, len);
   }
 
-  leScC = callByteArrayGetter(env, oobData, "android/bluetooth/OobData",
-                              "getLeSecureConnectionsConfirmation");
-  if (leScC != NULL) {
-    leScCBytes = env->GetByteArrayElements(leScC, NULL);
-    int len = env->GetArrayLength(leScC);
-    if (len != OOB_LE_SC_C_SIZE) {
-      ALOGI(
-          "%s: wrong length of LE SC Confirmation, should be empty or %d "
-          "bytes.",
-          __func__, OOB_LE_SC_C_SIZE);
-      jniThrowIOException(env, EINVAL);
-      goto done;
-    }
-    memcpy(oob_data.le_sc_c, leScCBytes, len);
-  }
-
-  leScR = callByteArrayGetter(env, oobData, "android/bluetooth/OobData",
-                              "getLeSecureConnectionsRandom");
-  if (leScR != NULL) {
-    leScRBytes = env->GetByteArrayElements(leScR, NULL);
-    int len = env->GetArrayLength(leScR);
-    if (len != OOB_LE_SC_R_SIZE) {
-      ALOGI("%s: wrong length of LE SC Random, should be empty or %d bytes.",
-            __func__, OOB_LE_SC_R_SIZE);
-      jniThrowIOException(env, EINVAL);
-      goto done;
-    }
-    memcpy(oob_data.le_sc_r, leScRBytes, len);
-  }
-
-  status = sBluetoothInterface->create_bond_out_of_band((RawAddress*)addr,
-                                                        transport, &oob_data);
-
-done:
-  env->ReleaseByteArrayElements(address, addr, 0);
-
-  if (leBtDeviceAddress != NULL)
-    env->ReleaseByteArrayElements(leBtDeviceAddress, leBtDeviceAddressBytes, 0);
-
-  if (smTK != NULL) env->ReleaseByteArrayElements(smTK, smTKBytes, 0);
-
-  if (leScC != NULL) env->ReleaseByteArrayElements(leScC, leScCBytes, 0);
-
-  if (leScR != NULL) env->ReleaseByteArrayElements(leScR, leScRBytes, 0);
-
-  return (status == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE;
+  return ((sBluetoothInterface->create_bond_out_of_band(
+              (RawAddress*)addr, transport, &p192_data, &p256_data)) ==
+          BT_STATUS_SUCCESS)
+             ? JNI_TRUE
+             : JNI_FALSE;
 }
 
 static jboolean removeBondNative(JNIEnv* env, jobject obj, jbyteArray address) {
@@ -1137,7 +1505,10 @@
 
   jstring* argObjs = new jstring[numArgs];
   const char** args = nullptr;
-  if (numArgs > 0) args = new const char*[numArgs];
+  if (numArgs > 0) {
+    args = new const char*[numArgs + 1];
+    args[numArgs] = nullptr;
+  }
 
   for (int i = 0; i < numArgs; i++) {
     argObjs[i] = (jstring)env->GetObjectArrayElement(argArray, i);
@@ -1214,6 +1585,16 @@
   return output_bytes;
 }
 
+static jboolean setBufferLengthMillisNative(JNIEnv* env, jobject obj,
+                                            jint codec, jint size) {
+  ALOGV("%s", __func__);
+
+  if (!sBluetoothInterface) return JNI_FALSE;
+
+  int ret = sBluetoothInterface->set_dynamic_audio_buffer_size(codec, size);
+  return (ret == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE;
+}
+
 static jint connectSocketNative(JNIEnv* env, jobject obj, jbyteArray address,
                                 jint type, jbyteArray uuid, jint port,
                                 jint flag, jint callingUid) {
@@ -1311,7 +1692,7 @@
 static JNINativeMethod sMethods[] = {
     /* name, signature, funcPtr */
     {"classInitNative", "()V", (void*)classInitNative},
-    {"initNative", "(ZZIZ)Z", (void*)initNative},
+    {"initNative", "(ZZI[Ljava/lang/String;Z)Z", (void*)initNative},
     {"cleanupNative", "()V", (void*)cleanupNative},
     {"enableNative", "()Z", (void*)enableNative},
     {"disableNative", "()Z", (void*)disableNative},
@@ -1323,10 +1704,12 @@
     {"startDiscoveryNative", "()Z", (void*)startDiscoveryNative},
     {"cancelDiscoveryNative", "()Z", (void*)cancelDiscoveryNative},
     {"createBondNative", "([BI)Z", (void*)createBondNative},
-    {"createBondOutOfBandNative", "([BILandroid/bluetooth/OobData;)Z",
+    {"createBondOutOfBandNative",
+     "([BILandroid/bluetooth/OobData;Landroid/bluetooth/OobData;)Z",
      (void*)createBondOutOfBandNative},
     {"removeBondNative", "([B)Z", (void*)removeBondNative},
     {"cancelBondNative", "([B)Z", (void*)cancelBondNative},
+    {"generateLocalOobDataNative", "(I)V", (void*)generateLocalOobDataNative},
     {"getConnectionStateNative", "([B)I", (void*)getConnectionStateNative},
     {"pinReplyNative", "([BZI[B)Z", (void*)pinReplyNative},
     {"sspReplyNative", "([BIZI)Z", (void*)sspReplyNative},
@@ -1340,6 +1723,8 @@
     {"interopDatabaseClearNative", "()V", (void*)interopDatabaseClearNative},
     {"interopDatabaseAddNative", "(I[BI)V", (void*)interopDatabaseAddNative},
     {"obfuscateAddressNative", "([B)[B", (void*)obfuscateAddressNative},
+    {"setBufferLengthMillisNative", "(II)Z",
+     (void*)setBufferLengthMillisNative},
     {"getMetricIdNative", "([B)I", (void*)getMetricIdNative},
     {"connectSocketNative", "([BI[BIII)I", (void*)connectSocketNative},
     {"createSocketChannelNative", "(ILjava/lang/String;[BIII)I",
@@ -1377,6 +1762,13 @@
   }
 
   status =
+      android::register_com_android_bluetooth_btservice_activity_attribution(e);
+  if (status < 0) {
+    ALOGE("jni activity attribution registration failure: %d", status);
+    return JNI_ERR;
+  }
+
+  status =
       android::register_com_android_bluetooth_btservice_BluetoothKeystore(e);
   if (status < 0) {
     ALOGE("jni BluetoothKeyStore registration failure: %d", status);
@@ -1454,5 +1846,11 @@
     return JNI_ERR;
   }
 
+  status = android::register_com_android_bluetooth_le_audio(e);
+  if (status < 0) {
+    ALOGE("jni le_audio registration failure: %d", status);
+    return JNI_ERR;
+  }
+
   return JNI_VERSION_1_6;
 }
diff --git a/jni/com_android_bluetooth_gatt.cpp b/jni/com_android_bluetooth_gatt.cpp
index 64db277..ead3f68 100644
--- a/jni/com_android_bluetooth_gatt.cpp
+++ b/jni/com_android_bluetooth_gatt.cpp
@@ -142,6 +142,7 @@
 static jmethodID method_onClientPhyUpdate;
 static jmethodID method_onClientPhyRead;
 static jmethodID method_onClientConnUpdate;
+static jmethodID method_onServiceChanged;
 
 /**
  * Server callback methods
@@ -528,6 +529,13 @@
                                conn_id, interval, latency, timeout, status);
 }
 
+void btgattc_service_changed_cb(int conn_id) {
+  CallbackEnv sCallbackEnv(__func__);
+  if (!sCallbackEnv.valid()) return;
+
+  sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onServiceChanged, conn_id);
+}
+
 static const btgatt_scanner_callbacks_t sGattScannerCallbacks = {
     btgattc_scan_result_cb,
     btgattc_batchscan_reports_cb,
@@ -554,7 +562,9 @@
     NULL, /* services_removed_cb */
     NULL, /* services_added_cb */
     btgattc_phy_updated_cb,
-    btgattc_conn_updated_cb};
+    btgattc_conn_updated_cb,
+    btgattc_service_changed_cb,
+};
 
 /**
  * BTA server callbacks
@@ -764,6 +774,192 @@
     &sGattScannerCallbacks,
 };
 
+class JniAdvertisingCallbacks : AdvertisingCallbacks {
+ public:
+  static AdvertisingCallbacks* GetInstance() {
+    static AdvertisingCallbacks* instance = new JniAdvertisingCallbacks();
+    return instance;
+  }
+
+  void OnAdvertisingSetStarted(int reg_id, uint8_t advertiser_id,
+                               int8_t tx_power, uint8_t status) {
+    CallbackEnv sCallbackEnv(__func__);
+    if (!sCallbackEnv.valid()) return;
+    sCallbackEnv->CallVoidMethod(mAdvertiseCallbacksObj,
+                                 method_onAdvertisingSetStarted, reg_id,
+                                 advertiser_id, tx_power, status);
+  }
+
+  void OnAdvertisingEnabled(uint8_t advertiser_id, bool enable,
+                            uint8_t status) {
+    CallbackEnv sCallbackEnv(__func__);
+    if (!sCallbackEnv.valid()) return;
+    sCallbackEnv->CallVoidMethod(mAdvertiseCallbacksObj,
+                                 method_onAdvertisingEnabled, advertiser_id,
+                                 enable, status);
+  }
+
+  void OnAdvertisingDataSet(uint8_t advertiser_id, uint8_t status) {
+    CallbackEnv sCallbackEnv(__func__);
+    if (!sCallbackEnv.valid()) return;
+    sCallbackEnv->CallVoidMethod(mAdvertiseCallbacksObj,
+                                 method_onAdvertisingDataSet, advertiser_id,
+                                 status);
+  }
+
+  void OnScanResponseDataSet(uint8_t advertiser_id, uint8_t status) {
+    CallbackEnv sCallbackEnv(__func__);
+    if (!sCallbackEnv.valid()) return;
+    sCallbackEnv->CallVoidMethod(mAdvertiseCallbacksObj,
+                                 method_onScanResponseDataSet, advertiser_id,
+                                 status);
+  }
+
+  void OnAdvertisingParametersUpdated(uint8_t advertiser_id, int8_t tx_power,
+                                      uint8_t status) {
+    CallbackEnv sCallbackEnv(__func__);
+    if (!sCallbackEnv.valid()) return;
+    sCallbackEnv->CallVoidMethod(mAdvertiseCallbacksObj,
+                                 method_onAdvertisingParametersUpdated,
+                                 advertiser_id, tx_power, status);
+  }
+
+  void OnPeriodicAdvertisingParametersUpdated(uint8_t advertiser_id,
+                                              uint8_t status) {
+    CallbackEnv sCallbackEnv(__func__);
+    if (!sCallbackEnv.valid()) return;
+    sCallbackEnv->CallVoidMethod(mAdvertiseCallbacksObj,
+                                 method_onPeriodicAdvertisingParametersUpdated,
+                                 advertiser_id, status);
+  }
+
+  void OnPeriodicAdvertisingDataSet(uint8_t advertiser_id, uint8_t status) {
+    CallbackEnv sCallbackEnv(__func__);
+    if (!sCallbackEnv.valid()) return;
+    sCallbackEnv->CallVoidMethod(mAdvertiseCallbacksObj,
+                                 method_onPeriodicAdvertisingDataSet,
+                                 advertiser_id, status);
+  }
+
+  void OnPeriodicAdvertisingEnabled(uint8_t advertiser_id, bool enable,
+                                    uint8_t status) {
+    CallbackEnv sCallbackEnv(__func__);
+    if (!sCallbackEnv.valid()) return;
+    sCallbackEnv->CallVoidMethod(mAdvertiseCallbacksObj,
+                                 method_onPeriodicAdvertisingEnabled,
+                                 advertiser_id, enable, status);
+  }
+
+  void OnOwnAddressRead(uint8_t advertiser_id, uint8_t address_type,
+                        RawAddress address) {
+    CallbackEnv sCallbackEnv(__func__);
+    if (!sCallbackEnv.valid()) return;
+
+    ScopedLocalRef<jstring> addr(sCallbackEnv.get(),
+                                 bdaddr2newjstr(sCallbackEnv.get(), &address));
+    sCallbackEnv->CallVoidMethod(mAdvertiseCallbacksObj,
+                                 method_onOwnAddressRead, advertiser_id,
+                                 address_type, addr.get());
+  }
+};
+
+class JniScanningCallbacks : ScanningCallbacks {
+ public:
+  static ScanningCallbacks* GetInstance() {
+    static ScanningCallbacks* instance = new JniScanningCallbacks();
+    return instance;
+  }
+
+  void OnScannerRegistered(const Uuid app_uuid, uint8_t scannerId,
+                           uint8_t status) {
+    CallbackEnv sCallbackEnv(__func__);
+    if (!sCallbackEnv.valid()) return;
+    sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onScannerRegistered,
+                                 status, scannerId, UUID_PARAMS(app_uuid));
+  }
+
+  void OnScanResult(uint16_t event_type, uint8_t addr_type, RawAddress bda,
+                    uint8_t primary_phy, uint8_t secondary_phy,
+                    uint8_t advertising_sid, int8_t tx_power, int8_t rssi,
+                    uint16_t periodic_adv_int, std::vector<uint8_t> adv_data) {
+    CallbackEnv sCallbackEnv(__func__);
+    if (!sCallbackEnv.valid()) return;
+
+    ScopedLocalRef<jstring> address(sCallbackEnv.get(),
+                                    bdaddr2newjstr(sCallbackEnv.get(), &bda));
+    ScopedLocalRef<jbyteArray> jb(sCallbackEnv.get(),
+                                  sCallbackEnv->NewByteArray(adv_data.size()));
+    sCallbackEnv->SetByteArrayRegion(jb.get(), 0, adv_data.size(),
+                                     (jbyte*)adv_data.data());
+
+    sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onScanResult, event_type,
+                                 addr_type, address.get(), primary_phy,
+                                 secondary_phy, advertising_sid, tx_power, rssi,
+                                 periodic_adv_int, jb.get());
+  }
+
+  void OnTrackAdvFoundLost(AdvertisingTrackInfo track_info) {
+    CallbackEnv sCallbackEnv(__func__);
+    if (!sCallbackEnv.valid()) return;
+
+    ScopedLocalRef<jstring> address(
+        sCallbackEnv.get(),
+        bdaddr2newjstr(sCallbackEnv.get(), &track_info.advertiser_address));
+
+    ScopedLocalRef<jbyteArray> jb_adv_pkt(
+        sCallbackEnv.get(),
+        sCallbackEnv->NewByteArray(track_info.adv_packet_len));
+    ScopedLocalRef<jbyteArray> jb_scan_rsp(
+        sCallbackEnv.get(),
+        sCallbackEnv->NewByteArray(track_info.scan_response_len));
+
+    sCallbackEnv->SetByteArrayRegion(jb_adv_pkt.get(), 0,
+                                     track_info.adv_packet_len,
+                                     (jbyte*)track_info.adv_packet.data());
+
+    sCallbackEnv->SetByteArrayRegion(jb_scan_rsp.get(), 0,
+                                     track_info.scan_response_len,
+                                     (jbyte*)track_info.scan_response.data());
+
+    ScopedLocalRef<jobject> trackadv_obj(
+        sCallbackEnv.get(),
+        sCallbackEnv->CallObjectMethod(
+            mCallbacksObj, method_createOnTrackAdvFoundLostObject,
+            track_info.scanner_id, track_info.adv_packet_len, jb_adv_pkt.get(),
+            track_info.scan_response_len, jb_scan_rsp.get(),
+            track_info.filter_index, track_info.advertiser_state,
+            track_info.advertiser_info_present, address.get(),
+            track_info.advertiser_address_type, track_info.tx_power,
+            track_info.rssi, track_info.time_stamp));
+
+    if (NULL != trackadv_obj.get()) {
+      sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onTrackAdvFoundLost,
+                                   trackadv_obj.get());
+    }
+  }
+
+  void OnBatchScanReports(int client_if, int status, int report_format,
+                          int num_records, std::vector<uint8_t> data) {
+    CallbackEnv sCallbackEnv(__func__);
+    if (!sCallbackEnv.valid()) return;
+    ScopedLocalRef<jbyteArray> jb(sCallbackEnv.get(),
+                                  sCallbackEnv->NewByteArray(data.size()));
+    sCallbackEnv->SetByteArrayRegion(jb.get(), 0, data.size(),
+                                     (jbyte*)data.data());
+
+    sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onBatchScanReports,
+                                 status, client_if, report_format, num_records,
+                                 jb.get());
+  }
+
+  void OnBatchScanThresholdCrossed(int client_if) {
+    CallbackEnv sCallbackEnv(__func__);
+    if (!sCallbackEnv.valid()) return;
+    sCallbackEnv->CallVoidMethod(mCallbacksObj,
+                                 method_onBatchScanThresholdCrossed, client_if);
+  }
+};
+
 /**
  * Native function definitions
  */
@@ -835,6 +1031,8 @@
       env->GetMethodID(clazz, "onClientPhyUpdate", "(IIII)V");
   method_onClientConnUpdate =
       env->GetMethodID(clazz, "onClientConnUpdate", "(IIIII)V");
+  method_onServiceChanged =
+      env->GetMethodID(clazz, "onServiceChanged", "(I)V");
 
   // Server callbacks
 
@@ -912,6 +1110,10 @@
     return;
   }
 
+  sGattIf->advertiser->RegisterCallbacks(
+      JniAdvertisingCallbacks::GetInstance());
+  sGattIf->scanner->RegisterCallbacks(JniScanningCallbacks::GetInstance());
+
   mCallbacksObj = env->NewGlobalRef(object);
 }
 
@@ -941,11 +1143,11 @@
 }
 
 static void gattClientRegisterAppNative(JNIEnv* env, jobject object,
-                                        jlong app_uuid_lsb,
-                                        jlong app_uuid_msb) {
+                                        jlong app_uuid_lsb, jlong app_uuid_msb,
+                                        jboolean eatt_support) {
   if (!sGattIf) return;
   Uuid uuid = from_java_uuid(app_uuid_msb, app_uuid_lsb);
-  sGattIf->client->register_client(uuid);
+  sGattIf->client->register_client(uuid, eatt_support);
 }
 
 static void gattClientUnregisterAppNative(JNIEnv* env, jobject object,
@@ -968,7 +1170,7 @@
 
   Uuid uuid = from_java_uuid(app_uuid_msb, app_uuid_lsb);
   sGattIf->scanner->RegisterScanner(
-      base::Bind(&btgattc_register_scanner_cb, uuid));
+      uuid, base::Bind(&btgattc_register_scanner_cb, uuid));
 }
 
 static void unregisterScannerNative(JNIEnv* env, jobject object,
@@ -1287,6 +1489,7 @@
   jfieldID addressFid =
       env->GetFieldID(entryClazz, "address", "Ljava/lang/String;");
   jfieldID addrTypeFid = env->GetFieldID(entryClazz, "addr_type", "B");
+  jfieldID irkTypeFid = env->GetFieldID(entryClazz, "irk", "[B");
   jfieldID uuidFid = env->GetFieldID(entryClazz, "uuid", "Ljava/util/UUID;");
   jfieldID uuidMaskFid =
       env->GetFieldID(entryClazz, "uuid_mask", "Ljava/util/UUID;");
@@ -1297,7 +1500,7 @@
   jfieldID dataMaskFid = env->GetFieldID(entryClazz, "data_mask", "[B");
 
   for (int i = 0; i < numFilters; ++i) {
-    ApcfCommand curr;
+    ApcfCommand curr{};
 
     ScopedLocalRef<jobject> current(env,
                                     env->GetObjectArrayElement(filters, i));
@@ -1312,6 +1515,25 @@
 
     curr.addr_type = env->GetByteField(current.get(), addrTypeFid);
 
+    ScopedLocalRef<jbyteArray> irkByteArray(
+        env, (jbyteArray)env->GetObjectField(current.get(), irkTypeFid));
+
+    if (irkByteArray.get() != nullptr) {
+      int len = env->GetArrayLength(irkByteArray.get());
+      // IRK is 128 bits or 16 octets, set the bytes or zero it out
+      if (len != 16) {
+        ALOGE("%s: Invalid IRK length '%d'; expected 16", __func__, len);
+        jniThrowIOException(env, EINVAL);
+      }
+      jbyte* irkBytes = env->GetByteArrayElements(irkByteArray.get(), NULL);
+      if (irkBytes == NULL) {
+        jniThrowIOException(env, EINVAL);
+      }
+      for (int j = 0; j < len; j++) {
+        curr.irk[j] = irkBytes[j];
+      }
+    }
+
     ScopedLocalRef<jobject> uuid(env,
                                  env->GetObjectField(current.get(), uuidFid));
     if (uuid.get() != NULL) {
@@ -1462,11 +1684,11 @@
  * Native server functions
  */
 static void gattServerRegisterAppNative(JNIEnv* env, jobject object,
-                                        jlong app_uuid_lsb,
-                                        jlong app_uuid_msb) {
+                                        jlong app_uuid_lsb, jlong app_uuid_msb,
+                                        jboolean eatt_support) {
   if (!sGattIf) return;
   Uuid uuid = from_java_uuid(app_uuid_msb, app_uuid_lsb);
-  sGattIf->server->register_server(uuid);
+  sGattIf->server->register_server(uuid, eatt_support);
 }
 
 static void gattServerUnregisterAppNative(JNIEnv* env, jobject object,
@@ -1825,8 +2047,8 @@
   env->ReleaseByteArrayElements(periodic_data, periodic_data_data, JNI_ABORT);
 
   sGattIf->advertiser->StartAdvertisingSet(
-      base::Bind(&ble_advertising_set_started_cb, reg_id), params, data_vec,
-      scan_resp_vec, periodicParams, periodic_data_vec, duration,
+      reg_id, base::Bind(&ble_advertising_set_started_cb, reg_id), params,
+      data_vec, scan_resp_vec, periodicParams, periodic_data_vec, duration,
       maxExtAdvEvents, base::Bind(ble_advertising_set_timeout_cb));
 }
 
@@ -2139,7 +2361,7 @@
     {"cleanupNative", "()V", (void*)cleanupNative},
     {"gattClientGetDeviceTypeNative", "(Ljava/lang/String;)I",
      (void*)gattClientGetDeviceTypeNative},
-    {"gattClientRegisterAppNative", "(JJ)V",
+    {"gattClientRegisterAppNative", "(JJZ)V",
      (void*)gattClientRegisterAppNative},
     {"gattClientUnregisterAppNative", "(I)V",
      (void*)gattClientUnregisterAppNative},
@@ -2178,7 +2400,7 @@
      (void*)gattClientConfigureMTUNative},
     {"gattConnectionParameterUpdateNative", "(ILjava/lang/String;IIIIII)V",
      (void*)gattConnectionParameterUpdateNative},
-    {"gattServerRegisterAppNative", "(JJ)V",
+    {"gattServerRegisterAppNative", "(JJZ)V",
      (void*)gattServerRegisterAppNative},
     {"gattServerUnregisterAppNative", "(I)V",
      (void*)gattServerUnregisterAppNative},
diff --git a/jni/com_android_bluetooth_hearing_aid.cpp b/jni/com_android_bluetooth_hearing_aid.cpp
index b459ae2..e359faf 100644
--- a/jni/com_android_bluetooth_hearing_aid.cpp
+++ b/jni/com_android_bluetooth_hearing_aid.cpp
@@ -193,7 +193,7 @@
   return JNI_TRUE;
 }
 
-static jboolean addToWhiteListNative(JNIEnv* env, jobject object,
+static jboolean addToAcceptlistNative(JNIEnv* env, jobject object,
                                      jbyteArray address) {
   std::shared_lock<std::shared_timed_mutex> lock(interface_mutex);
   if (!sHearingAidInterface) return JNI_FALSE;
@@ -204,7 +204,7 @@
   }
 
   RawAddress* tmpraw = (RawAddress*)addr;
-  sHearingAidInterface->AddToWhiteList(*tmpraw);
+  sHearingAidInterface->AddToAcceptlist(*tmpraw);
   env->ReleaseByteArrayElements(address, addr, 0);
   return JNI_TRUE;
 }
@@ -224,7 +224,7 @@
     {"cleanupNative", "()V", (void*)cleanupNative},
     {"connectHearingAidNative", "([B)Z", (void*)connectHearingAidNative},
     {"disconnectHearingAidNative", "([B)Z", (void*)disconnectHearingAidNative},
-    {"addToWhiteListNative", "([B)Z", (void*)addToWhiteListNative},
+    {"addToAcceptlistNative", "([B)Z", (void*)addToAcceptlistNative},
     {"setVolumeNative", "(I)V", (void*)setVolumeNative},
 };
 
diff --git a/jni/com_android_bluetooth_hfp.cpp b/jni/com_android_bluetooth_hfp.cpp
index 9918c87..813bdd0 100644
--- a/jni/com_android_bluetooth_hfp.cpp
+++ b/jni/com_android_bluetooth_hfp.cpp
@@ -593,6 +593,44 @@
   return (status == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE;
 }
 
+static jboolean isNoiseReductionSupportedNative(JNIEnv* env, jobject object,
+                                                jbyteArray address) {
+  std::shared_lock<std::shared_timed_mutex> lock(interface_mutex);
+  if (!sBluetoothHfpInterface) {
+    ALOGW("%s: sBluetoothHfpInterface is null", __func__);
+    return JNI_FALSE;
+  }
+  jbyte* addr = env->GetByteArrayElements(address, nullptr);
+  if (!addr) {
+    ALOGE("%s: failed to get device address", __func__);
+    jniThrowIOException(env, EINVAL);
+    return JNI_FALSE;
+  }
+  bt_status_t status =
+      sBluetoothHfpInterface->isNoiseReductionSupported((RawAddress*)addr);
+  env->ReleaseByteArrayElements(address, addr, 0);
+  return (status == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE;
+}
+
+static jboolean isVoiceRecognitionSupportedNative(JNIEnv* env, jobject object,
+                                                  jbyteArray address) {
+  std::shared_lock<std::shared_timed_mutex> lock(interface_mutex);
+  if (!sBluetoothHfpInterface) {
+    ALOGW("%s: sBluetoothHfpInterface is null", __func__);
+    return JNI_FALSE;
+  }
+  jbyte* addr = env->GetByteArrayElements(address, nullptr);
+  if (!addr) {
+    ALOGE("%s: failed to get device address", __func__);
+    jniThrowIOException(env, EINVAL);
+    return JNI_FALSE;
+  }
+  bt_status_t status =
+      sBluetoothHfpInterface->isVoiceRecognitionSupported((RawAddress*)addr);
+  env->ReleaseByteArrayElements(address, addr, 0);
+  return (status == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE;
+}
+
 static jboolean startVoiceRecognitionNative(JNIEnv* env, jobject object,
                                             jbyteArray address) {
   std::shared_lock<std::shared_timed_mutex> lock(interface_mutex);
@@ -924,6 +962,10 @@
     {"disconnectHfpNative", "([B)Z", (void*)disconnectHfpNative},
     {"connectAudioNative", "([B)Z", (void*)connectAudioNative},
     {"disconnectAudioNative", "([B)Z", (void*)disconnectAudioNative},
+    {"isNoiseReductionSupportedNative", "([B)Z",
+     (void*)isNoiseReductionSupportedNative},
+    {"isVoiceRecognitionSupportedNative", "([B)Z",
+     (void*)isVoiceRecognitionSupportedNative},
     {"startVoiceRecognitionNative", "([B)Z",
      (void*)startVoiceRecognitionNative},
     {"stopVoiceRecognitionNative", "([B)Z", (void*)stopVoiceRecognitionNative},
diff --git a/jni/com_android_bluetooth_hid_host.cpp b/jni/com_android_bluetooth_hid_host.cpp
index cab5e33..074e39d 100644
--- a/jni/com_android_bluetooth_hid_host.cpp
+++ b/jni/com_android_bluetooth_hid_host.cpp
@@ -274,7 +274,7 @@
 
   jboolean ret = JNI_TRUE;
   bt_status_t status = sBluetoothHidInterface->connect((RawAddress*)addr);
-  if (status != BT_STATUS_SUCCESS) {
+  if (status != BT_STATUS_SUCCESS && status != BT_STATUS_BUSY) {
     ALOGE("Failed HID channel connection, status: %d", status);
     ret = JNI_FALSE;
   }
diff --git a/jni/com_android_bluetooth_le_audio.cpp b/jni/com_android_bluetooth_le_audio.cpp
new file mode 100644
index 0000000..a8f5dbe
--- /dev/null
+++ b/jni/com_android_bluetooth_le_audio.cpp
@@ -0,0 +1,280 @@
+/*   Copyright 2019 HIMSA II K/S - www.himsa.com
+ * Represented by EHIMA - www.ehima.com
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "BluetoothLeAudioServiceJni"
+
+#define LOG_NDEBUG 0
+
+#include <hardware/bluetooth.h>
+
+#include <array>
+#include <optional>
+#include <shared_mutex>
+
+#include "com_android_bluetooth.h"
+#include "hardware/bt_le_audio.h"
+
+using bluetooth::le_audio::ConnectionState;
+using bluetooth::le_audio::GroupStatus;
+using bluetooth::le_audio::LeAudioClientCallbacks;
+using bluetooth::le_audio::LeAudioClientInterface;
+
+namespace android {
+static jmethodID method_onConnectionStateChanged;
+static jmethodID method_onGroupStatus;
+static jmethodID method_onAudioConf;
+static jmethodID method_onSetMemberAvailable;
+
+static LeAudioClientInterface* sLeAudioClientInterface = nullptr;
+static std::shared_timed_mutex interface_mutex;
+
+static jobject mCallbacksObj = nullptr;
+static std::shared_timed_mutex callbacks_mutex;
+
+class LeAudioClientCallbacksImpl : public LeAudioClientCallbacks {
+ public:
+  ~LeAudioClientCallbacksImpl() = default;
+
+  void OnConnectionState(ConnectionState state,
+                         const RawAddress& bd_addr) override {
+    LOG(INFO) << __func__ << ", state:" << int(state);
+
+    std::shared_lock<std::shared_timed_mutex> lock(callbacks_mutex);
+    CallbackEnv sCallbackEnv(__func__);
+    if (!sCallbackEnv.valid() || mCallbacksObj == nullptr) return;
+
+    ScopedLocalRef<jbyteArray> addr(
+        sCallbackEnv.get(), sCallbackEnv->NewByteArray(sizeof(RawAddress)));
+    if (!addr.get()) {
+      LOG(ERROR) << "Failed to new jbyteArray bd addr for connection state";
+      return;
+    }
+
+    sCallbackEnv->SetByteArrayRegion(addr.get(), 0, sizeof(RawAddress),
+                                     (jbyte*)&bd_addr);
+    sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onConnectionStateChanged,
+                                 (jint)state, addr.get());
+  }
+
+  void OnGroupStatus(uint8_t group_id, GroupStatus group_status,
+                     uint8_t group_flags) override {
+    LOG(INFO) << __func__;
+
+    std::shared_lock<std::shared_timed_mutex> lock(callbacks_mutex);
+    CallbackEnv sCallbackEnv(__func__);
+    if (!sCallbackEnv.valid() || mCallbacksObj == nullptr) return;
+
+    sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onGroupStatus,
+                                 (jint)group_id, (jint)group_status,
+                                 (jint)group_flags);
+  }
+
+  void OnAudioConf(const RawAddress& bd_addr, uint8_t direction,
+                   uint8_t group_id, uint32_t sink_audio_location,
+                   uint32_t source_audio_location) override {
+    LOG(INFO) << __func__;
+
+    std::shared_lock<std::shared_timed_mutex> lock(callbacks_mutex);
+    CallbackEnv sCallbackEnv(__func__);
+    if (!sCallbackEnv.valid() || mCallbacksObj == nullptr) return;
+
+    ScopedLocalRef<jbyteArray> addr(
+        sCallbackEnv.get(), sCallbackEnv->NewByteArray(sizeof(RawAddress)));
+    if (!addr.get()) {
+      LOG(ERROR) << "Failed to new jbyteArray bd addr for group status";
+      return;
+    }
+
+    sCallbackEnv->SetByteArrayRegion(addr.get(), 0, sizeof(RawAddress),
+                                     (jbyte*)&bd_addr);
+    sCallbackEnv->CallVoidMethod(
+        mCallbacksObj, method_onAudioConf, (jint)direction, (jint)group_id,
+        (jint)sink_audio_location, (jint)source_audio_location, addr.get());
+  }
+
+  void OnSetMemberAvailable(const RawAddress& bd_addr,
+                            uint8_t group_id) override {
+    LOG(INFO) << __func__ << ", group id:" << int(group_id);
+
+    std::shared_lock<std::shared_timed_mutex> lock(callbacks_mutex);
+    CallbackEnv sCallbackEnv(__func__);
+    if (!sCallbackEnv.valid() || mCallbacksObj == nullptr) return;
+
+    ScopedLocalRef<jbyteArray> addr(
+        sCallbackEnv.get(), sCallbackEnv->NewByteArray(sizeof(RawAddress)));
+    if (!addr.get()) {
+      LOG(ERROR) << "Failed to new jbyteArray bd addr for connection state";
+      return;
+    }
+
+    sCallbackEnv->SetByteArrayRegion(addr.get(), 0, sizeof(RawAddress),
+                                     (jbyte*)&bd_addr);
+    sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onSetMemberAvailable,
+                                 addr.get(), (jint)group_id);
+  }
+};
+
+static LeAudioClientCallbacksImpl sLeAudioClientCallbacks;
+
+static void classInitNative(JNIEnv* env, jclass clazz) {
+  method_onGroupStatus = env->GetMethodID(clazz, "onGroupStatus", "(III)V");
+  method_onAudioConf = env->GetMethodID(clazz, "onAudioConf", "(IIII[B)V");
+  method_onConnectionStateChanged =
+      env->GetMethodID(clazz, "onConnectionStateChanged", "(I[B)V");
+  method_onSetMemberAvailable =
+      env->GetMethodID(clazz, "onSetMemberAvailable", "([BI)V");
+}
+
+static void initNative(JNIEnv* env, jobject object) {
+  std::unique_lock<std::shared_timed_mutex> interface_lock(interface_mutex);
+  std::unique_lock<std::shared_timed_mutex> callbacks_lock(callbacks_mutex);
+
+  const bt_interface_t* btInf = getBluetoothInterface();
+  if (btInf == nullptr) {
+    LOG(ERROR) << "Bluetooth module is not loaded";
+    return;
+  }
+
+  if (mCallbacksObj != nullptr) {
+    LOG(INFO) << "Cleaning up LeAudio callback object";
+    env->DeleteGlobalRef(mCallbacksObj);
+    mCallbacksObj = nullptr;
+  }
+
+  if ((mCallbacksObj = env->NewGlobalRef(object)) == nullptr) {
+    LOG(ERROR) << "Failed to allocate Global Ref for LeAudio Callbacks";
+    return;
+  }
+
+  sLeAudioClientInterface =
+      (LeAudioClientInterface*)btInf->get_profile_interface(
+          BT_PROFILE_LE_AUDIO_ID);
+  if (sLeAudioClientInterface == nullptr) {
+    LOG(ERROR) << "Failed to get Bluetooth LeAudio Interface";
+    return;
+  }
+
+  sLeAudioClientInterface->Initialize(&sLeAudioClientCallbacks);
+}
+
+static void cleanupNative(JNIEnv* env, jobject object) {
+  std::unique_lock<std::shared_timed_mutex> interface_lock(interface_mutex);
+  std::unique_lock<std::shared_timed_mutex> callbacks_lock(callbacks_mutex);
+
+  const bt_interface_t* btInf = getBluetoothInterface();
+  if (btInf == nullptr) {
+    LOG(ERROR) << "Bluetooth module is not loaded";
+    return;
+  }
+
+  if (sLeAudioClientInterface != nullptr) {
+    sLeAudioClientInterface->Cleanup();
+    sLeAudioClientInterface = nullptr;
+  }
+
+  if (mCallbacksObj != nullptr) {
+    env->DeleteGlobalRef(mCallbacksObj);
+    mCallbacksObj = nullptr;
+  }
+}
+
+static jboolean connectLeAudioNative(JNIEnv* env, jobject object,
+                                     jbyteArray address) {
+  LOG(INFO) << __func__;
+  std::shared_lock<std::shared_timed_mutex> lock(interface_mutex);
+  if (!sLeAudioClientInterface) return JNI_FALSE;
+
+  jbyte* addr = env->GetByteArrayElements(address, nullptr);
+  if (!addr) {
+    jniThrowIOException(env, EINVAL);
+    return JNI_FALSE;
+  }
+
+  RawAddress* tmpraw = (RawAddress*)addr;
+  sLeAudioClientInterface->Connect(*tmpraw);
+  env->ReleaseByteArrayElements(address, addr, 0);
+  return JNI_TRUE;
+}
+
+static jboolean disconnectLeAudioNative(JNIEnv* env, jobject object,
+                                        jbyteArray address) {
+  LOG(INFO) << __func__;
+  std::shared_lock<std::shared_timed_mutex> lock(interface_mutex);
+  if (!sLeAudioClientInterface) return JNI_FALSE;
+
+  jbyte* addr = env->GetByteArrayElements(address, nullptr);
+  if (!addr) {
+    jniThrowIOException(env, EINVAL);
+    return JNI_FALSE;
+  }
+
+  RawAddress* tmpraw = (RawAddress*)addr;
+  sLeAudioClientInterface->Disconnect(*tmpraw);
+  env->ReleaseByteArrayElements(address, addr, 0);
+  return JNI_TRUE;
+}
+
+static void groupStreamNative(JNIEnv* env, jobject object, jint group_id,
+                              jint content_type) {
+  LOG(INFO) << __func__;
+
+  if (!sLeAudioClientInterface) {
+    LOG(ERROR) << __func__ << ": Failed to get the Bluetooth LeAudio Interface";
+    return;
+  }
+
+  sLeAudioClientInterface->GroupStream(group_id, content_type);
+}
+
+static void groupSuspendNative(JNIEnv* env, jobject object, jint group_id) {
+  LOG(INFO) << __func__;
+
+  if (!sLeAudioClientInterface) {
+    LOG(ERROR) << __func__ << ": Failed to get the Bluetooth LeAudio Interface";
+    return;
+  }
+
+  sLeAudioClientInterface->GroupSuspend(group_id);
+}
+
+static void groupStopNative(JNIEnv* env, jobject object, jint group_id) {
+  LOG(INFO) << __func__;
+
+  if (!sLeAudioClientInterface) {
+    LOG(ERROR) << __func__ << ": Failed to get the Bluetooth LeAudio Interface";
+    return;
+  }
+
+  sLeAudioClientInterface->GroupStop(group_id);
+}
+
+static JNINativeMethod sMethods[] = {
+    {"classInitNative", "()V", (void*)classInitNative},
+    {"initNative", "()V", (void*)initNative},
+    {"cleanupNative", "()V", (void*)cleanupNative},
+    {"connectLeAudioNative", "([B)Z", (void*)connectLeAudioNative},
+    {"disconnectLeAudioNative", "([B)Z", (void*)disconnectLeAudioNative},
+    {"groupStreamNative", "(II)V", (void*)groupStreamNative},
+    {"groupSuspendNative", "(I)V", (void*)groupSuspendNative},
+    {"groupStopNative", "(I)V", (void*)groupStopNative},
+};
+
+int register_com_android_bluetooth_le_audio(JNIEnv* env) {
+  return jniRegisterNativeMethods(
+      env, "com/android/bluetooth/le_audio/LeAudioNativeInterface", sMethods,
+      NELEM(sMethods));
+}
+}  // namespace android
diff --git a/jni/com_android_bluetooth_pan.cpp b/jni/com_android_bluetooth_pan.cpp
index d423eab..b02ae9f 100644
--- a/jni/com_android_bluetooth_pan.cpp
+++ b/jni/com_android_bluetooth_pan.cpp
@@ -171,21 +171,6 @@
   btIf = NULL;
 }
 
-static jboolean enablePanNative(JNIEnv* env, jobject object, jint local_role) {
-  bt_status_t status = BT_STATUS_FAIL;
-  debug("in");
-  if (sPanIf) status = sPanIf->enable(local_role);
-  debug("out");
-  return status == BT_STATUS_SUCCESS ? JNI_TRUE : JNI_FALSE;
-}
-static jint getPanLocalRoleNative(JNIEnv* env, jobject object) {
-  debug("in");
-  int local_role = 0;
-  if (sPanIf) local_role = sPanIf->get_local_role();
-  debug("out");
-  return (jint)local_role;
-}
-
 static jboolean connectPanNative(JNIEnv* env, jobject object,
                                  jbyteArray address, jint src_role,
                                  jint dest_role) {
@@ -235,8 +220,6 @@
     {"initializeNative", "()V", (void*)initializeNative},
     {"cleanupNative", "()V", (void*)cleanupNative},
     {"connectPanNative", "([BII)Z", (void*)connectPanNative},
-    {"enablePanNative", "(I)Z", (void*)enablePanNative},
-    {"getPanLocalRoleNative", "()I", (void*)getPanLocalRoleNative},
     {"disconnectPanNative", "([B)Z", (void*)disconnectPanNative},
     // TBD cleanup
 };
diff --git a/jni/com_android_bluetooth_sdp.cpp b/jni/com_android_bluetooth_sdp.cpp
old mode 100644
new mode 100755
index 90e3fc1..bba109a
--- a/jni/com_android_bluetooth_sdp.cpp
+++ b/jni/com_android_bluetooth_sdp.cpp
@@ -31,6 +31,7 @@
 static const Uuid UUID_MAP_MAS = Uuid::From16Bit(0x1132);
 static const Uuid UUID_MAP_MNS = Uuid::From16Bit(0x1133);
 static const Uuid UUID_SAP = Uuid::From16Bit(0x112D);
+static const Uuid UUID_DIP = Uuid::From16Bit(0x1200);
 
 namespace android {
 static jmethodID method_sdpRecordFoundCallback;
@@ -39,6 +40,7 @@
 static jmethodID method_sdpPseRecordFoundCallback;
 static jmethodID method_sdpOppOpsRecordFoundCallback;
 static jmethodID method_sdpSapsRecordFoundCallback;
+static jmethodID method_sdpDipRecordFoundCallback;
 
 static const btsdp_interface_t* sBluetoothSdpInterface = NULL;
 
@@ -96,6 +98,9 @@
   /* SAP Server record */
   method_sdpSapsRecordFoundCallback = env->GetMethodID(
       clazz, "sdpSapsRecordFoundCallback", "(I[B[BIILjava/lang/String;Z)V");
+  /* DIP record */
+  method_sdpDipRecordFoundCallback = env->GetMethodID(
+      clazz, "sdpDipRecordFoundCallback", "(I[B[BIIIIIZZ)V");
 }
 
 static jboolean sdpSearchNative(JNIEnv* env, jobject obj, jbyteArray address,
@@ -214,6 +219,17 @@
           addr.get(), uuid.get(), (jint)record->mas.hdr.rfcomm_channel_number,
           (jint)record->mas.hdr.profile_version, service_name.get(),
           more_results);
+    } else if (uuid_in == UUID_DIP) {
+      ALOGD("%s, Get UUID_DIP", __func__);
+      sCallbackEnv->CallVoidMethod(
+          sCallbacksObj, method_sdpDipRecordFoundCallback, (jint)status,
+          addr.get(), uuid.get(), (jint)record->dip.spec_id,
+          (jint)record->dip.vendor,
+          (jint)record->dip.vendor_id_source,
+          (jint)record->dip.product,
+          (jint)record->dip.version,
+          record->dip.primary_record,
+          more_results);
     } else {
       // we don't have a wrapper for this uuid, send as raw data
       jint record_data_size = record->hdr.user1_ptr_len;
diff --git a/res/layout/bt_enabling_progress.xml b/res/layout/bt_enabling_progress.xml
index 0e4f870..02cfe4d 100644
--- a/res/layout/bt_enabling_progress.xml
+++ b/res/layout/bt_enabling_progress.xml
@@ -25,21 +25,26 @@
     <LinearLayout
         android:orientation="horizontal"
         android:layout_width="match_parent"
-        android:layout_height="match_parent">
+        android:layout_height="match_parent"
+        android:baselineAligned="false"
+        android:paddingStart="?android:attr/dialogPreferredPadding"
+        android:paddingTop="@*android:dimen/dialog_padding_top_material"
+        android:paddingEnd="?android:attr/dialogPreferredPadding"
+        android:paddingBottom="@*android:dimen/dialog_padding_top_material">
 
-        <ProgressBar android:id="@+id/progress"
+        <ProgressBar
+            android:id="@+id/progress"
+            style="?android:attr/progressBarStyle"
             android:layout_width="wrap_content"
-            android:layout_height="wrap_content" />
+            android:layout_height="wrap_content"
+            android:max="10000"
+            android:layout_marginEnd="?android:attr/dialogPreferredPadding" />
 
         <TextView
             android:id="@+id/progress_info"
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
-            android:layout_marginLeft="20dip"
-            android:layout_marginRight="20dip"
-            android:layout_gravity="center_vertical"
-            android:textAppearance="?android:attr/textAppearanceMedium" />
-
+            android:layout_gravity="center_vertical" />
      </LinearLayout>
 
  </ScrollView>
diff --git a/res/values-af/strings.xml b/res/values-af/strings.xml
index 04198e4..b854bdc 100644
--- a/res/values-af/strings.xml
+++ b/res/values-af/strings.xml
@@ -18,8 +18,8 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="permlab_bluetoothShareManager" msgid="311492132450338925">"Maak toegang aflaaibestuurder oop."</string>
     <string name="permdesc_bluetoothShareManager" msgid="8930572979123190223">"Gee aan die program toegang tot die BluetoothShare-bestuurder en om dit te gebruik om lêers oor te dra."</string>
-    <string name="permlab_bluetoothWhitelist" msgid="7091552898592306386">"Witlys Bluetooth-toesteltoegang."</string>
-    <string name="permdesc_bluetoothWhitelist" msgid="5494513855192170109">"Laat die program toe om \'n Bluetooth-toestel tydelik te witlys, sodat daardie toestel lêers na hierdie toestel kan stuur sonder die gebruiker se bevestiging."</string>
+    <string name="permlab_bluetoothAcceptlist" msgid="2647575807648622053">"Voeg Bluetooth-toesteltoegang by aanvaarlys."</string>
+    <string name="permdesc_bluetoothAcceptlist" msgid="2406423665674766442">"Laat die program toe om \'n Bluetooth-toestel tydelik by aanvaarlys te voeg, sodat daardie toestel lêers na hierdie toestel kan stuur sonder die gebruiker se bevestiging."</string>
     <string name="bt_share_picker_label" msgid="6268100924487046932">"Bluetooth"</string>
     <string name="unknown_device" msgid="9221903979877041009">"Onbekende toestel"</string>
     <string name="unknownNumber" msgid="4994750948072751566">"Onbekend"</string>
@@ -37,7 +37,7 @@
     <string name="incoming_file_confirm_timeout_ok" msgid="1414676773249857278">"OK"</string>
     <string name="incoming_file_confirm_timeout_content" msgid="172779756093975981">"Daar was \'n uittelling terwyl \'n inkomende lêer van \"<xliff:g id="SENDER">%1$s</xliff:g>\" aanvaar is"</string>
     <string name="incoming_file_confirm_Notification_title" msgid="5573329005298936903">"Inkomende lêer"</string>
-    <string name="incoming_file_confirm_Notification_content" msgid="3359694069319644738">"<xliff:g id="SENDER">%1$s</xliff:g> is gereed om <xliff:g id="FILE">%2$s</xliff:g> te stuur"</string>
+    <string name="incoming_file_confirm_Notification_content" msgid="6237487486781004992">"<xliff:g id="SENDER">%1$s</xliff:g> is gereed om \'n lêer te stuur: <xliff:g id="FILE">%2$s</xliff:g>"</string>
     <string name="notification_receiving" msgid="4674648179652543984">"Bluetooth-deling: Ontvang <xliff:g id="FILE">%1$s</xliff:g>"</string>
     <string name="notification_received" msgid="3324588019186687985">"Bluetooth-deling: Het \"<xliff:g id="FILE">%1$s</xliff:g>\" ontvang"</string>
     <string name="notification_received_fail" msgid="3619350997285714746">"Bluetooth-deling: Lêer <xliff:g id="FILE">%1$s</xliff:g> nie ontvang nie"</string>
@@ -83,8 +83,8 @@
     <string name="bt_toast_4" msgid="4678812947604395649">"Stuur lêer na \"<xliff:g id="RECIPIENT">%1$s</xliff:g>\""</string>
     <string name="bt_toast_5" msgid="2846870992823019494">"Stuur <xliff:g id="NUMBER">%1$s</xliff:g> lêers na \"<xliff:g id="RECIPIENT">%2$s</xliff:g>\""</string>
     <string name="bt_toast_6" msgid="1855266596936622458">"Opgehou om lêer na \"<xliff:g id="RECIPIENT">%1$s</xliff:g>\" te stuur"</string>
-    <string name="bt_sm_2_1_nosdcard" msgid="1791835163301501637">"Daar is te min spasie op die USB-berging om die lêer van \"<xliff:g id="SENDER">%1$s</xliff:g>\" af te stoor"</string>
-    <string name="bt_sm_2_1_default" msgid="9115512207909504071">"Daar is te min spasie op die SD-kaart om die lêer van \"<xliff:g id="SENDER">%1$s</xliff:g>\" af te stoor"</string>
+    <string name="bt_sm_2_1_nosdcard" msgid="5354343190503952837">"Daar is te min spasie in die USB-berging om die lêer te stoor."</string>
+    <string name="bt_sm_2_1_default" msgid="2497541206648973852">"Daar is te min spasie op die SD-kaart om die lêer te stoor."</string>
     <string name="bt_sm_2_2" msgid="2965243265852680543">"Spasie nodig: <xliff:g id="SIZE">%1$s</xliff:g>"</string>
     <string name="ErrorTooManyRequests" msgid="8578277541472944529">"Te veel versoeke word verwerk. Probeer later weer."</string>
     <string name="status_pending" msgid="2503691772030877944">"Lêeroordrag nog nie begin nie."</string>
diff --git a/res/values-am/strings.xml b/res/values-am/strings.xml
index b704ac0..79c348e 100644
--- a/res/values-am/strings.xml
+++ b/res/values-am/strings.xml
@@ -18,8 +18,8 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="permlab_bluetoothShareManager" msgid="311492132450338925">"አውርድ አደራጅን ድረስ።"</string>
     <string name="permdesc_bluetoothShareManager" msgid="8930572979123190223">"የብሉቱዝ አጋራ አስተዳዳሪውን ለመድረስ እና ፋይሎች እንዲያስተላልፉ ለመጠቀም ለመተግበሪያው ይፈቅዳል።"</string>
-    <string name="permlab_bluetoothWhitelist" msgid="7091552898592306386">"የብሉቱዝ መሳሪያ መዳረሻን በተወዳጆች ዝርዝር ያስገቡ።"</string>
-    <string name="permdesc_bluetoothWhitelist" msgid="5494513855192170109">"መተግበሪያው አንድን የብሉቱዝ መሳሪያ በጊዜያዊነት በተወዳጆች ዝርዝር እንዲያስገባ ይፈቅድለታል፣ ይህም መሳሪያው ወደዚህኛው መሳሪያ ፋይሎችን ያለተጠቃሚው ማረጋገጫ ለመላክ ያስችለዋል።"</string>
+    <string name="permlab_bluetoothAcceptlist" msgid="2647575807648622053">"የብሉቱዝ መሣሪያ መዳረሻን በመቀበያ ዝርዝር ያስገቡ።"</string>
+    <string name="permdesc_bluetoothAcceptlist" msgid="2406423665674766442">"መተግበሪያው አንድ የብሉቱዝ መሣሪያ በጊዜያዊነት በመቀበያ ዝርዝር ውስጥ እንዲያስገባ ይፈቅድለታል፣ ይህም መሣሪያው ያለተጠቃሚው ማረጋገጫ ፋይሎችን ወደዚህኛው መሣሪያ እንዲልክ ያስችለዋል።"</string>
     <string name="bt_share_picker_label" msgid="6268100924487046932">"ብሉቱዝ"</string>
     <string name="unknown_device" msgid="9221903979877041009">"ያልታወቀ መሣሪያ"</string>
     <string name="unknownNumber" msgid="4994750948072751566">"ያልታወቀ"</string>
@@ -37,7 +37,7 @@
     <string name="incoming_file_confirm_timeout_ok" msgid="1414676773249857278">"እሺ"</string>
     <string name="incoming_file_confirm_timeout_content" msgid="172779756093975981">"ከ \"<xliff:g id="SENDER">%1$s</xliff:g>\" ገቢ መልዕክት ፋይል እየተቀበለ ሳለ ጊዜ አልቆ ነበር።"</string>
     <string name="incoming_file_confirm_Notification_title" msgid="5573329005298936903">"ገቢ ፋይል"</string>
-    <string name="incoming_file_confirm_Notification_content" msgid="3359694069319644738">"<xliff:g id="SENDER">%1$s</xliff:g> <xliff:g id="FILE">%2$s</xliff:g>ን ለመላክ ዝግጁ ነው"</string>
+    <string name="incoming_file_confirm_Notification_content" msgid="6237487486781004992">"<xliff:g id="SENDER">%1$s</xliff:g> ፋይል ለመላክ ዝግጁ ነው፦ <xliff:g id="FILE">%2$s</xliff:g>"</string>
     <string name="notification_receiving" msgid="4674648179652543984">"ብሉቱዝ ማጋሪያ፡ <xliff:g id="FILE">%1$s</xliff:g> እየተቀበለ"</string>
     <string name="notification_received" msgid="3324588019186687985">"ብሉቱዝ ማጋሪያ፡ <xliff:g id="FILE">%1$s</xliff:g> ደርሷል"</string>
     <string name="notification_received_fail" msgid="3619350997285714746">"ብሉቱዝ ማጋሪያ፡ ፋይል<xliff:g id="FILE">%1$s</xliff:g> አልደረሰም"</string>
@@ -83,8 +83,8 @@
     <string name="bt_toast_4" msgid="4678812947604395649">"ፋይል ወደ \"<xliff:g id="RECIPIENT">%1$s</xliff:g>\" በመላክ ላይ"</string>
     <string name="bt_toast_5" msgid="2846870992823019494">"<xliff:g id="NUMBER">%1$s</xliff:g> ፋይሎችን ወደ \"<xliff:g id="RECIPIENT">%2$s</xliff:g>\" በመላክ ላይ"</string>
     <string name="bt_toast_6" msgid="1855266596936622458">"ለ \"<xliff:g id="RECIPIENT">%1$s</xliff:g>\" ፋይል መላክ አቁሟል"</string>
-    <string name="bt_sm_2_1_nosdcard" msgid="1791835163301501637">"በዩኤስቢ ማከማቻ ላይ ከ«<xliff:g id="SENDER">%1$s</xliff:g>» የመጣውን ፋይል ለማስቀመጥ በቂ ቦታ የለም"</string>
-    <string name="bt_sm_2_1_default" msgid="9115512207909504071">"ከ«<xliff:g id="SENDER">%1$s</xliff:g>» የመጣውን ፋይል በኤስዲ ካርዱ ላይ ለማስቀመጥ በቂ ቦታ የለም"</string>
+    <string name="bt_sm_2_1_nosdcard" msgid="5354343190503952837">"በUSB ማከማቻ ላይ ፋይል ለማስቀመጥ በቂ ቦታ የለም።"</string>
+    <string name="bt_sm_2_1_default" msgid="2497541206648973852">"በኤስዲ ካርዱ ላይ ፋይሉን ለማስቀመጥ በቂ ቦታ የለም።"</string>
     <string name="bt_sm_2_2" msgid="2965243265852680543">"የሚያስፈልግ ቦታ፡ <xliff:g id="SIZE">%1$s</xliff:g>"</string>
     <string name="ErrorTooManyRequests" msgid="8578277541472944529">"እጅግ ብዙ ጥየቃዎች ተካሂደዋል። ትንሽ ቆይተው እንደገና ይሞክሩ።"</string>
     <string name="status_pending" msgid="2503691772030877944">"የፋይል ዝውውር ገና አልተጀመረም::"</string>
diff --git a/res/values-ar/strings.xml b/res/values-ar/strings.xml
index 72492dd..52eaf76 100644
--- a/res/values-ar/strings.xml
+++ b/res/values-ar/strings.xml
@@ -18,8 +18,8 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="permlab_bluetoothShareManager" msgid="311492132450338925">"الدخول إلى إدارة التنزيل."</string>
     <string name="permdesc_bluetoothShareManager" msgid="8930572979123190223">"‏للسماح للتطبيق بالدخول إلى إدارة BluetoothShare واستخدامها لنقل الملفات."</string>
-    <string name="permlab_bluetoothWhitelist" msgid="7091552898592306386">"إضافة وصول جهاز بلوتوث إلى القائمة البيضاء."</string>
-    <string name="permdesc_bluetoothWhitelist" msgid="5494513855192170109">"يسمح للتطبيق مؤقتًا بإضافة جهاز بلوتوث إلى القائمة البيضاء مما يتيح لذلك الجهاز إرسال ملفات إلى هذا الجهاز بدون تأكيد المستخدم."</string>
+    <string name="permlab_bluetoothAcceptlist" msgid="2647575807648622053">"إضافة جهاز يتضمّن بلوتوث إلى القائمة المسموح بها."</string>
+    <string name="permdesc_bluetoothAcceptlist" msgid="2406423665674766442">"للسماح للتطبيق مؤقتًا بإضافة جهاز يتضمّن بلوتوث إلى القائمة المسموح بها، وهو ما يتيح لذلك الجهاز إرسال ملفات إلى هذا الجهاز بدون تأكيد المستخدم."</string>
     <string name="bt_share_picker_label" msgid="6268100924487046932">"بلوتوث"</string>
     <string name="unknown_device" msgid="9221903979877041009">"جهاز غير معروف"</string>
     <string name="unknownNumber" msgid="4994750948072751566">"غير معروف"</string>
@@ -37,7 +37,7 @@
     <string name="incoming_file_confirm_timeout_ok" msgid="1414676773249857278">"حسنًا"</string>
     <string name="incoming_file_confirm_timeout_content" msgid="172779756093975981">"انتهت المهلة أثناء قبول ملف وراد من \"<xliff:g id="SENDER">%1$s</xliff:g>\""</string>
     <string name="incoming_file_confirm_Notification_title" msgid="5573329005298936903">"ملف وارد"</string>
-    <string name="incoming_file_confirm_Notification_content" msgid="3359694069319644738">"<xliff:g id="SENDER">%1$s</xliff:g> جاهز لإرسال <xliff:g id="FILE">%2$s</xliff:g>"</string>
+    <string name="incoming_file_confirm_Notification_content" msgid="6237487486781004992">"<xliff:g id="SENDER">%1$s</xliff:g> على استعداد لإرسال ملف: <xliff:g id="FILE">%2$s</xliff:g>"</string>
     <string name="notification_receiving" msgid="4674648179652543984">"مشاركة البلوتوث: يتم استلام <xliff:g id="FILE">%1$s</xliff:g>"</string>
     <string name="notification_received" msgid="3324588019186687985">"مشاركة البلوتوث: تم استلام <xliff:g id="FILE">%1$s</xliff:g>"</string>
     <string name="notification_received_fail" msgid="3619350997285714746">"مشاركة البلوتوث: لم يتم استلام الملف <xliff:g id="FILE">%1$s</xliff:g>"</string>
@@ -83,8 +83,8 @@
     <string name="bt_toast_4" msgid="4678812947604395649">"إرسال الملف إلى \"<xliff:g id="RECIPIENT">%1$s</xliff:g>\""</string>
     <string name="bt_toast_5" msgid="2846870992823019494">"إرسال <xliff:g id="NUMBER">%1$s</xliff:g> من الملفات إلى \"<xliff:g id="RECIPIENT">%2$s</xliff:g>\""</string>
     <string name="bt_toast_6" msgid="1855266596936622458">"تم إيقاف إرسال الملف إلى \"<xliff:g id="RECIPIENT">%1$s</xliff:g>\""</string>
-    <string name="bt_sm_2_1_nosdcard" msgid="1791835163301501637">"‏لا تتوفّر مساحة كافية على وحدة تخزين USB لحفظ الملف من \"<xliff:g id="SENDER">%1$s</xliff:g>\"."</string>
-    <string name="bt_sm_2_1_default" msgid="9115512207909504071">"‏لا تتوفّر مساحة كافية على بطاقة SD لحفظ الملف من \"<xliff:g id="SENDER">%1$s</xliff:g>\"."</string>
+    <string name="bt_sm_2_1_nosdcard" msgid="5354343190503952837">"‏ليس هناك مساحة كافية على وحدة تخزين USB لحفظ الملف."</string>
+    <string name="bt_sm_2_1_default" msgid="2497541206648973852">"‏ليس هناك مساحة كافية على بطاقة SD لحفظ الملف."</string>
     <string name="bt_sm_2_2" msgid="2965243265852680543">"المساحة اللازمة: <xliff:g id="SIZE">%1$s</xliff:g>"</string>
     <string name="ErrorTooManyRequests" msgid="8578277541472944529">"تتم حاليًا معالجة طلبات كثيرة جدًا. حاول مرة أخرى لاحقًا."</string>
     <string name="status_pending" msgid="2503691772030877944">"لم يبدأ نقل الملف بعد."</string>
diff --git a/res/values-as/strings.xml b/res/values-as/strings.xml
index 292f0b6..04b6206 100644
--- a/res/values-as/strings.xml
+++ b/res/values-as/strings.xml
@@ -18,8 +18,8 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="permlab_bluetoothShareManager" msgid="311492132450338925">"ডাউনল’ড মেনেজাৰ ব্যৱহাৰ কৰিব পাৰে।"</string>
     <string name="permdesc_bluetoothShareManager" msgid="8930572979123190223">"এপটোক BluetoothShare মেনেজাৰ ব্যৱহাৰ কৰি ফাইল স্থানান্তৰ কৰিবলৈ অনুমতি দিয়ে।"</string>
-    <string name="permlab_bluetoothWhitelist" msgid="7091552898592306386">"ব্লুটুথ ডিভাইচ ব্যৱাহৰ কৰিবলৈ স্বীকৃতি দিয়ক।"</string>
-    <string name="permdesc_bluetoothWhitelist" msgid="5494513855192170109">"ব্যৱহাৰকাৰীৰ অনুমতি অবিহনে এই ডিভাইচক ফাইলবোৰ পঠিয়াবলৈ দি এপটোক ব্লুটুথ ডিভাইচক কিছু সময়ৰ বাবে স্বীকৃতি দিয়ে।"</string>
+    <string name="permlab_bluetoothAcceptlist" msgid="2647575807648622053">"ব্লুটুথ ডিভাইচ এক্সেছ কৰাৰ স্বীকৃতি দিয়ে।"</string>
+    <string name="permdesc_bluetoothAcceptlist" msgid="2406423665674766442">"এপ্‌টোক এটা ব্লুটুথ ডিভাইচ অস্থায়ীৰূপে স্বীকাৰ কৰাৰ অনুমতি দিয়ে যিয়ে ডিভাইচটোক ব্যৱহাৰকাৰীৰ নিশ্চিতিকৰণৰ অবিহনেই ইয়ালৈ ফাইল পঠিওৱাৰ অনুমতি দিয়ে।"</string>
     <string name="bt_share_picker_label" msgid="6268100924487046932">"ব্লুটুথ"</string>
     <string name="unknown_device" msgid="9221903979877041009">"অজ্ঞাত ডিভাইচ"</string>
     <string name="unknownNumber" msgid="4994750948072751566">"অজ্ঞাত"</string>
@@ -37,7 +37,7 @@
     <string name="incoming_file_confirm_timeout_ok" msgid="1414676773249857278">"ঠিক"</string>
     <string name="incoming_file_confirm_timeout_content" msgid="172779756093975981">"\"<xliff:g id="SENDER">%1$s</xliff:g>\"ৰ পৰা লাভ কৰা ফাইলটো গ্ৰহণ কৰোঁতে সময় ওকলিছে"</string>
     <string name="incoming_file_confirm_Notification_title" msgid="5573329005298936903">"অন্তৰ্গামী ফাইল"</string>
-    <string name="incoming_file_confirm_Notification_content" msgid="3359694069319644738">"<xliff:g id="SENDER">%1$s</xliff:g> <xliff:g id="FILE">%2$s</xliff:g> পঠাবলৈ সাজু"</string>
+    <string name="incoming_file_confirm_Notification_content" msgid="6237487486781004992">"<xliff:g id="SENDER">%1$s</xliff:g> এটা ফাইল পঠিয়াবলৈ সাজু: <xliff:g id="FILE">%2$s</xliff:g>"</string>
     <string name="notification_receiving" msgid="4674648179652543984">"ব্লুটুথ শ্বেয়াৰ: <xliff:g id="FILE">%1$s</xliff:g> লাভ কৰি থকা হৈছে"</string>
     <string name="notification_received" msgid="3324588019186687985">"ব্লুটুথ শ্বেয়াৰ: <xliff:g id="FILE">%1$s</xliff:g> লাভ কৰা হ’ল"</string>
     <string name="notification_received_fail" msgid="3619350997285714746">"ব্লুটুথ শ্বেয়াৰ: ফাইল <xliff:g id="FILE">%1$s</xliff:g> লাভ কৰা নহ\'ল"</string>
@@ -83,8 +83,8 @@
     <string name="bt_toast_4" msgid="4678812947604395649">"\"<xliff:g id="RECIPIENT">%1$s</xliff:g>\"লৈ ফাইল প্ৰেৰণ কৰি থকা হৈছে"</string>
     <string name="bt_toast_5" msgid="2846870992823019494">"\"<xliff:g id="RECIPIENT">%2$s</xliff:g>\"লৈ <xliff:g id="NUMBER">%1$s</xliff:g>টা ফাইল পঠিয়াই থকা হৈছে"</string>
     <string name="bt_toast_6" msgid="1855266596936622458">"\"<xliff:g id="RECIPIENT">%1$s</xliff:g>\"লৈ ফাইল পঠিওৱা বন্ধ কৰা হ’ল"</string>
-    <string name="bt_sm_2_1_nosdcard" msgid="1791835163301501637">"\"<xliff:g id="SENDER">%1$s</xliff:g>\"ৰ পৰা লাভ কৰা ফাইলটো ছেভ কৰিবলৈ USB সঞ্চয়াগাৰত পৰ্যাপ্ত খালী ঠাই নাই"</string>
-    <string name="bt_sm_2_1_default" msgid="9115512207909504071">"\"<xliff:g id="SENDER">%1$s</xliff:g>\"ৰ পৰা লাভ কৰা ফাইলটো ছেভ কৰিবলৈ SD কাৰ্ডত পৰ্যাপ্ত খালী ঠাই নাই"</string>
+    <string name="bt_sm_2_1_nosdcard" msgid="5354343190503952837">"ফাইলটো ছেভ কৰিব পৰাকৈ ইউএছবি ষ্ট’ৰেজত পৰ্যাপ্ত খালী ঠাই নাই।"</string>
+    <string name="bt_sm_2_1_default" msgid="2497541206648973852">"ফাইলটো ছেভ কৰিব পৰাকৈ এছডি কাৰ্ডখনত পৰ্যাপ্ত খালী ঠাই নাই।"</string>
     <string name="bt_sm_2_2" msgid="2965243265852680543">"ইমান খালী ঠাইৰ দৰকাৰ: <xliff:g id="SIZE">%1$s</xliff:g>"</string>
     <string name="ErrorTooManyRequests" msgid="8578277541472944529">"বহুত বেছি অনুৰোধৰ ওপৰত প্ৰক্ৰিয়া চলি আছে৷ পিছত আকৌ চেষ্টা কৰক৷"</string>
     <string name="status_pending" msgid="2503691772030877944">"ফাইলৰ স্থানান্তৰণ এতিয়ালৈকে আৰম্ভ হোৱা নাই।"</string>
@@ -93,8 +93,8 @@
     <string name="status_not_accept" msgid="1695082417193780738">"সমল সমৰ্থিত নহয়।"</string>
     <string name="status_forbidden" msgid="613956401054050725">"নিৰ্দিষ্ট কৰা ডিভাইচটোৱে স্থানান্তৰণ নিষিদ্ধ কৰিছে।"</string>
     <string name="status_canceled" msgid="6664490318773098285">"ব্যৱহাৰকাৰীয়ে স্থানান্তৰণ বাতিল কৰিছে।"</string>
-    <string name="status_file_error" msgid="3671917770630165299">"সঞ্চয়াগাৰৰ সমস্যা।"</string>
-    <string name="status_no_sd_card_nosdcard" msgid="573631036356922221">"কোনো USB সঞ্চয়াগাৰ নাই।"</string>
+    <string name="status_file_error" msgid="3671917770630165299">"ষ্ট’ৰেজৰ সমস্যা।"</string>
+    <string name="status_no_sd_card_nosdcard" msgid="573631036356922221">"কোনো ইউএছবি ষ্ট’ৰেজ নাই।"</string>
     <string name="status_no_sd_card_default" msgid="396564893716701954">"কোনো SD কাৰ্ড নাই। স্থানান্তৰ কৰা ফাইলসমূহ ছেভ কৰিবলৈ SD কাৰ্ড ভৰাওক।"</string>
     <string name="status_connection_error" msgid="947681831523219891">"সংযোগ কৰিব পৰা নগ\'ল।"</string>
     <string name="status_protocol_error" msgid="3245444473429269539">"অনুৰোধ সঠিকভাৱে পৰিচালনা কৰিব নোৱাৰি।"</string>
@@ -106,7 +106,7 @@
     <string name="inbound_history_title" msgid="6940914942271327563">"অন্তৰ্গামী স্থানান্তৰণসমূহ"</string>
     <string name="outbound_history_title" msgid="4279418703178140526">"বহিৰ্গামী স্থানান্তৰণসমূহ"</string>
     <string name="no_transfers" msgid="3482965619151865672">"স্থানান্তৰণৰ ইতিহাস খালী আছে।"</string>
-    <string name="transfer_clear_dlg_msg" msgid="1712376797268438075">"সকলো সমল সূচীৰ পৰা মচা হ\'ব।"</string>
+    <string name="transfer_clear_dlg_msg" msgid="1712376797268438075">"আটাইবোৰ সমল সূচীৰ পৰা মচা হ\'ব।"</string>
     <string name="outbound_noti_title" msgid="8051906709452260849">"ব্লুটুথ শ্বেয়াৰ: প্ৰেৰণ কৰা ফাইলসমূহ"</string>
     <string name="inbound_noti_title" msgid="4143352641953027595">"ব্লুটুথ শ্বেয়াৰ: লাভ কৰা ফাইলসমূহ"</string>
     <plurals name="noti_caption_unsuccessful" formatted="false" msgid="2020750076679526122">
diff --git a/res/values-as/test_strings.xml b/res/values-as/test_strings.xml
index a2efd87..861f5a8 100644
--- a/res/values-as/test_strings.xml
+++ b/res/values-as/test_strings.xml
@@ -5,7 +5,7 @@
     <string name="insert_record" msgid="1450997173838378132">"ৰেকৰ্ড ভৰাওক"</string>
     <string name="update_record" msgid="2480425402384910635">"ৰেকৰ্ড নিশ্চিত কৰক"</string>
     <string name="ack_record" msgid="6716152390978472184">"স্বীকৃত কৰা ৰেকৰ্ড"</string>
-    <string name="deleteAll_record" msgid="4383349788485210582">"সকলো ৰেকৰ্ড মচক"</string>
+    <string name="deleteAll_record" msgid="4383349788485210582">"আটাইবোৰ ৰেকৰ্ড মচক"</string>
     <string name="ok_button" msgid="6519033415223065454">"ঠিক"</string>
     <string name="delete_record" msgid="4645040331967533724">"ৰেকৰ্ড মচক"</string>
     <string name="start_server" msgid="9034821924409165795">"TCP ছাৰ্ভাৰ আৰম্ভ কৰক"</string>
diff --git a/res/values-az/strings.xml b/res/values-az/strings.xml
index ebe3d98..645e699 100644
--- a/res/values-az/strings.xml
+++ b/res/values-az/strings.xml
@@ -18,8 +18,8 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="permlab_bluetoothShareManager" msgid="311492132450338925">"Endirmə menecerinə daxil olun."</string>
     <string name="permdesc_bluetoothShareManager" msgid="8930572979123190223">"Tətbiq BluetoothPaylaşım menecerinə daxil ola və faylları ötürmək üçün ondan istifadə edə bilər."</string>
-    <string name="permlab_bluetoothWhitelist" msgid="7091552898592306386">"Bluetooth cihazına girişi ağ siyahıya salın."</string>
-    <string name="permdesc_bluetoothWhitelist" msgid="5494513855192170109">"Tətbiq Bluetooth cihazını müvəqqəti olaraq ağ siyahıya daxil edə bilər, bununla həmin cihaz istifadəçi təsdiqi olmadan bu cihaza fayllar göndərə biləcək."</string>
+    <string name="permlab_bluetoothAcceptlist" msgid="2647575807648622053">"Bluetooth cihazı girişini qəbul siyahısına daxil edin."</string>
+    <string name="permdesc_bluetoothAcceptlist" msgid="2406423665674766442">"Tətbiqin Bluetooth cihazını müvəqqəti olaraq qəbul siyahısına daxil etməsinə imkan verir, bununla həmin cihaz istifadəçi təsdiqi olmadan bu cihaza fayllar göndərə biləcək."</string>
     <string name="bt_share_picker_label" msgid="6268100924487046932">"Bluetooth"</string>
     <string name="unknown_device" msgid="9221903979877041009">"Naməlum cihaz"</string>
     <string name="unknownNumber" msgid="4994750948072751566">"Naməlum"</string>
@@ -37,7 +37,7 @@
     <string name="incoming_file_confirm_timeout_ok" msgid="1414676773249857278">"OK"</string>
     <string name="incoming_file_confirm_timeout_content" msgid="172779756093975981">"\"<xliff:g id="SENDER">%1$s</xliff:g>\" adlı istifadəçidən gələn faylı qəbul edərkən gecikmə baş verdi"</string>
     <string name="incoming_file_confirm_Notification_title" msgid="5573329005298936903">"Gələn fayl"</string>
-    <string name="incoming_file_confirm_Notification_content" msgid="3359694069319644738">"<xliff:g id="SENDER">%1$s</xliff:g> <xliff:g id="FILE">%2$s</xliff:g> faylını göndərməyə hazırdır"</string>
+    <string name="incoming_file_confirm_Notification_content" msgid="6237487486781004992">"<xliff:g id="SENDER">%1$s</xliff:g> bu faylı göndərməyə hazırdır: <xliff:g id="FILE">%2$s</xliff:g>"</string>
     <string name="notification_receiving" msgid="4674648179652543984">"Bluetooth paylaşım: <xliff:g id="FILE">%1$s</xliff:g> qəbul edilir"</string>
     <string name="notification_received" msgid="3324588019186687985">"Bluetooth paylaşım: <xliff:g id="FILE">%1$s</xliff:g> qəbul edildi"</string>
     <string name="notification_received_fail" msgid="3619350997285714746">"Bluetooth paylaşım: <xliff:g id="FILE">%1$s</xliff:g> faylı qəbul edilmədi"</string>
@@ -83,8 +83,8 @@
     <string name="bt_toast_4" msgid="4678812947604395649">"\"<xliff:g id="RECIPIENT">%1$s</xliff:g>\" adlı istifadəçiyə fayl göndərilir"</string>
     <string name="bt_toast_5" msgid="2846870992823019494">"\"<xliff:g id="RECIPIENT">%2$s</xliff:g>\" adlı istifadəçiyə <xliff:g id="NUMBER">%1$s</xliff:g> fayl göndərilir"</string>
     <string name="bt_toast_6" msgid="1855266596936622458">"\"<xliff:g id="RECIPIENT">%1$s</xliff:g>\" adlı istifadəçiyə faylın göndərilməsi dayandırıldı"</string>
-    <string name="bt_sm_2_1_nosdcard" msgid="1791835163301501637">"\"<xliff:g id="SENDER">%1$s</xliff:g>\" tərəfindən göndərilən faylı yadda saxlamaq üçün USB yaddaşında kifayət qədər yer yoxdur"</string>
-    <string name="bt_sm_2_1_default" msgid="9115512207909504071">"\"<xliff:g id="SENDER">%1$s</xliff:g>\" tərəfindən göndərilən faylı yadda saxlamaq üçün SD kartda kifayət qədər yer yoxdur"</string>
+    <string name="bt_sm_2_1_nosdcard" msgid="5354343190503952837">"Faylı saxlamaq üçün USB yaddaşında kifayət qədər yer yoxdur."</string>
+    <string name="bt_sm_2_1_default" msgid="2497541206648973852">"Faylı saxlamaq üçün SD kartda kifayət qədər yer yoxdur."</string>
     <string name="bt_sm_2_2" msgid="2965243265852680543">"Gərəkli: <xliff:g id="SIZE">%1$s</xliff:g>"</string>
     <string name="ErrorTooManyRequests" msgid="8578277541472944529">"Həddindən çox sorğu işlənilir. Sonra bir daha cəhd edin."</string>
     <string name="status_pending" msgid="2503691772030877944">"Fayl transferi hələ başlamayıb."</string>
diff --git a/res/values-b+sr+Latn/strings.xml b/res/values-b+sr+Latn/strings.xml
index 7f790d1..f82d280 100644
--- a/res/values-b+sr+Latn/strings.xml
+++ b/res/values-b+sr+Latn/strings.xml
@@ -18,8 +18,8 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="permlab_bluetoothShareManager" msgid="311492132450338925">"Pristup menadžeru preuzimanja."</string>
     <string name="permdesc_bluetoothShareManager" msgid="8930572979123190223">"Omogućava aplikaciji da pristupa menadžeru za deljenje preko Bluetooth-a i da ga koristi za prenos datoteka."</string>
-    <string name="permlab_bluetoothWhitelist" msgid="7091552898592306386">"Pristup Bluetooth uređaja sa bele liste."</string>
-    <string name="permdesc_bluetoothWhitelist" msgid="5494513855192170109">"Dozvoljava aplikaciji da privremeno stavi Bluetooth uređaj na belu listu, što omogućava tom uređaju da šalje datoteke ovom uređaju bez potvrde korisnika."</string>
+    <string name="permlab_bluetoothAcceptlist" msgid="2647575807648622053">"Stavi pristup Bluetooth uređaja na listu prihvaćenih uređaja."</string>
+    <string name="permdesc_bluetoothAcceptlist" msgid="2406423665674766442">"Dozvoljava aplikaciji da privremeno stavi Bluetooth uređaj na listu prihvaćenih uređaja, što omogućava tom uređaju da šalje datoteke ovom uređaju bez odobrenja korisnika."</string>
     <string name="bt_share_picker_label" msgid="6268100924487046932">"Bluetooth"</string>
     <string name="unknown_device" msgid="9221903979877041009">"Nepoznati uređaj"</string>
     <string name="unknownNumber" msgid="4994750948072751566">"Nepoznato"</string>
@@ -37,7 +37,7 @@
     <string name="incoming_file_confirm_timeout_ok" msgid="1414676773249857278">"Potvrdi"</string>
     <string name="incoming_file_confirm_timeout_content" msgid="172779756093975981">"Došlo je do vremenskog ograničenja tokom prijema dolazne datoteke od „<xliff:g id="SENDER">%1$s</xliff:g>“"</string>
     <string name="incoming_file_confirm_Notification_title" msgid="5573329005298936903">"Dolazna datoteka"</string>
-    <string name="incoming_file_confirm_Notification_content" msgid="3359694069319644738">"<xliff:g id="SENDER">%1$s</xliff:g> je spreman/na da pošalje <xliff:g id="FILE">%2$s</xliff:g>"</string>
+    <string name="incoming_file_confirm_Notification_content" msgid="6237487486781004992">"<xliff:g id="SENDER">%1$s</xliff:g> je spreman/na za slanje fajla: <xliff:g id="FILE">%2$s</xliff:g>"</string>
     <string name="notification_receiving" msgid="4674648179652543984">"Bluetooth deljenje: prijem <xliff:g id="FILE">%1$s</xliff:g>"</string>
     <string name="notification_received" msgid="3324588019186687985">"Bluetooth deljenje: primljeno <xliff:g id="FILE">%1$s</xliff:g>"</string>
     <string name="notification_received_fail" msgid="3619350997285714746">"Bluetooth deljenje: datoteka <xliff:g id="FILE">%1$s</xliff:g> nije primljena"</string>
@@ -83,8 +83,8 @@
     <string name="bt_toast_4" msgid="4678812947604395649">"Slanje datoteke primaocu „<xliff:g id="RECIPIENT">%1$s</xliff:g>“"</string>
     <string name="bt_toast_5" msgid="2846870992823019494">"Slanje<xliff:g id="NUMBER">%1$s</xliff:g> datoteka primaocu „<xliff:g id="RECIPIENT">%2$s</xliff:g>“"</string>
     <string name="bt_toast_6" msgid="1855266596936622458">"Zaustavljeno slanje primaocu „<xliff:g id="RECIPIENT">%1$s</xliff:g>“"</string>
-    <string name="bt_sm_2_1_nosdcard" msgid="1791835163301501637">"Nema dovoljno prostora u USB memoriji da bi se sačuvala datoteka pošiljaoca „<xliff:g id="SENDER">%1$s</xliff:g>“"</string>
-    <string name="bt_sm_2_1_default" msgid="9115512207909504071">"Nema dovoljno prostora na SD kartici da bi se sačuvala datoteka pošiljaoca „<xliff:g id="SENDER">%1$s</xliff:g>“"</string>
+    <string name="bt_sm_2_1_nosdcard" msgid="5354343190503952837">"Nema dovoljno prostora u USB memoriji za čuvanje datoteke."</string>
+    <string name="bt_sm_2_1_default" msgid="2497541206648973852">"Nema dovoljno prostora na SD kartici za čuvanje datoteke."</string>
     <string name="bt_sm_2_2" msgid="2965243265852680543">"Potreban prostor: <xliff:g id="SIZE">%1$s</xliff:g>"</string>
     <string name="ErrorTooManyRequests" msgid="8578277541472944529">"Previše zahteva se obrađuje. Probajte ponovo kasnije."</string>
     <string name="status_pending" msgid="2503691772030877944">"Prenos datoteke još nije počeo."</string>
diff --git a/res/values-be/strings.xml b/res/values-be/strings.xml
index 968d201..6f052f6 100644
--- a/res/values-be/strings.xml
+++ b/res/values-be/strings.xml
@@ -18,8 +18,8 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="permlab_bluetoothShareManager" msgid="311492132450338925">"Доступ да Менеджара спамповак."</string>
     <string name="permdesc_bluetoothShareManager" msgid="8930572979123190223">"Дазваляе прыкладанням атрымліваць доступ да менеджэра BluetoothShare і выкарыстоўваць яго для перадачы файлаў."</string>
-    <string name="permlab_bluetoothWhitelist" msgid="7091552898592306386">"Даданне прылад Bluetooth у белы спiс."</string>
-    <string name="permdesc_bluetoothWhitelist" msgid="5494513855192170109">"Дазваляе прыкладанням часова ўносiць у белы спіс прылады Bluetooth, дазваляючы iм адпраўляць файлы на гэту прыладу без пацверджання карыстальнiка."</string>
+    <string name="permlab_bluetoothAcceptlist" msgid="2647575807648622053">"Уносьце ў белы спіс прылады з Bluetooth."</string>
+    <string name="permdesc_bluetoothAcceptlist" msgid="2406423665674766442">"Дазваляе праграме часова ўносіць у белы спіс прылады з Bluetooth, дазваляючы ім адпраўляць файлы на гэтую прыладу без пацвярджэння карыстальніка."</string>
     <string name="bt_share_picker_label" msgid="6268100924487046932">"Bluetooth"</string>
     <string name="unknown_device" msgid="9221903979877041009">"Невядомая прылада"</string>
     <string name="unknownNumber" msgid="4994750948072751566">"Невядомы"</string>
@@ -37,7 +37,7 @@
     <string name="incoming_file_confirm_timeout_ok" msgid="1414676773249857278">"ОК"</string>
     <string name="incoming_file_confirm_timeout_content" msgid="172779756093975981">"Тайм-аўт прыняцця ўваходнага файла ад адпраўніка \"<xliff:g id="SENDER">%1$s</xliff:g>\""</string>
     <string name="incoming_file_confirm_Notification_title" msgid="5573329005298936903">"Уваходны файл"</string>
-    <string name="incoming_file_confirm_Notification_content" msgid="3359694069319644738">"<xliff:g id="SENDER">%1$s</xliff:g> гатовы(-ая) адправіць <xliff:g id="FILE">%2$s</xliff:g>"</string>
+    <string name="incoming_file_confirm_Notification_content" msgid="6237487486781004992">"<xliff:g id="SENDER">%1$s</xliff:g> збіраецца адправіць файл <xliff:g id="FILE">%2$s</xliff:g>"</string>
     <string name="notification_receiving" msgid="4674648179652543984">"Перадача праз Bluetooth: атрыманне файла <xliff:g id="FILE">%1$s</xliff:g>"</string>
     <string name="notification_received" msgid="3324588019186687985">"Перадача праз Bluetooth: атрыманы файл <xliff:g id="FILE">%1$s</xliff:g>"</string>
     <string name="notification_received_fail" msgid="3619350997285714746">"Перадача прз Bluetooth: файл <xliff:g id="FILE">%1$s</xliff:g> не атрыманы"</string>
@@ -83,8 +83,8 @@
     <string name="bt_toast_4" msgid="4678812947604395649">"Адпраўка файла атрымальніку \"<xliff:g id="RECIPIENT">%1$s</xliff:g>\""</string>
     <string name="bt_toast_5" msgid="2846870992823019494">"Адпраўка файлаў (<xliff:g id="NUMBER">%1$s</xliff:g>) атрымальніку \"<xliff:g id="RECIPIENT">%2$s</xliff:g>\""</string>
     <string name="bt_toast_6" msgid="1855266596936622458">"Адпраўка файла атрымальніку \"<xliff:g id="RECIPIENT">%1$s</xliff:g>\" спыненая"</string>
-    <string name="bt_sm_2_1_nosdcard" msgid="1791835163301501637">"У USB-сховішчы не хапае месца, каб захаваць файл ад адпраўшчыка \"<xliff:g id="SENDER">%1$s</xliff:g>\""</string>
-    <string name="bt_sm_2_1_default" msgid="9115512207909504071">"На SD-карце не хапае месца, каб захаваць файл ад адпраўшчыка \"<xliff:g id="SENDER">%1$s</xliff:g>\""</string>
+    <string name="bt_sm_2_1_nosdcard" msgid="5354343190503952837">"У USB-сховішчы недастаткова месца, каб захаваць файл."</string>
+    <string name="bt_sm_2_1_default" msgid="2497541206648973852">"На SD-карце недастаткова месца, каб захаваць файл."</string>
     <string name="bt_sm_2_2" msgid="2965243265852680543">"Спатрэбіцца месца: <xliff:g id="SIZE">%1$s</xliff:g>"</string>
     <string name="ErrorTooManyRequests" msgid="8578277541472944529">"Апрацоўваецца занадта шмат запытаў. Паспрабуйце пазней."</string>
     <string name="status_pending" msgid="2503691772030877944">"Перадача файла яшчэ не пачалася"</string>
diff --git a/res/values-bg/strings.xml b/res/values-bg/strings.xml
index a0edf47..7750e24 100644
--- a/res/values-bg/strings.xml
+++ b/res/values-bg/strings.xml
@@ -18,8 +18,8 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="permlab_bluetoothShareManager" msgid="311492132450338925">"Достъп до диспечера за изтегляне."</string>
     <string name="permdesc_bluetoothShareManager" msgid="8930572979123190223">"Дава на приложението достъп и възможност за прехвърляне на файлове с диспечера за BluetoothShare."</string>
-    <string name="permlab_bluetoothWhitelist" msgid="7091552898592306386">"Достъп до устройство с Bluetooth, поставено в белия списък."</string>
-    <string name="permdesc_bluetoothWhitelist" msgid="5494513855192170109">"Разрешава на приложението временно да постави в белия списък устройство с Bluetooth, позволявайки му да изпраща файлове до това устройство без потвърждение от потребителя."</string>
+    <string name="permlab_bluetoothAcceptlist" msgid="2647575807648622053">"Достъп до устройство с Bluetooth, поставено в списъка за приемане."</string>
+    <string name="permdesc_bluetoothAcceptlist" msgid="2406423665674766442">"Разрешава на приложението временно да постави в списъка за приемане устройство с Bluetooth, позволявайки му да изпраща файлове до това устройство без потвърждение от потребителя."</string>
     <string name="bt_share_picker_label" msgid="6268100924487046932">"Bluetooth"</string>
     <string name="unknown_device" msgid="9221903979877041009">"Неизвестно устройство"</string>
     <string name="unknownNumber" msgid="4994750948072751566">"Неизвестно"</string>
@@ -37,7 +37,7 @@
     <string name="incoming_file_confirm_timeout_ok" msgid="1414676773249857278">"OK"</string>
     <string name="incoming_file_confirm_timeout_content" msgid="172779756093975981">"Времето за изчакване изтече при приемането на входящ файл от „<xliff:g id="SENDER">%1$s</xliff:g>“"</string>
     <string name="incoming_file_confirm_Notification_title" msgid="5573329005298936903">"Входящ файл"</string>
-    <string name="incoming_file_confirm_Notification_content" msgid="3359694069319644738">"<xliff:g id="SENDER">%1$s</xliff:g> има готовност да изпрати „<xliff:g id="FILE">%2$s</xliff:g>“"</string>
+    <string name="incoming_file_confirm_Notification_content" msgid="6237487486781004992">"<xliff:g id="SENDER">%1$s</xliff:g> е в готовност за изпращане на файл: <xliff:g id="FILE">%2$s</xliff:g>"</string>
     <string name="notification_receiving" msgid="4674648179652543984">"Споделяне чрез Bluetooth: <xliff:g id="FILE">%1$s</xliff:g> се получава"</string>
     <string name="notification_received" msgid="3324588019186687985">"Споделяне чрез Bluetooth: <xliff:g id="FILE">%1$s</xliff:g> се получи"</string>
     <string name="notification_received_fail" msgid="3619350997285714746">"Споделяне чрез Bluetooth: Файлът <xliff:g id="FILE">%1$s</xliff:g> не е получен"</string>
@@ -83,8 +83,8 @@
     <string name="bt_toast_4" msgid="4678812947604395649">"Изпраща се файл до „<xliff:g id="RECIPIENT">%1$s</xliff:g>“"</string>
     <string name="bt_toast_5" msgid="2846870992823019494">"<xliff:g id="NUMBER">%1$s</xliff:g> файла се изпращат до „<xliff:g id="RECIPIENT">%2$s</xliff:g>“"</string>
     <string name="bt_toast_6" msgid="1855266596936622458">"Изпращането на файл до „<xliff:g id="RECIPIENT">%1$s</xliff:g>“ спря"</string>
-    <string name="bt_sm_2_1_nosdcard" msgid="1791835163301501637">"В USB хранилището няма достатъчно място, за да бъде запазен файлът от <xliff:g id="SENDER">%1$s</xliff:g>"</string>
-    <string name="bt_sm_2_1_default" msgid="9115512207909504071">"На SD картата няма достатъчно място, за да бъде запазен файлът от <xliff:g id="SENDER">%1$s</xliff:g>"</string>
+    <string name="bt_sm_2_1_nosdcard" msgid="5354343190503952837">"В USB хранилището няма достатъчно място за запазване на файла."</string>
+    <string name="bt_sm_2_1_default" msgid="2497541206648973852">"На SD картата няма достатъчно място за запазване на файла."</string>
     <string name="bt_sm_2_2" msgid="2965243265852680543">"Необходимо място: <xliff:g id="SIZE">%1$s</xliff:g>"</string>
     <string name="ErrorTooManyRequests" msgid="8578277541472944529">"Обработват се твърде много заявки. Опитайте отново по-късно."</string>
     <string name="status_pending" msgid="2503691772030877944">"Прехвърлянето на файла още не е започнало."</string>
diff --git a/res/values-bn/strings.xml b/res/values-bn/strings.xml
index f452781..3487c09 100644
--- a/res/values-bn/strings.xml
+++ b/res/values-bn/strings.xml
@@ -18,8 +18,8 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="permlab_bluetoothShareManager" msgid="311492132450338925">"ডাউনলোড ম্যানেজার অ্যাক্সেস করুন।"</string>
     <string name="permdesc_bluetoothShareManager" msgid="8930572979123190223">"অ্যাপ্লিকেশানটিকে BluetoothShare ম্যানেজার অ্যাক্সেস করতে ও ফাইলগুলি স্থানান্তর করতে এটি ব্যবহার করার অনুমতি দেয়।"</string>
-    <string name="permlab_bluetoothWhitelist" msgid="7091552898592306386">"শ্বেততালিকাভুক্ত ব্লুটুথ ডিভাইসের অ্যাক্সেস।"</string>
-    <string name="permdesc_bluetoothWhitelist" msgid="5494513855192170109">"অ্যাপ্লিকেশানটিকে অস্থায়ীভাবে কোনো ব্লুটুথ ডিভাইসকে শ্বেততালিকায় অন্তর্ভুক্ত করার অনুমতি দেয়, যা সেই ডিভাইসটিকে এই ডিভাইসে ব্যবহারকারীর নিশ্চিতকরণ ছাড়া ফাইল পাঠানোর অনুমতি দেয়।"</string>
+    <string name="permlab_bluetoothAcceptlist" msgid="2647575807648622053">"অ্যাক্সেপ্ট করা হয়েছে এমন তালিকাভুক্ত ব্লুটুথ ডিভাইসের অ্যাক্সেস।"</string>
+    <string name="permdesc_bluetoothAcceptlist" msgid="2406423665674766442">"অ্যাপটিকে অস্থায়ীভাবে কোনও ব্লুটুথ ডিভাইসকে অ্যাক্সেপ্ট করা হয়েছে এমন তালিকায় অন্তর্ভুক্ত করার অনুমতি দেয়, যার ফলে ব্যবহারকারীর নিশ্চিতকরণ ছাড়াই ওই ডিভাইস থেকে এই ডিভাইসে ফাইল পাঠানো যায়।"</string>
     <string name="bt_share_picker_label" msgid="6268100924487046932">"ব্লুটুথ"</string>
     <string name="unknown_device" msgid="9221903979877041009">"অজানা ডিভাইস"</string>
     <string name="unknownNumber" msgid="4994750948072751566">"অজানা"</string>
@@ -37,7 +37,7 @@
     <string name="incoming_file_confirm_timeout_ok" msgid="1414676773249857278">"ঠিক আছে"</string>
     <string name="incoming_file_confirm_timeout_content" msgid="172779756093975981">"\"<xliff:g id="SENDER">%1$s</xliff:g>\" এর থেকে ইনকামিং ফাইল গ্রহণ করার সময় অতিবাহিত হয়ে গেছে।"</string>
     <string name="incoming_file_confirm_Notification_title" msgid="5573329005298936903">"আগত ফাইল"</string>
-    <string name="incoming_file_confirm_Notification_content" msgid="3359694069319644738">"<xliff:g id="SENDER">%1$s</xliff:g> <xliff:g id="FILE">%2$s</xliff:g> পাঠানোর জন্য প্রস্তুত"</string>
+    <string name="incoming_file_confirm_Notification_content" msgid="6237487486781004992">"<xliff:g id="SENDER">%1$s</xliff:g> এবার ফাইল পাঠাতে পারবে: <xliff:g id="FILE">%2$s</xliff:g>"</string>
     <string name="notification_receiving" msgid="4674648179652543984">"ব্লুটুথ share: <xliff:g id="FILE">%1$s</xliff:g> প্রাপ্ত করা হচ্ছে"</string>
     <string name="notification_received" msgid="3324588019186687985">"ব্লুটুথ share: <xliff:g id="FILE">%1$s</xliff:g> প্রাপ্ত করা হয়েছে"</string>
     <string name="notification_received_fail" msgid="3619350997285714746">"ব্লুটুথ share: <xliff:g id="FILE">%1$s</xliff:g> ফাইল প্রাপ্ত করা হয়নি"</string>
@@ -83,8 +83,8 @@
     <string name="bt_toast_4" msgid="4678812947604395649">"\"<xliff:g id="RECIPIENT">%1$s</xliff:g>\" কে ফাইল পাঠানো হচ্ছে"</string>
     <string name="bt_toast_5" msgid="2846870992823019494">"\"<xliff:g id="RECIPIENT">%2$s</xliff:g>\" কে <xliff:g id="NUMBER">%1$s</xliff:g>টি ফাইল পাঠানো হচ্ছে"</string>
     <string name="bt_toast_6" msgid="1855266596936622458">"\"<xliff:g id="RECIPIENT">%1$s</xliff:g>\" কে ফাইল পাঠানো বন্ধ করা হয়েছে"</string>
-    <string name="bt_sm_2_1_nosdcard" msgid="1791835163301501637">"\"<xliff:g id="SENDER">%1$s</xliff:g>\"-এর পাঠানো ফাইল সেভ করার জন্য ইউএসবি স্টোরেজে পর্যাপ্ত জায়গা নেই"</string>
-    <string name="bt_sm_2_1_default" msgid="9115512207909504071">"\"<xliff:g id="SENDER">%1$s</xliff:g>\"-এর পাঠানো ফাইল সেভ করার জন্য এসডি কার্ডে পর্যাপ্ত জায়গা নেই"</string>
+    <string name="bt_sm_2_1_nosdcard" msgid="5354343190503952837">"ফাইল সেভ করার মতো USB স্টোরেজে পর্যাপ্ত স্পেস নেই।"</string>
+    <string name="bt_sm_2_1_default" msgid="2497541206648973852">"ফাইল সেভ করার মতো এসডি কার্ডে পর্যাপ্ত স্পেস নেই।"</string>
     <string name="bt_sm_2_2" msgid="2965243265852680543">"জায়গা প্রয়োজন: <xliff:g id="SIZE">%1$s</xliff:g>"</string>
     <string name="ErrorTooManyRequests" msgid="8578277541472944529">"অনেকগুলি অনুরোধ প্রক্রিয়া করা হচ্ছে৷ পরে আবার চেষ্টা করুন৷"</string>
     <string name="status_pending" msgid="2503691772030877944">"ফাইল ট্রান্সফার করা এখনও শুরু হয়নি।"</string>
diff --git a/res/values-bs/strings.xml b/res/values-bs/strings.xml
index 113ce50..ed97c52 100644
--- a/res/values-bs/strings.xml
+++ b/res/values-bs/strings.xml
@@ -18,15 +18,15 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="permlab_bluetoothShareManager" msgid="311492132450338925">"Pristupite upravitelju za preuzimanja."</string>
     <string name="permdesc_bluetoothShareManager" msgid="8930572979123190223">"Dozvoljava aplikaciji da pristupa BluetoothShare upravitelju i koristi ga za prenošenje fajlova."</string>
-    <string name="permlab_bluetoothWhitelist" msgid="7091552898592306386">"Dozvoli pristup bluetooth uređaju."</string>
-    <string name="permdesc_bluetoothWhitelist" msgid="5494513855192170109">"Dozvoljava aplikaciji da privremeno stavi Bluetooth uređaj na spisak dopuštenih fajlova, omogućavajući tom uređaju da šalje fajlove na ovaj uređaj bez potvrde korisnika."</string>
+    <string name="permlab_bluetoothAcceptlist" msgid="2647575807648622053">"Stavi pristup Bluetooth uređaja na listu prihvaćenih."</string>
+    <string name="permdesc_bluetoothAcceptlist" msgid="2406423665674766442">"Dozvoljava aplikaciji da privremeno stavi Bluetooth uređaj na listu prihvaćenih, čime mu se omogućava da šalje fajlove na ovaj uređaj bez potvrde korisnika."</string>
     <string name="bt_share_picker_label" msgid="6268100924487046932">"Bluetooth"</string>
     <string name="unknown_device" msgid="9221903979877041009">"Nepoznat uređaj"</string>
     <string name="unknownNumber" msgid="4994750948072751566">"Nepoznato"</string>
     <string name="airplane_error_title" msgid="2683839635115739939">"Način rada u avionu"</string>
     <string name="airplane_error_msg" msgid="8698965595254137230">"Ne možete koristiti Bluetooth u načinu rada u avionu."</string>
     <string name="bt_enable_title" msgid="8657832550503456572"></string>
-    <string name="bt_enable_line1" msgid="7203551583048149">"Da biste koristili Bluetooth usluge, prvo morate uključiti Bluetooth."</string>
+    <string name="bt_enable_line1" msgid="7203551583048149">"Da biste koristili usluge Bluetootha, prvo morate uključiti Bluetooth."</string>
     <string name="bt_enable_line2" msgid="4341936569415937994">"Želite uključiti Bluetooth sada?"</string>
     <string name="bt_enable_cancel" msgid="1988832367505151727">"Otkaži"</string>
     <string name="bt_enable_ok" msgid="3432462749994538265">"Uključi"</string>
@@ -37,7 +37,7 @@
     <string name="incoming_file_confirm_timeout_ok" msgid="1414676773249857278">"Uredu"</string>
     <string name="incoming_file_confirm_timeout_content" msgid="172779756093975981">"Isteklo je vrijeme prilikom prihvatanja dolaznog fajla koji šalje \"<xliff:g id="SENDER">%1$s</xliff:g>\""</string>
     <string name="incoming_file_confirm_Notification_title" msgid="5573329005298936903">"Dolazni fajl"</string>
-    <string name="incoming_file_confirm_Notification_content" msgid="3359694069319644738">"<xliff:g id="SENDER">%1$s</xliff:g> sada može poslati <xliff:g id="FILE">%2$s</xliff:g>"</string>
+    <string name="incoming_file_confirm_Notification_content" msgid="6237487486781004992">"<xliff:g id="SENDER">%1$s</xliff:g> je spreman/na za slanje fajla: <xliff:g id="FILE">%2$s</xliff:g>"</string>
     <string name="notification_receiving" msgid="4674648179652543984">"Bluetooth dijeljenje: Prima se fajl <xliff:g id="FILE">%1$s</xliff:g>"</string>
     <string name="notification_received" msgid="3324588019186687985">"Bluetooth dijeljenje: Primljen fajl <xliff:g id="FILE">%1$s</xliff:g>"</string>
     <string name="notification_received_fail" msgid="3619350997285714746">"Bluetooth dijeljenje: Fajl <xliff:g id="FILE">%1$s</xliff:g> nije primljen"</string>
@@ -83,8 +83,8 @@
     <string name="bt_toast_4" msgid="4678812947604395649">"Slanje fajla kojeg prima \"<xliff:g id="RECIPIENT">%1$s</xliff:g>\""</string>
     <string name="bt_toast_5" msgid="2846870992823019494">"Slanje <xliff:g id="NUMBER">%1$s</xliff:g> fajl(ov)a, prima \"<xliff:g id="RECIPIENT">%2$s</xliff:g>\""</string>
     <string name="bt_toast_6" msgid="1855266596936622458">"Zaustavljeno slanje fajla kojeg prima \"<xliff:g id="RECIPIENT">%1$s</xliff:g>\""</string>
-    <string name="bt_sm_2_1_nosdcard" msgid="1791835163301501637">"Nema dovoljno prostora na USB pohrani da se sačuva fajl koji šalje \"<xliff:g id="SENDER">%1$s</xliff:g>\""</string>
-    <string name="bt_sm_2_1_default" msgid="9115512207909504071">"Nema dovoljno prostora na SD kartici da se sačuva fajl koji šalje \"<xliff:g id="SENDER">%1$s</xliff:g>\""</string>
+    <string name="bt_sm_2_1_nosdcard" msgid="5354343190503952837">"Na USB pohrani nema dovoljno prostora da se sačuva ovaj fajl."</string>
+    <string name="bt_sm_2_1_default" msgid="2497541206648973852">"Na SD kartici nema dovoljno prostora da se sačuva ovaj fajl."</string>
     <string name="bt_sm_2_2" msgid="2965243265852680543">"Potrebni prostor: <xliff:g id="SIZE">%1$s</xliff:g>"</string>
     <string name="ErrorTooManyRequests" msgid="8578277541472944529">"Obrađuje se previše zahtjeva. Pokušajte ponovo kasnije."</string>
     <string name="status_pending" msgid="2503691772030877944">"Prenošenje fajla još nije započelo."</string>
diff --git a/res/values-ca/strings.xml b/res/values-ca/strings.xml
index b9c64de..3024b85 100644
--- a/res/values-ca/strings.xml
+++ b/res/values-ca/strings.xml
@@ -18,8 +18,8 @@
     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 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="permlab_bluetoothAcceptlist" msgid="2647575807648622053">"Afegeix l\'accés al dispositiu Bluetooth a la llista de permesos."</string>
+    <string name="permdesc_bluetoothAcceptlist" msgid="2406423665674766442">"Permet que l\'aplicació col·loqui temporalment en una llista de permesos 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>
     <string name="unknownNumber" msgid="4994750948072751566">"Desconegut"</string>
@@ -37,7 +37,7 @@
     <string name="incoming_file_confirm_timeout_ok" msgid="1414676773249857278">"D\'acord"</string>
     <string name="incoming_file_confirm_timeout_content" msgid="172779756093975981">"S\'ha esgotat el temps d\'espera mentre s\'acceptava un fitxer entrant de \"<xliff:g id="SENDER">%1$s</xliff:g>\""</string>
     <string name="incoming_file_confirm_Notification_title" msgid="5573329005298936903">"Fitxer entrant"</string>
-    <string name="incoming_file_confirm_Notification_content" msgid="3359694069319644738">"<xliff:g id="SENDER">%1$s</xliff:g> ja pot enviar <xliff:g id="FILE">%2$s</xliff:g>"</string>
+    <string name="incoming_file_confirm_Notification_content" msgid="6237487486781004992">"<xliff:g id="SENDER">%1$s</xliff:g> està a punt per enviar un fitxer: <xliff:g id="FILE">%2$s</xliff:g>"</string>
     <string name="notification_receiving" msgid="4674648179652543984">"Bluetooth: s\'està rebent <xliff:g id="FILE">%1$s</xliff:g>"</string>
     <string name="notification_received" msgid="3324588019186687985">"Bluetooth: <xliff:g id="FILE">%1$s</xliff:g> rebut"</string>
     <string name="notification_received_fail" msgid="3619350997285714746">"Bluetooth: <xliff:g id="FILE">%1$s</xliff:g> no rebut"</string>
@@ -83,8 +83,8 @@
     <string name="bt_toast_4" msgid="4678812947604395649">"S\'està enviant el fitxer a \"<xliff:g id="RECIPIENT">%1$s</xliff:g>\""</string>
     <string name="bt_toast_5" msgid="2846870992823019494">"S\'estan enviant <xliff:g id="NUMBER">%1$s</xliff:g> fitxers a \"<xliff:g id="RECIPIENT">%2$s</xliff:g>\""</string>
     <string name="bt_toast_6" msgid="1855266596936622458">"S\'ha aturat l\'enviament del fitxer a \"<xliff:g id="RECIPIENT">%1$s</xliff:g>\""</string>
-    <string name="bt_sm_2_1_nosdcard" msgid="1791835163301501637">"No hi ha prou espai a l\'emmagatzematge USB per desar el fitxer de: <xliff:g id="SENDER">%1$s</xliff:g>"</string>
-    <string name="bt_sm_2_1_default" msgid="9115512207909504071">"No hi ha prou espai a la targeta SD per desar el fitxer de: <xliff:g id="SENDER">%1$s</xliff:g>"</string>
+    <string name="bt_sm_2_1_nosdcard" msgid="5354343190503952837">"No hi ha prou espai a l\'emmagatzematge USB per desar el fitxer."</string>
+    <string name="bt_sm_2_1_default" msgid="2497541206648973852">"No hi ha prou espai a la targeta SD per desar el fitxer."</string>
     <string name="bt_sm_2_2" msgid="2965243265852680543">"Espai necessari: <xliff:g id="SIZE">%1$s</xliff:g>"</string>
     <string name="ErrorTooManyRequests" msgid="8578277541472944529">"S\'estan processant massa sol·licituds. Torneu-ho a provar més tard."</string>
     <string name="status_pending" msgid="2503691772030877944">"Encara no s\'ha iniciat la transferència del fitxer."</string>
diff --git a/res/values-cs/strings.xml b/res/values-cs/strings.xml
index df63233..99e3177 100644
--- a/res/values-cs/strings.xml
+++ b/res/values-cs/strings.xml
@@ -18,8 +18,8 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="permlab_bluetoothShareManager" msgid="311492132450338925">"Získat přístup ke správci stahování."</string>
     <string name="permdesc_bluetoothShareManager" msgid="8930572979123190223">"Umožňuje aplikaci přistupovat ke Správci sdílení Bluetooth a využívat jej k přenosu souborů."</string>
-    <string name="permlab_bluetoothWhitelist" msgid="7091552898592306386">"Povolit přístup zařízení Bluetooth."</string>
-    <string name="permdesc_bluetoothWhitelist" msgid="5494513855192170109">"Umožňuje aplikaci dočasně povolit zařízení Bluetooth, aby tak mohlo odesílat soubory do tohoto zařízení bez potvrzení uživatele."</string>
+    <string name="permlab_bluetoothAcceptlist" msgid="2647575807648622053">"Povolit přístup zařízení Bluetooth."</string>
+    <string name="permdesc_bluetoothAcceptlist" msgid="2406423665674766442">"Umožňuje aplikaci dočasně povolit zařízení Bluetooth, aby tak mohlo odesílat soubory do tohoto zařízení bez potvrzení uživatele."</string>
     <string name="bt_share_picker_label" msgid="6268100924487046932">"Bluetooth"</string>
     <string name="unknown_device" msgid="9221903979877041009">"Neznámé zařízení"</string>
     <string name="unknownNumber" msgid="4994750948072751566">"Neznámé"</string>
@@ -37,7 +37,7 @@
     <string name="incoming_file_confirm_timeout_ok" msgid="1414676773249857278">"OK"</string>
     <string name="incoming_file_confirm_timeout_content" msgid="172779756093975981">"Při příjmu příchozího souboru od uživatele <xliff:g id="SENDER">%1$s</xliff:g> vypršel časový limit."</string>
     <string name="incoming_file_confirm_Notification_title" msgid="5573329005298936903">"Příchozí soubor"</string>
-    <string name="incoming_file_confirm_Notification_content" msgid="3359694069319644738">"<xliff:g id="SENDER">%1$s</xliff:g> – odeslání souboru <xliff:g id="FILE">%2$s</xliff:g> je připraveno"</string>
+    <string name="incoming_file_confirm_Notification_content" msgid="6237487486781004992">"<xliff:g id="SENDER">%1$s</xliff:g> může odeslat soubor: <xliff:g id="FILE">%2$s</xliff:g>"</string>
     <string name="notification_receiving" msgid="4674648179652543984">"Sdílení Bluetooth: Příjem souboru <xliff:g id="FILE">%1$s</xliff:g>"</string>
     <string name="notification_received" msgid="3324588019186687985">"Sdílení Bluetooth: Soubor <xliff:g id="FILE">%1$s</xliff:g> byl přijat"</string>
     <string name="notification_received_fail" msgid="3619350997285714746">"Sdílení Bluetooth: Soubor <xliff:g id="FILE">%1$s</xliff:g> nebyl přijat"</string>
@@ -83,8 +83,8 @@
     <string name="bt_toast_4" msgid="4678812947604395649">"Odesílání souboru uživateli <xliff:g id="RECIPIENT">%1$s</xliff:g>"</string>
     <string name="bt_toast_5" msgid="2846870992823019494">"Odesílání <xliff:g id="NUMBER">%1$s</xliff:g> souborů uživateli <xliff:g id="RECIPIENT">%2$s</xliff:g>"</string>
     <string name="bt_toast_6" msgid="1855266596936622458">"Odesílání souboru uživateli <xliff:g id="RECIPIENT">%1$s</xliff:g> bylo zastaveno"</string>
-    <string name="bt_sm_2_1_nosdcard" msgid="1791835163301501637">"Pro uložení souboru od odesílatele <xliff:g id="SENDER">%1$s</xliff:g> není v úložišti USB dost místa."</string>
-    <string name="bt_sm_2_1_default" msgid="9115512207909504071">"Pro uložení souboru od odesílatele <xliff:g id="SENDER">%1$s</xliff:g> není na SD kartě dost místa."</string>
+    <string name="bt_sm_2_1_nosdcard" msgid="5354343190503952837">"Na úložišti USB není dost místa k uložení souboru."</string>
+    <string name="bt_sm_2_1_default" msgid="2497541206648973852">"Na SD kartě není dost místa k uložení souboru."</string>
     <string name="bt_sm_2_2" msgid="2965243265852680543">"Požadované místo v paměti: <xliff:g id="SIZE">%1$s</xliff:g>"</string>
     <string name="ErrorTooManyRequests" msgid="8578277541472944529">"Je zpracováváno příliš mnoho požadavků. Opakujte akci později."</string>
     <string name="status_pending" msgid="2503691772030877944">"Přenos souborů ještě nebyl zahájen."</string>
diff --git a/res/values-da/strings.xml b/res/values-da/strings.xml
index 05238cf..ae4f157 100644
--- a/res/values-da/strings.xml
+++ b/res/values-da/strings.xml
@@ -18,8 +18,8 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="permlab_bluetoothShareManager" msgid="311492132450338925">"Få adgang til downloadadministrator."</string>
     <string name="permdesc_bluetoothShareManager" msgid="8930572979123190223">"Tillader, at appen får adgang til BluetoothShare-administratoren og kan bruge den til overførsel af filer."</string>
-    <string name="permlab_bluetoothWhitelist" msgid="7091552898592306386">"Godkender adgang til Bluetooth-enheden."</string>
-    <string name="permdesc_bluetoothWhitelist" msgid="5494513855192170109">"Tillader, at appen midlertidigt godkender en Bluetooth-enhed, så der kan sendes filer derfra til din enhed uden bekræftelse fra brugerens side."</string>
+    <string name="permlab_bluetoothAcceptlist" msgid="2647575807648622053">"Godkend adgang for Bluetooth-enhed"</string>
+    <string name="permdesc_bluetoothAcceptlist" msgid="2406423665674766442">"Tillader, at appen midlertidigt godkender en Bluetooth-enhed, så der kan sendes filer fra den pågældende enhed til din enhed uden bekræftelse fra brugerens side."</string>
     <string name="bt_share_picker_label" msgid="6268100924487046932">"Bluetooth"</string>
     <string name="unknown_device" msgid="9221903979877041009">"Ukendt enhed"</string>
     <string name="unknownNumber" msgid="4994750948072751566">"Ukendt"</string>
@@ -37,7 +37,7 @@
     <string name="incoming_file_confirm_timeout_ok" msgid="1414676773249857278">"OK"</string>
     <string name="incoming_file_confirm_timeout_content" msgid="172779756093975981">"Der opstod timeout ved modtagelse af indgående fil fra \"<xliff:g id="SENDER">%1$s</xliff:g>\""</string>
     <string name="incoming_file_confirm_Notification_title" msgid="5573329005298936903">"Indgående fil"</string>
-    <string name="incoming_file_confirm_Notification_content" msgid="3359694069319644738">"<xliff:g id="SENDER">%1$s</xliff:g> er klar til at sende <xliff:g id="FILE">%2$s</xliff:g>"</string>
+    <string name="incoming_file_confirm_Notification_content" msgid="6237487486781004992">"<xliff:g id="SENDER">%1$s</xliff:g> er klar til at sende en fil: <xliff:g id="FILE">%2$s</xliff:g>"</string>
     <string name="notification_receiving" msgid="4674648179652543984">"Bluetooth-deling: Modtager <xliff:g id="FILE">%1$s</xliff:g>"</string>
     <string name="notification_received" msgid="3324588019186687985">"Bluetooth-deling: Modtog <xliff:g id="FILE">%1$s</xliff:g>"</string>
     <string name="notification_received_fail" msgid="3619350997285714746">"Bluetooth-deling: Filen <xliff:g id="FILE">%1$s</xliff:g> blev ikke modtaget"</string>
@@ -83,8 +83,8 @@
     <string name="bt_toast_4" msgid="4678812947604395649">"Sender filen til \"<xliff:g id="RECIPIENT">%1$s</xliff:g>\""</string>
     <string name="bt_toast_5" msgid="2846870992823019494">"Sender <xliff:g id="NUMBER">%1$s</xliff:g> filer til \"<xliff:g id="RECIPIENT">%2$s</xliff:g>\""</string>
     <string name="bt_toast_6" msgid="1855266596936622458">"Afsendelse af fil til \"<xliff:g id="RECIPIENT">%1$s</xliff:g>\" stoppede"</string>
-    <string name="bt_sm_2_1_nosdcard" msgid="1791835163301501637">"Der er ikke nok plads på USB-lageret til at gemme filen fra \"<xliff:g id="SENDER">%1$s</xliff:g>\""</string>
-    <string name="bt_sm_2_1_default" msgid="9115512207909504071">"Der er ikke nok plads på SD-kortet til at gemme filen fra \"<xliff:g id="SENDER">%1$s</xliff:g>\""</string>
+    <string name="bt_sm_2_1_nosdcard" msgid="5354343190503952837">"Der er ikke nok plads på USB-lageret til at gemme filen."</string>
+    <string name="bt_sm_2_1_default" msgid="2497541206648973852">"Der er ikke nok plads på SD-kortet til at gemme filen."</string>
     <string name="bt_sm_2_2" msgid="2965243265852680543">"Nødvendig plads: <xliff:g id="SIZE">%1$s</xliff:g>"</string>
     <string name="ErrorTooManyRequests" msgid="8578277541472944529">"Der behandles for mange anmodninger. Prøv igen senere."</string>
     <string name="status_pending" msgid="2503691772030877944">"Filoverførslen er endnu ikke påbegyndt."</string>
diff --git a/res/values-de/strings.xml b/res/values-de/strings.xml
index c9c436d..57160f0 100644
--- a/res/values-de/strings.xml
+++ b/res/values-de/strings.xml
@@ -18,8 +18,8 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="permlab_bluetoothShareManager" msgid="311492132450338925">"Auf Download-Manager zugreifen"</string>
     <string name="permdesc_bluetoothShareManager" msgid="8930572979123190223">"Ermöglicht der App, auf den Bluetooth-Weiterleitungs-Manager zuzugreifen und diesen für die Übertragung von Dateien zu verwenden."</string>
-    <string name="permlab_bluetoothWhitelist" msgid="7091552898592306386">"Bluetooth-Gerät für Zugriff zur Zulassungsliste hinzufügen"</string>
-    <string name="permdesc_bluetoothWhitelist" msgid="5494513855192170109">"Ermöglicht der App, ein Bluetooth-Gerät vorübergehend zur Zulassungsliste hinzuzufügen, sodass es ohne Bestätigung des Nutzers Dateien an dieses Gerät senden kann"</string>
+    <string name="permlab_bluetoothAcceptlist" msgid="2647575807648622053">"Bluetooth-Gerät für Zugriff zur Zulassungsliste hinzufügen"</string>
+    <string name="permdesc_bluetoothAcceptlist" msgid="2406423665674766442">"Ermöglicht der App, ein Bluetooth-Gerät vorübergehend zur Zulassungsliste hinzuzufügen, sodass es ohne Bestätigung des Nutzers Dateien an dieses Gerät senden kann."</string>
     <string name="bt_share_picker_label" msgid="6268100924487046932">"Bluetooth"</string>
     <string name="unknown_device" msgid="9221903979877041009">"Unbekanntes Gerät"</string>
     <string name="unknownNumber" msgid="4994750948072751566">"Unbekannter Anrufer"</string>
@@ -37,7 +37,7 @@
     <string name="incoming_file_confirm_timeout_ok" msgid="1414676773249857278">"Ok"</string>
     <string name="incoming_file_confirm_timeout_content" msgid="172779756093975981">"Die Zeit zum Empfang der eingehenden Datei von \"<xliff:g id="SENDER">%1$s</xliff:g>\" ist abgelaufen."</string>
     <string name="incoming_file_confirm_Notification_title" msgid="5573329005298936903">"Eingehende Datei"</string>
-    <string name="incoming_file_confirm_Notification_content" msgid="3359694069319644738">"<xliff:g id="SENDER">%1$s</xliff:g> kann jetzt <xliff:g id="FILE">%2$s</xliff:g> senden."</string>
+    <string name="incoming_file_confirm_Notification_content" msgid="6237487486781004992">"<xliff:g id="SENDER">%1$s</xliff:g> ist bereit, eine Datei zu senden: <xliff:g id="FILE">%2$s</xliff:g>"</string>
     <string name="notification_receiving" msgid="4674648179652543984">"Bluetooth-Freigabe: <xliff:g id="FILE">%1$s</xliff:g> wird empfangen"</string>
     <string name="notification_received" msgid="3324588019186687985">"Bluetooth-Freigabe: <xliff:g id="FILE">%1$s</xliff:g> wurde empfangen"</string>
     <string name="notification_received_fail" msgid="3619350997285714746">"Bluetooth-Freigabe: <xliff:g id="FILE">%1$s</xliff:g> wurde nicht empfangen"</string>
@@ -83,8 +83,8 @@
     <string name="bt_toast_4" msgid="4678812947604395649">"Datei wird an \"<xliff:g id="RECIPIENT">%1$s</xliff:g>\" gesendet..."</string>
     <string name="bt_toast_5" msgid="2846870992823019494">"<xliff:g id="NUMBER">%1$s</xliff:g> Dateien werden an \"<xliff:g id="RECIPIENT">%2$s</xliff:g>\" gesendet."</string>
     <string name="bt_toast_6" msgid="1855266596936622458">"Die Übertragung der Datei an \"<xliff:g id="RECIPIENT">%1$s</xliff:g>\" wurde abgebrochen"</string>
-    <string name="bt_sm_2_1_nosdcard" msgid="1791835163301501637">"Der USB-Speicher verfügt nicht über genügend Speicherplatz für die Datei von \"<xliff:g id="SENDER">%1$s</xliff:g>\""</string>
-    <string name="bt_sm_2_1_default" msgid="9115512207909504071">"Die SD-Karte verfügt über zu wenig Speicherplatz für die Datei von \"<xliff:g id="SENDER">%1$s</xliff:g>\""</string>
+    <string name="bt_sm_2_1_nosdcard" msgid="5354343190503952837">"Auf dem USB-Speicher ist nicht genügend Platz, um die Datei zu speichern."</string>
+    <string name="bt_sm_2_1_default" msgid="2497541206648973852">"Auf der SD-Karte ist nicht genügend Platz, um die Datei zu speichern."</string>
     <string name="bt_sm_2_2" msgid="2965243265852680543">"Erforderlicher Speicherplatz: <xliff:g id="SIZE">%1$s</xliff:g>"</string>
     <string name="ErrorTooManyRequests" msgid="8578277541472944529">"Es werden zurzeit zu viele Anfragen verarbeitet. Bitte versuche es später noch einmal."</string>
     <string name="status_pending" msgid="2503691772030877944">"Die Dateiübertragung wurde noch nicht gestartet."</string>
diff --git a/res/values-el/strings.xml b/res/values-el/strings.xml
index 052bbdf..0f92a3e 100644
--- a/res/values-el/strings.xml
+++ b/res/values-el/strings.xml
@@ -18,8 +18,8 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="permlab_bluetoothShareManager" msgid="311492132450338925">"Πρόσβαση στη διαχείριση λήψεων."</string>
     <string name="permdesc_bluetoothShareManager" msgid="8930572979123190223">"Επιτρέπει στην εφαρμογή να αποκτά πρόσβαση στο πρόγραμμα διαχείρισης BluetoothShare και να το χρησιμοποιεί για τη μεταφορά αρχείων."</string>
-    <string name="permlab_bluetoothWhitelist" msgid="7091552898592306386">"Πρόσβαση συσκευής Bluetooth επιτρεπόμενης λίστας."</string>
-    <string name="permdesc_bluetoothWhitelist" msgid="5494513855192170109">"Επιτρέπει στην εφαρμογή να προσθέσει μια συσκευή Bluetooth σε μια λίστα επιτρεπόμενων συσκευών, δίνοντας τη δυνατότητα στη συγκεκριμένη εφαρμογή να αποστέλλει αρχεία σε αυτήν τη συσκευή χωρίς επιβεβαίωση χρήστη."</string>
+    <string name="permlab_bluetoothAcceptlist" msgid="2647575807648622053">"Πρόσβαση συσκευής Bluetooth επιτρεπόμενης λίστας."</string>
+    <string name="permdesc_bluetoothAcceptlist" msgid="2406423665674766442">"Επιτρέπει στην εφαρμογή την προσωρινή προσθήκη συσκευής Bluetooth σε μια λίστα επιτρεπόμενων συσκευών, δίνοντας τη δυνατότητα στη συγκεκριμένη συσκευή να αποστέλλει αρχεία σε αυτήν τη συσκευή χωρίς επιβεβαίωση χρήστη."</string>
     <string name="bt_share_picker_label" msgid="6268100924487046932">"Bluetooth"</string>
     <string name="unknown_device" msgid="9221903979877041009">"Άγνωστη συσκευή"</string>
     <string name="unknownNumber" msgid="4994750948072751566">"Άγνωστος"</string>
@@ -37,7 +37,7 @@
     <string name="incoming_file_confirm_timeout_ok" msgid="1414676773249857278">"OK"</string>
     <string name="incoming_file_confirm_timeout_content" msgid="172779756093975981">"Σημειώθηκε διακοπή κατά την αποδοχή ενός εισερχόμενου αρχείου από τον αποστολέα \"<xliff:g id="SENDER">%1$s</xliff:g>\""</string>
     <string name="incoming_file_confirm_Notification_title" msgid="5573329005298936903">"Εισερχόμενο αρχείο"</string>
-    <string name="incoming_file_confirm_Notification_content" msgid="3359694069319644738">"Ο χρήστης <xliff:g id="SENDER">%1$s</xliff:g> πρόκειται να στείλει το αρχείο <xliff:g id="FILE">%2$s</xliff:g>"</string>
+    <string name="incoming_file_confirm_Notification_content" msgid="6237487486781004992">"Ο χρήστης <xliff:g id="SENDER">%1$s</xliff:g> είναι έτοιμος να στείλει ένα αρχείο: <xliff:g id="FILE">%2$s</xliff:g>"</string>
     <string name="notification_receiving" msgid="4674648179652543984">"Μοιραστείτε μέσω Bluetooth: Λήψη του <xliff:g id="FILE">%1$s</xliff:g>"</string>
     <string name="notification_received" msgid="3324588019186687985">"Μοιραστείτε μέσω Bluetooth: Ελήφθη το <xliff:g id="FILE">%1$s</xliff:g>"</string>
     <string name="notification_received_fail" msgid="3619350997285714746">"Μοιραστείτε μέσω Bluetooth: Το αρχείο <xliff:g id="FILE">%1$s</xliff:g> δεν ελήφθη"</string>
@@ -83,8 +83,8 @@
     <string name="bt_toast_4" msgid="4678812947604395649">"Γίνεται αποστολή του αρχείου στον παραλήπτη \"<xliff:g id="RECIPIENT">%1$s</xliff:g>\""</string>
     <string name="bt_toast_5" msgid="2846870992823019494">"Αποστολή <xliff:g id="NUMBER">%1$s</xliff:g> αρχείων στον παραλήπτη \"<xliff:g id="RECIPIENT">%2$s</xliff:g>\""</string>
     <string name="bt_toast_6" msgid="1855266596936622458">"Διακόπηκε η αποστολή του αρχείου στον παραλήπτη \"<xliff:g id="RECIPIENT">%1$s</xliff:g>\""</string>
-    <string name="bt_sm_2_1_nosdcard" msgid="1791835163301501637">"Δεν υπάρχει αρκετός χώρος στον χώρο αποθήκευσης USB για την αποθήκευση του αρχείου από τον αποστολέα \"<xliff:g id="SENDER">%1$s</xliff:g>\""</string>
-    <string name="bt_sm_2_1_default" msgid="9115512207909504071">"Δεν υπάρχει αρκετός χώρος στην κάρτα SD για την αποθήκευση του αρχείου από τον αποστολέα \"<xliff:g id="SENDER">%1$s</xliff:g>\""</string>
+    <string name="bt_sm_2_1_nosdcard" msgid="5354343190503952837">"Δεν υπάρχει αρκετός χώρος στον αποθηκευτικό χώρο USB για την αποθήκευση του αρχείου."</string>
+    <string name="bt_sm_2_1_default" msgid="2497541206648973852">"Δεν υπάρχει αρκετός χώρος στην κάρτα SD για την αποθήκευση του αρχείου."</string>
     <string name="bt_sm_2_2" msgid="2965243265852680543">"Απαιτούμενος χώρος: <xliff:g id="SIZE">%1$s</xliff:g>"</string>
     <string name="ErrorTooManyRequests" msgid="8578277541472944529">"Πραγματοποιείται επεξεργασία πάρα πολλών αιτημάτων. Προσπαθήστε ξανά αργότερα."</string>
     <string name="status_pending" msgid="2503691772030877944">"Η μεταφορά του αρχείου δεν έχει ξεκινήσει ακόμα."</string>
diff --git a/res/values-en-rAU/strings.xml b/res/values-en-rAU/strings.xml
index bf4d6b2..7b79e0d 100644
--- a/res/values-en-rAU/strings.xml
+++ b/res/values-en-rAU/strings.xml
@@ -18,8 +18,8 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="permlab_bluetoothShareManager" msgid="311492132450338925">"Access download manager."</string>
     <string name="permdesc_bluetoothShareManager" msgid="8930572979123190223">"Allows the application to access the Bluetooth Share manager and to use it to transfer files."</string>
-    <string name="permlab_bluetoothWhitelist" msgid="7091552898592306386">"Whitelist bluetooth device access."</string>
-    <string name="permdesc_bluetoothWhitelist" msgid="5494513855192170109">"Allows the app to temporarily whitelist a Bluetooth device, allowing that device to send files to this device without user confirmation."</string>
+    <string name="permlab_bluetoothAcceptlist" msgid="2647575807648622053">"Acceptlist Bluetooth device access."</string>
+    <string name="permdesc_bluetoothAcceptlist" msgid="2406423665674766442">"Allows the app to temporarily acceptlist a Bluetooth device, allowing that device to send files to this device without user confirmation."</string>
     <string name="bt_share_picker_label" msgid="6268100924487046932">"Bluetooth"</string>
     <string name="unknown_device" msgid="9221903979877041009">"Unknown device"</string>
     <string name="unknownNumber" msgid="4994750948072751566">"Unknown"</string>
@@ -37,7 +37,7 @@
     <string name="incoming_file_confirm_timeout_ok" msgid="1414676773249857278">"OK"</string>
     <string name="incoming_file_confirm_timeout_content" msgid="172779756093975981">"There was a timeout while accepting an incoming file from \"<xliff:g id="SENDER">%1$s</xliff:g>\""</string>
     <string name="incoming_file_confirm_Notification_title" msgid="5573329005298936903">"Incoming file"</string>
-    <string name="incoming_file_confirm_Notification_content" msgid="3359694069319644738">"<xliff:g id="SENDER">%1$s</xliff:g> is ready to send <xliff:g id="FILE">%2$s</xliff:g>"</string>
+    <string name="incoming_file_confirm_Notification_content" msgid="6237487486781004992">"<xliff:g id="SENDER">%1$s</xliff:g> is ready to send a file: <xliff:g id="FILE">%2$s</xliff:g>"</string>
     <string name="notification_receiving" msgid="4674648179652543984">"Bluetooth share: Receiving <xliff:g id="FILE">%1$s</xliff:g>"</string>
     <string name="notification_received" msgid="3324588019186687985">"Bluetooth share: Received <xliff:g id="FILE">%1$s</xliff:g>"</string>
     <string name="notification_received_fail" msgid="3619350997285714746">"Bluetooth share: File <xliff:g id="FILE">%1$s</xliff:g> not received"</string>
@@ -83,8 +83,8 @@
     <string name="bt_toast_4" msgid="4678812947604395649">"Sending file to \"<xliff:g id="RECIPIENT">%1$s</xliff:g>\""</string>
     <string name="bt_toast_5" msgid="2846870992823019494">"Sending <xliff:g id="NUMBER">%1$s</xliff:g> files to \"<xliff:g id="RECIPIENT">%2$s</xliff:g>\""</string>
     <string name="bt_toast_6" msgid="1855266596936622458">"Stopped sending file to \"<xliff:g id="RECIPIENT">%1$s</xliff:g>\""</string>
-    <string name="bt_sm_2_1_nosdcard" msgid="1791835163301501637">"There isn\'t enough space in USB storage to save the file from \"<xliff:g id="SENDER">%1$s</xliff:g>\""</string>
-    <string name="bt_sm_2_1_default" msgid="9115512207909504071">"There isn\'t enough space on the SD card to save the file from \"<xliff:g id="SENDER">%1$s</xliff:g>\""</string>
+    <string name="bt_sm_2_1_nosdcard" msgid="5354343190503952837">"There isn\'t enough space in USB storage to save the file."</string>
+    <string name="bt_sm_2_1_default" msgid="2497541206648973852">"There isn\'t enough space on the SD card to save the file."</string>
     <string name="bt_sm_2_2" msgid="2965243265852680543">"Space needed: <xliff:g id="SIZE">%1$s</xliff:g>"</string>
     <string name="ErrorTooManyRequests" msgid="8578277541472944529">"Too many requests are being processed. Try again later."</string>
     <string name="status_pending" msgid="2503691772030877944">"File transfer not started yet"</string>
diff --git a/res/values-en-rCA/strings.xml b/res/values-en-rCA/strings.xml
index 865b307..bdd7b13 100644
--- a/res/values-en-rCA/strings.xml
+++ b/res/values-en-rCA/strings.xml
@@ -18,8 +18,8 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="permlab_bluetoothShareManager" msgid="311492132450338925">"Access download manager."</string>
     <string name="permdesc_bluetoothShareManager" msgid="8930572979123190223">"Allows the application to access the Bluetooth Share manager and to use it to transfer files."</string>
-    <string name="permlab_bluetoothWhitelist" msgid="7091552898592306386">"Whitelist bluetooth device access."</string>
-    <string name="permdesc_bluetoothWhitelist" msgid="5494513855192170109">"Allows the app to temporarily whitelist a Bluetooth device, allowing that device to send files to this device without user confirmation."</string>
+    <string name="permlab_bluetoothAcceptlist" msgid="2647575807648622053">"Acceptlist Bluetooth device access."</string>
+    <string name="permdesc_bluetoothAcceptlist" msgid="2406423665674766442">"Allows the app to temporarily acceptlist a Bluetooth device, allowing that device to send files to this device without user confirmation."</string>
     <string name="bt_share_picker_label" msgid="6268100924487046932">"Bluetooth"</string>
     <string name="unknown_device" msgid="9221903979877041009">"Unknown device"</string>
     <string name="unknownNumber" msgid="4994750948072751566">"Unknown"</string>
@@ -37,7 +37,7 @@
     <string name="incoming_file_confirm_timeout_ok" msgid="1414676773249857278">"OK"</string>
     <string name="incoming_file_confirm_timeout_content" msgid="172779756093975981">"There was a timeout while accepting an incoming file from \"<xliff:g id="SENDER">%1$s</xliff:g>\""</string>
     <string name="incoming_file_confirm_Notification_title" msgid="5573329005298936903">"Incoming file"</string>
-    <string name="incoming_file_confirm_Notification_content" msgid="3359694069319644738">"<xliff:g id="SENDER">%1$s</xliff:g> is ready to send <xliff:g id="FILE">%2$s</xliff:g>"</string>
+    <string name="incoming_file_confirm_Notification_content" msgid="6237487486781004992">"<xliff:g id="SENDER">%1$s</xliff:g> is ready to send a file: <xliff:g id="FILE">%2$s</xliff:g>"</string>
     <string name="notification_receiving" msgid="4674648179652543984">"Bluetooth share: Receiving <xliff:g id="FILE">%1$s</xliff:g>"</string>
     <string name="notification_received" msgid="3324588019186687985">"Bluetooth share: Received <xliff:g id="FILE">%1$s</xliff:g>"</string>
     <string name="notification_received_fail" msgid="3619350997285714746">"Bluetooth share: File <xliff:g id="FILE">%1$s</xliff:g> not received"</string>
@@ -83,8 +83,8 @@
     <string name="bt_toast_4" msgid="4678812947604395649">"Sending file to \"<xliff:g id="RECIPIENT">%1$s</xliff:g>\""</string>
     <string name="bt_toast_5" msgid="2846870992823019494">"Sending <xliff:g id="NUMBER">%1$s</xliff:g> files to \"<xliff:g id="RECIPIENT">%2$s</xliff:g>\""</string>
     <string name="bt_toast_6" msgid="1855266596936622458">"Stopped sending file to \"<xliff:g id="RECIPIENT">%1$s</xliff:g>\""</string>
-    <string name="bt_sm_2_1_nosdcard" msgid="1791835163301501637">"There isn\'t enough space in USB storage to save the file from \"<xliff:g id="SENDER">%1$s</xliff:g>\""</string>
-    <string name="bt_sm_2_1_default" msgid="9115512207909504071">"There isn\'t enough space on the SD card to save the file from \"<xliff:g id="SENDER">%1$s</xliff:g>\""</string>
+    <string name="bt_sm_2_1_nosdcard" msgid="5354343190503952837">"There isn\'t enough space in USB storage to save the file."</string>
+    <string name="bt_sm_2_1_default" msgid="2497541206648973852">"There isn\'t enough space on the SD card to save the file."</string>
     <string name="bt_sm_2_2" msgid="2965243265852680543">"Space needed: <xliff:g id="SIZE">%1$s</xliff:g>"</string>
     <string name="ErrorTooManyRequests" msgid="8578277541472944529">"Too many requests are being processed. Try again later."</string>
     <string name="status_pending" msgid="2503691772030877944">"File transfer not started yet"</string>
diff --git a/res/values-en-rGB/strings.xml b/res/values-en-rGB/strings.xml
index bf4d6b2..7b79e0d 100644
--- a/res/values-en-rGB/strings.xml
+++ b/res/values-en-rGB/strings.xml
@@ -18,8 +18,8 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="permlab_bluetoothShareManager" msgid="311492132450338925">"Access download manager."</string>
     <string name="permdesc_bluetoothShareManager" msgid="8930572979123190223">"Allows the application to access the Bluetooth Share manager and to use it to transfer files."</string>
-    <string name="permlab_bluetoothWhitelist" msgid="7091552898592306386">"Whitelist bluetooth device access."</string>
-    <string name="permdesc_bluetoothWhitelist" msgid="5494513855192170109">"Allows the app to temporarily whitelist a Bluetooth device, allowing that device to send files to this device without user confirmation."</string>
+    <string name="permlab_bluetoothAcceptlist" msgid="2647575807648622053">"Acceptlist Bluetooth device access."</string>
+    <string name="permdesc_bluetoothAcceptlist" msgid="2406423665674766442">"Allows the app to temporarily acceptlist a Bluetooth device, allowing that device to send files to this device without user confirmation."</string>
     <string name="bt_share_picker_label" msgid="6268100924487046932">"Bluetooth"</string>
     <string name="unknown_device" msgid="9221903979877041009">"Unknown device"</string>
     <string name="unknownNumber" msgid="4994750948072751566">"Unknown"</string>
@@ -37,7 +37,7 @@
     <string name="incoming_file_confirm_timeout_ok" msgid="1414676773249857278">"OK"</string>
     <string name="incoming_file_confirm_timeout_content" msgid="172779756093975981">"There was a timeout while accepting an incoming file from \"<xliff:g id="SENDER">%1$s</xliff:g>\""</string>
     <string name="incoming_file_confirm_Notification_title" msgid="5573329005298936903">"Incoming file"</string>
-    <string name="incoming_file_confirm_Notification_content" msgid="3359694069319644738">"<xliff:g id="SENDER">%1$s</xliff:g> is ready to send <xliff:g id="FILE">%2$s</xliff:g>"</string>
+    <string name="incoming_file_confirm_Notification_content" msgid="6237487486781004992">"<xliff:g id="SENDER">%1$s</xliff:g> is ready to send a file: <xliff:g id="FILE">%2$s</xliff:g>"</string>
     <string name="notification_receiving" msgid="4674648179652543984">"Bluetooth share: Receiving <xliff:g id="FILE">%1$s</xliff:g>"</string>
     <string name="notification_received" msgid="3324588019186687985">"Bluetooth share: Received <xliff:g id="FILE">%1$s</xliff:g>"</string>
     <string name="notification_received_fail" msgid="3619350997285714746">"Bluetooth share: File <xliff:g id="FILE">%1$s</xliff:g> not received"</string>
@@ -83,8 +83,8 @@
     <string name="bt_toast_4" msgid="4678812947604395649">"Sending file to \"<xliff:g id="RECIPIENT">%1$s</xliff:g>\""</string>
     <string name="bt_toast_5" msgid="2846870992823019494">"Sending <xliff:g id="NUMBER">%1$s</xliff:g> files to \"<xliff:g id="RECIPIENT">%2$s</xliff:g>\""</string>
     <string name="bt_toast_6" msgid="1855266596936622458">"Stopped sending file to \"<xliff:g id="RECIPIENT">%1$s</xliff:g>\""</string>
-    <string name="bt_sm_2_1_nosdcard" msgid="1791835163301501637">"There isn\'t enough space in USB storage to save the file from \"<xliff:g id="SENDER">%1$s</xliff:g>\""</string>
-    <string name="bt_sm_2_1_default" msgid="9115512207909504071">"There isn\'t enough space on the SD card to save the file from \"<xliff:g id="SENDER">%1$s</xliff:g>\""</string>
+    <string name="bt_sm_2_1_nosdcard" msgid="5354343190503952837">"There isn\'t enough space in USB storage to save the file."</string>
+    <string name="bt_sm_2_1_default" msgid="2497541206648973852">"There isn\'t enough space on the SD card to save the file."</string>
     <string name="bt_sm_2_2" msgid="2965243265852680543">"Space needed: <xliff:g id="SIZE">%1$s</xliff:g>"</string>
     <string name="ErrorTooManyRequests" msgid="8578277541472944529">"Too many requests are being processed. Try again later."</string>
     <string name="status_pending" msgid="2503691772030877944">"File transfer not started yet"</string>
diff --git a/res/values-en-rIN/strings.xml b/res/values-en-rIN/strings.xml
index bf4d6b2..7b79e0d 100644
--- a/res/values-en-rIN/strings.xml
+++ b/res/values-en-rIN/strings.xml
@@ -18,8 +18,8 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="permlab_bluetoothShareManager" msgid="311492132450338925">"Access download manager."</string>
     <string name="permdesc_bluetoothShareManager" msgid="8930572979123190223">"Allows the application to access the Bluetooth Share manager and to use it to transfer files."</string>
-    <string name="permlab_bluetoothWhitelist" msgid="7091552898592306386">"Whitelist bluetooth device access."</string>
-    <string name="permdesc_bluetoothWhitelist" msgid="5494513855192170109">"Allows the app to temporarily whitelist a Bluetooth device, allowing that device to send files to this device without user confirmation."</string>
+    <string name="permlab_bluetoothAcceptlist" msgid="2647575807648622053">"Acceptlist Bluetooth device access."</string>
+    <string name="permdesc_bluetoothAcceptlist" msgid="2406423665674766442">"Allows the app to temporarily acceptlist a Bluetooth device, allowing that device to send files to this device without user confirmation."</string>
     <string name="bt_share_picker_label" msgid="6268100924487046932">"Bluetooth"</string>
     <string name="unknown_device" msgid="9221903979877041009">"Unknown device"</string>
     <string name="unknownNumber" msgid="4994750948072751566">"Unknown"</string>
@@ -37,7 +37,7 @@
     <string name="incoming_file_confirm_timeout_ok" msgid="1414676773249857278">"OK"</string>
     <string name="incoming_file_confirm_timeout_content" msgid="172779756093975981">"There was a timeout while accepting an incoming file from \"<xliff:g id="SENDER">%1$s</xliff:g>\""</string>
     <string name="incoming_file_confirm_Notification_title" msgid="5573329005298936903">"Incoming file"</string>
-    <string name="incoming_file_confirm_Notification_content" msgid="3359694069319644738">"<xliff:g id="SENDER">%1$s</xliff:g> is ready to send <xliff:g id="FILE">%2$s</xliff:g>"</string>
+    <string name="incoming_file_confirm_Notification_content" msgid="6237487486781004992">"<xliff:g id="SENDER">%1$s</xliff:g> is ready to send a file: <xliff:g id="FILE">%2$s</xliff:g>"</string>
     <string name="notification_receiving" msgid="4674648179652543984">"Bluetooth share: Receiving <xliff:g id="FILE">%1$s</xliff:g>"</string>
     <string name="notification_received" msgid="3324588019186687985">"Bluetooth share: Received <xliff:g id="FILE">%1$s</xliff:g>"</string>
     <string name="notification_received_fail" msgid="3619350997285714746">"Bluetooth share: File <xliff:g id="FILE">%1$s</xliff:g> not received"</string>
@@ -83,8 +83,8 @@
     <string name="bt_toast_4" msgid="4678812947604395649">"Sending file to \"<xliff:g id="RECIPIENT">%1$s</xliff:g>\""</string>
     <string name="bt_toast_5" msgid="2846870992823019494">"Sending <xliff:g id="NUMBER">%1$s</xliff:g> files to \"<xliff:g id="RECIPIENT">%2$s</xliff:g>\""</string>
     <string name="bt_toast_6" msgid="1855266596936622458">"Stopped sending file to \"<xliff:g id="RECIPIENT">%1$s</xliff:g>\""</string>
-    <string name="bt_sm_2_1_nosdcard" msgid="1791835163301501637">"There isn\'t enough space in USB storage to save the file from \"<xliff:g id="SENDER">%1$s</xliff:g>\""</string>
-    <string name="bt_sm_2_1_default" msgid="9115512207909504071">"There isn\'t enough space on the SD card to save the file from \"<xliff:g id="SENDER">%1$s</xliff:g>\""</string>
+    <string name="bt_sm_2_1_nosdcard" msgid="5354343190503952837">"There isn\'t enough space in USB storage to save the file."</string>
+    <string name="bt_sm_2_1_default" msgid="2497541206648973852">"There isn\'t enough space on the SD card to save the file."</string>
     <string name="bt_sm_2_2" msgid="2965243265852680543">"Space needed: <xliff:g id="SIZE">%1$s</xliff:g>"</string>
     <string name="ErrorTooManyRequests" msgid="8578277541472944529">"Too many requests are being processed. Try again later."</string>
     <string name="status_pending" msgid="2503691772030877944">"File transfer not started yet"</string>
diff --git a/res/values-en-rXC/strings.xml b/res/values-en-rXC/strings.xml
index a1bd3fc..5a2ab0d 100644
--- a/res/values-en-rXC/strings.xml
+++ b/res/values-en-rXC/strings.xml
@@ -18,8 +18,8 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="permlab_bluetoothShareManager" msgid="311492132450338925">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‎‏‏‏‎‎‎‏‎‏‎‎‏‎‏‎‏‎‎‏‎‎‎‏‏‏‎‎‎‏‏‎‏‏‎‎‎‏‎‎‎‎‏‎‎‎‎‎‎‎‏‎‎‎‎‏‏‎‏‏‎‏‎Access download manager.‎‏‎‎‏‎"</string>
     <string name="permdesc_bluetoothShareManager" msgid="8930572979123190223">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‏‏‏‏‏‎‏‏‏‏‏‎‏‏‏‏‏‏‎‎‎‏‎‎‏‏‎‎‏‏‏‎‏‏‏‎‏‏‎‏‎‏‎‏‎‏‏‎‏‏‏‏‏‎‎‏‏‏‎‎‏‏‏‏‎Allows the app to access the BluetoothShare manager and use it to transfer files.‎‏‎‎‏‎"</string>
-    <string name="permlab_bluetoothWhitelist" msgid="7091552898592306386">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‏‏‏‎‎‎‏‎‎‏‏‎‏‎‏‎‎‏‎‎‎‎‎‏‏‏‎‎‏‎‎‏‏‏‏‎‎‎‏‎‎‏‏‏‏‏‎‎‎‏‏‏‎‏‎‎‏‏‎‏‎‎‏‎‎Whitelist bluetooth device access.‎‏‎‎‏‎"</string>
-    <string name="permdesc_bluetoothWhitelist" msgid="5494513855192170109">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‏‏‎‎‏‏‎‎‎‏‎‎‎‎‎‎‎‏‏‎‏‏‏‏‎‎‏‏‏‏‎‏‎‏‏‏‏‎‎‎‎‎‏‏‎‎‎‎‏‏‏‏‏‏‏‎‎‏‏‏‏‏‎‏‎Allows the app to temporarily whitelist a Bluetooth device, allowing that device to send files to this device without user confirmation.‎‏‎‎‏‎"</string>
+    <string name="permlab_bluetoothAcceptlist" msgid="2647575807648622053">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‎‏‎‎‏‎‎‏‎‏‏‏‏‏‎‎‎‎‏‎‏‎‎‎‎‏‎‏‎‏‏‎‏‏‎‏‎‏‎‏‏‎‏‎‎‎‎‎‏‏‎‎‏‎‏‏‏‏‎‎‏‎‏‎Acceptlist bluetooth device access.‎‏‎‎‏‎"</string>
+    <string name="permdesc_bluetoothAcceptlist" msgid="2406423665674766442">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‎‏‎‎‎‎‏‎‏‏‎‎‏‎‏‎‏‎‏‎‏‎‏‏‎‎‏‎‎‏‎‏‏‏‏‏‎‏‏‏‎‏‎‎‎‏‏‎‏‎‎‏‎‎‎‎‏‏‎‏‎‏‎‎Allows the app to temporarily acceptlist a Bluetooth device, allowing that device to send files to this device without user confirmation.‎‏‎‎‏‎"</string>
     <string name="bt_share_picker_label" msgid="6268100924487046932">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‏‏‎‏‎‏‏‎‏‏‏‏‏‏‎‎‏‏‎‎‎‏‎‎‏‎‎‏‎‏‎‎‏‎‏‏‏‏‎‎‎‏‎‏‏‎‎‏‎‎‎‏‎‏‏‏‎‎‎‏‎‏‎‎‎Bluetooth‎‏‎‎‏‎"</string>
     <string name="unknown_device" msgid="9221903979877041009">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‎‏‎‏‏‎‎‏‎‎‎‏‏‎‎‏‏‏‏‎‏‎‏‏‎‎‏‎‏‎‏‎‏‎‏‎‎‎‎‎‎‏‏‎‏‏‏‎‎‎‏‎Unknown device‎‏‎‎‏‎"</string>
     <string name="unknownNumber" msgid="4994750948072751566">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‏‏‎‎‎‏‎‏‎‏‎‏‎‎‎‎‏‏‏‎‏‎‏‏‏‎‎‎‎‏‏‎‎‎‏‎‎‎‎‏‎‏‏‎‏‏‎‎‎‎‎‎‎‎‎‏‏‏‎‎‏‏‏‎‎Unknown‎‏‎‎‏‎"</string>
@@ -37,7 +37,7 @@
     <string name="incoming_file_confirm_timeout_ok" msgid="1414676773249857278">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‎‏‏‎‎‏‏‏‎‏‎‎‎‎‏‏‏‏‏‎‎‎‏‎‎‎‎‎‏‏‏‏‎‏‎‏‏‎‏‏‎‎‎‏‏‎‏‏‎‏‏‎‏‏‎‏‏‏‏‏‏‏‎‎OK‎‏‎‎‏‎"</string>
     <string name="incoming_file_confirm_timeout_content" msgid="172779756093975981">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‎‏‎‏‎‎‏‏‎‎‏‎‏‏‏‎‏‎‏‏‎‎‏‎‎‎‏‎‏‏‏‎‏‎‎‏‎‎‏‎‏‏‏‎‎‎‎‏‎‏‏‎‏‏‎‏‎‏‏‎‏‎There was a timeout while accepting an incoming file from \"‎‏‎‎‏‏‎<xliff:g id="SENDER">%1$s</xliff:g>‎‏‎‎‏‏‏‎\"‎‏‎‎‏‎"</string>
     <string name="incoming_file_confirm_Notification_title" msgid="5573329005298936903">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‏‏‎‎‏‏‎‏‎‏‎‏‏‎‎‎‎‏‏‏‎‎‎‏‎‎‏‏‎‎‏‏‏‎‎‏‏‎‎‏‎‎‏‏‏‎‎‏‎‏‏‎‏‏‎‎‎‏‎‎‎‏‏‏‎Incoming file‎‏‎‎‏‎"</string>
-    <string name="incoming_file_confirm_Notification_content" msgid="3359694069319644738">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‎‏‎‏‏‏‎‏‎‏‎‎‎‎‎‎‎‎‎‎‏‏‏‏‏‏‏‎‏‎‎‏‎‏‎‎‎‏‏‏‏‎‎‏‎‎‎‏‎‏‏‎‏‏‎‎‏‎‎‎‎‏‎‎‎‏‎‎‏‏‎<xliff:g id="SENDER">%1$s</xliff:g>‎‏‎‎‏‏‏‎ is ready to send ‎‏‎‎‏‏‎<xliff:g id="FILE">%2$s</xliff:g>‎‏‎‎‏‏‏‎‎‏‎‎‏‎"</string>
+    <string name="incoming_file_confirm_Notification_content" msgid="6237487486781004992">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‏‏‎‏‎‏‏‎‏‎‎‏‎‎‎‎‎‎‎‎‎‎‎‏‏‏‎‏‎‎‏‎‎‏‎‏‎‏‎‎‏‎‎‎‎‏‏‎‏‎‎‏‏‏‎‎‏‏‎‎‎‎‎‎‎‎‏‎‎‏‏‎<xliff:g id="SENDER">%1$s</xliff:g>‎‏‎‎‏‏‏‎ is ready to send a file: ‎‏‎‎‏‏‎<xliff:g id="FILE">%2$s</xliff:g>‎‏‎‎‏‏‏‎‎‏‎‎‏‎"</string>
     <string name="notification_receiving" msgid="4674648179652543984">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‏‏‎‎‎‎‎‎‏‏‎‏‏‏‏‏‏‎‏‎‏‏‏‏‏‏‎‎‎‎‎‎‏‎‎‎‎‏‏‎‎‎‎‏‏‏‎‏‏‏‎‎‎‏‎‏‏‏‏‏‎‎‎‎‎Bluetooth share: Receiving ‎‏‎‎‏‏‎<xliff:g id="FILE">%1$s</xliff:g>‎‏‎‎‏‏‏‎‎‏‎‎‏‎"</string>
     <string name="notification_received" msgid="3324588019186687985">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‎‏‎‏‏‏‎‎‎‏‎‎‎‏‏‎‏‎‎‏‏‏‏‎‎‏‏‎‎‎‎‎‏‏‎‎‏‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‏‏‎‎‎‏‎Bluetooth share: Received ‎‏‎‎‏‏‎<xliff:g id="FILE">%1$s</xliff:g>‎‏‎‎‏‏‏‎‎‏‎‎‏‎"</string>
     <string name="notification_received_fail" msgid="3619350997285714746">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‎‏‏‎‎‏‎‎‎‏‏‏‎‏‎‏‎‎‎‎‏‎‎‏‎‎‎‏‏‏‎‎‏‎‎‏‏‏‎‏‎‏‎‏‎‏‏‏‎‏‏‏‎‏‏‎‎‏‏‏‎‏‎‎Bluetooth share: File ‎‏‎‎‏‏‎<xliff:g id="FILE">%1$s</xliff:g>‎‏‎‎‏‏‏‎ not received‎‏‎‎‏‎"</string>
@@ -83,8 +83,8 @@
     <string name="bt_toast_4" msgid="4678812947604395649">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‏‏‎‎‎‎‎‎‏‏‏‎‏‏‏‎‎‏‏‏‏‎‏‏‏‎‎‏‎‏‏‎‎‎‏‎‎‎‎‎‎‏‏‏‎‏‎‏‎‏‎‏‎‏‏‎‏‎‎‎‎‎‎‏‎Sending file to \"‎‏‎‎‏‏‎<xliff:g id="RECIPIENT">%1$s</xliff:g>‎‏‎‎‏‏‏‎\"‎‏‎‎‏‎"</string>
     <string name="bt_toast_5" msgid="2846870992823019494">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‎‏‎‎‏‏‏‏‎‎‎‎‎‏‎‎‎‎‏‏‏‏‎‎‎‎‏‎‏‎‏‏‎‏‎‏‎‎‎‏‎‎‏‎‏‎‎‎‏‏‎‎‏‏‏‏‏‏‎‎‏‏‎‎Sending ‎‏‎‎‏‏‎<xliff:g id="NUMBER">%1$s</xliff:g>‎‏‎‎‏‏‏‎ files to \"‎‏‎‎‏‏‎<xliff:g id="RECIPIENT">%2$s</xliff:g>‎‏‎‎‏‏‏‎\"‎‏‎‎‏‎"</string>
     <string name="bt_toast_6" msgid="1855266596936622458">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‎‏‏‏‎‎‏‏‎‏‏‏‏‏‏‎‎‏‏‏‎‏‏‎‎‏‎‎‎‏‏‏‏‏‎‏‎‏‎‎‎‎‎‏‏‏‎‎‎‏‎‎‎‎‏‎‏‏‏‏‎‏‎‎Stopped sending file to \"‎‏‎‎‏‏‎<xliff:g id="RECIPIENT">%1$s</xliff:g>‎‏‎‎‏‏‏‎\"‎‏‎‎‏‎"</string>
-    <string name="bt_sm_2_1_nosdcard" msgid="1791835163301501637">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‎‏‏‏‎‎‎‏‏‎‏‏‏‎‏‏‏‏‎‎‎‎‎‏‎‎‏‎‏‏‎‎‏‏‎‎‏‎‎‏‎‏‎‎‎‏‏‏‏‎‏‏‎‏‎‏‏‎‎‎‏‎‏‎There isn\'t enough space in USB storage to save the file from \"‎‏‎‎‏‏‎<xliff:g id="SENDER">%1$s</xliff:g>‎‏‎‎‏‏‏‎\"‎‏‎‎‏‎"</string>
-    <string name="bt_sm_2_1_default" msgid="9115512207909504071">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‏‏‏‏‏‏‏‎‏‎‎‎‎‎‎‎‏‏‎‎‏‏‏‎‎‎‎‎‏‏‏‎‏‎‎‏‏‎‏‎‎‎‏‎‏‏‏‎‏‏‎‎‎‏‎‎‎‏‎‎‎‏‏‏‎There isn\'t enough space on the SD card to save the file from \"‎‏‎‎‏‏‎<xliff:g id="SENDER">%1$s</xliff:g>‎‏‎‎‏‏‏‎\"‎‏‎‎‏‎"</string>
+    <string name="bt_sm_2_1_nosdcard" msgid="5354343190503952837">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‏‏‎‎‏‎‏‎‎‏‎‎‏‏‏‎‎‏‏‏‎‎‏‎‏‏‎‎‎‎‏‏‎‏‏‎‎‎‏‏‏‎‎‎‎‏‎‏‎‎‎‏‎‎‎‏‏‏‎‎‎‏‎‏‎There isn\'t enough space in USB storage to save the file.‎‏‎‎‏‎"</string>
+    <string name="bt_sm_2_1_default" msgid="2497541206648973852">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‎‏‎‎‎‏‎‏‎‏‎‏‎‎‏‎‎‎‎‏‏‎‎‎‏‏‏‏‏‏‎‏‎‏‏‎‎‏‎‎‎‏‏‎‏‏‎‎‏‏‎‏‎‏‎‎‎‎‏‏‏‎‎‎There isn\'t enough space on the SD card to save the file.‎‏‎‎‏‎"</string>
     <string name="bt_sm_2_2" msgid="2965243265852680543">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‎‏‎‏‎‎‏‎‎‏‎‎‏‏‎‏‎‏‎‏‎‎‏‎‎‎‎‏‎‏‎‎‎‏‎‏‏‎‏‏‏‎‎‎‏‎‎‏‎‏‎‏‎‎‏‎‏‎‏‏‏‏‏‎Space needed: ‎‏‎‎‏‏‎<xliff:g id="SIZE">%1$s</xliff:g>‎‏‎‎‏‏‏‎‎‏‎‎‏‎"</string>
     <string name="ErrorTooManyRequests" msgid="8578277541472944529">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‏‏‏‏‎‏‏‏‎‎‎‎‏‏‎‎‎‎‏‎‏‎‎‏‏‏‏‏‏‎‎‏‎‏‏‎‏‏‎‎‎‎‏‏‎‏‎‎‏‏‎‎‎‎‎‏‏‎‎‏‎‎‎‏‎Too many requests are being processed. Try again later.‎‏‎‎‏‎"</string>
     <string name="status_pending" msgid="2503691772030877944">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‎‏‎‎‎‏‎‏‎‏‏‏‏‏‎‏‏‏‎‎‏‏‎‎‏‏‎‎‏‏‎‏‏‎‎‏‎‏‎‏‏‏‏‏‎‎‎‎‎‎‎‎‎‎‎‏‏‏‏‏‎‎‎‎File transfer not started yet.‎‏‎‎‏‎"</string>
diff --git a/res/values-es-rUS/strings.xml b/res/values-es-rUS/strings.xml
index d7be544..8dee78a 100644
--- a/res/values-es-rUS/strings.xml
+++ b/res/values-es-rUS/strings.xml
@@ -18,8 +18,8 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="permlab_bluetoothShareManager" msgid="311492132450338925">"Accede al administrador de descarga."</string>
     <string name="permdesc_bluetoothShareManager" msgid="8930572979123190223">"Permite a la aplicación acceder al administrador de BluetoothShare y usarlo para transferir archivos."</string>
-    <string name="permlab_bluetoothWhitelist" msgid="7091552898592306386">"Acceso a los dispositivos Bluetooth autorizados"</string>
-    <string name="permdesc_bluetoothWhitelist" msgid="5494513855192170109">"Permite a la aplicación autorizar temporalmente un dispositivo Bluetooth, lo cual permite que este dispositivo envíe archivos sin la confirmación del usuario."</string>
+    <string name="permlab_bluetoothAcceptlist" msgid="2647575807648622053">"Acceso del dispositivo Bluetooth a la lista blanca."</string>
+    <string name="permdesc_bluetoothAcceptlist" msgid="2406423665674766442">"Permite que la app incluya temporalmente un dispositivo Bluetooth en la lista blanca, lo que permite que ese dispositivo envíe archivos sin la confirmación del usuario."</string>
     <string name="bt_share_picker_label" msgid="6268100924487046932">"Bluetooth"</string>
     <string name="unknown_device" msgid="9221903979877041009">"Dispositivo desconocido"</string>
     <string name="unknownNumber" msgid="4994750948072751566">"Desconocido"</string>
@@ -37,7 +37,7 @@
     <string name="incoming_file_confirm_timeout_ok" msgid="1414676773249857278">"Aceptar"</string>
     <string name="incoming_file_confirm_timeout_content" msgid="172779756093975981">"Tiempo de espera agotado al aceptar un archivo entrante de \"<xliff:g id="SENDER">%1$s</xliff:g>\""</string>
     <string name="incoming_file_confirm_Notification_title" msgid="5573329005298936903">"Archivo entrante"</string>
-    <string name="incoming_file_confirm_Notification_content" msgid="3359694069319644738">"<xliff:g id="SENDER">%1$s</xliff:g> está listo para enviar <xliff:g id="FILE">%2$s</xliff:g>"</string>
+    <string name="incoming_file_confirm_Notification_content" msgid="6237487486781004992">"<xliff:g id="SENDER">%1$s</xliff:g> se preparó para enviar un archivo: <xliff:g id="FILE">%2$s</xliff:g>"</string>
     <string name="notification_receiving" msgid="4674648179652543984">"Bluetooth: recibiendo <xliff:g id="FILE">%1$s</xliff:g>"</string>
     <string name="notification_received" msgid="3324588019186687985">"Bluetooth: <xliff:g id="FILE">%1$s</xliff:g> recibido"</string>
     <string name="notification_received_fail" msgid="3619350997285714746">"Bluetooth: no se recibió el archivo <xliff:g id="FILE">%1$s</xliff:g>"</string>
@@ -83,8 +83,8 @@
     <string name="bt_toast_4" msgid="4678812947604395649">"Enviando archivo a \"<xliff:g id="RECIPIENT">%1$s</xliff:g>\""</string>
     <string name="bt_toast_5" msgid="2846870992823019494">"Enviando <xliff:g id="NUMBER">%1$s</xliff:g> archivos a \"<xliff:g id="RECIPIENT">%2$s</xliff:g>\""</string>
     <string name="bt_toast_6" msgid="1855266596936622458">"Se detuvo el envío del archivo a \"<xliff:g id="RECIPIENT">%1$s</xliff:g>\""</string>
-    <string name="bt_sm_2_1_nosdcard" msgid="1791835163301501637">"No hay espacio suficiente en el almacenamiento USB para guardar el archivo de \"<xliff:g id="SENDER">%1$s</xliff:g>\""</string>
-    <string name="bt_sm_2_1_default" msgid="9115512207909504071">"No hay suficiente espacio en la tarjeta SD para guardar el archivo de \"<xliff:g id="SENDER">%1$s</xliff:g>\""</string>
+    <string name="bt_sm_2_1_nosdcard" msgid="5354343190503952837">"No hay espacio suficiente en el almacenamiento USB para guardar el archivo."</string>
+    <string name="bt_sm_2_1_default" msgid="2497541206648973852">"No hay espacio suficiente en la tarjeta SD para guardar el archivo."</string>
     <string name="bt_sm_2_2" msgid="2965243265852680543">"Espacio necesario: <xliff:g id="SIZE">%1$s</xliff:g>"</string>
     <string name="ErrorTooManyRequests" msgid="8578277541472944529">"Se están procesando demasiadas solicitudes. Vuelve a intentarlo más tarde."</string>
     <string name="status_pending" msgid="2503691772030877944">"Aún no comenzó la transferencia de archivos."</string>
diff --git a/res/values-es/strings.xml b/res/values-es/strings.xml
index 65a0fa3..55ffd5c 100644
--- a/res/values-es/strings.xml
+++ b/res/values-es/strings.xml
@@ -18,8 +18,8 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="permlab_bluetoothShareManager" msgid="311492132450338925">"Acceso al administrador de descargas"</string>
     <string name="permdesc_bluetoothShareManager" msgid="8930572979123190223">"Permite que la aplicación acceda al administrador BluetoothShare y lo use para transferir archivos."</string>
-    <string name="permlab_bluetoothWhitelist" msgid="7091552898592306386">"Acceso de dispositivos Bluetooth autorizados"</string>
-    <string name="permdesc_bluetoothWhitelist" msgid="5494513855192170109">"Permite que la aplicación autorice temporalmente a un dispositivo Bluetooth para que pueda enviar archivos a este dispositivo sin la confirmación del usuario."</string>
+    <string name="permlab_bluetoothAcceptlist" msgid="2647575807648622053">"Permitir acceso de dispositivo Bluetooth."</string>
+    <string name="permdesc_bluetoothAcceptlist" msgid="2406423665674766442">"Permite que la aplicación autorice temporalmente un dispositivo Bluetooth a enviar archivos a este dispositivo sin la confirmación del usuario."</string>
     <string name="bt_share_picker_label" msgid="6268100924487046932">"Bluetooth"</string>
     <string name="unknown_device" msgid="9221903979877041009">"Dispositivo desconocido"</string>
     <string name="unknownNumber" msgid="4994750948072751566">"Desconocido"</string>
@@ -37,7 +37,7 @@
     <string name="incoming_file_confirm_timeout_ok" msgid="1414676773249857278">"Aceptar"</string>
     <string name="incoming_file_confirm_timeout_content" msgid="172779756093975981">"Se ha agotado el tiempo para aceptar el archivo entrante de \"<xliff:g id="SENDER">%1$s</xliff:g>\"."</string>
     <string name="incoming_file_confirm_Notification_title" msgid="5573329005298936903">"Archivo entrante"</string>
-    <string name="incoming_file_confirm_Notification_content" msgid="3359694069319644738">"<xliff:g id="SENDER">%1$s</xliff:g> ya puede enviar <xliff:g id="FILE">%2$s</xliff:g>"</string>
+    <string name="incoming_file_confirm_Notification_content" msgid="6237487486781004992">"<xliff:g id="SENDER">%1$s</xliff:g> ya puede enviar un archivo: <xliff:g id="FILE">%2$s</xliff:g>"</string>
     <string name="notification_receiving" msgid="4674648179652543984">"Bluetooth: recibiendo <xliff:g id="FILE">%1$s</xliff:g>"</string>
     <string name="notification_received" msgid="3324588019186687985">"Compartir con Bluetooth: <xliff:g id="FILE">%1$s</xliff:g> recibido"</string>
     <string name="notification_received_fail" msgid="3619350997285714746">"Bluetooth: <xliff:g id="FILE">%1$s</xliff:g> no recibido"</string>
@@ -83,8 +83,8 @@
     <string name="bt_toast_4" msgid="4678812947604395649">"Enviando archivo a \"<xliff:g id="RECIPIENT">%1$s</xliff:g>\""</string>
     <string name="bt_toast_5" msgid="2846870992823019494">"Enviando <xliff:g id="NUMBER">%1$s</xliff:g> archivos a \"<xliff:g id="RECIPIENT">%2$s</xliff:g>\""</string>
     <string name="bt_toast_6" msgid="1855266596936622458">"Se ha detenido el envío del archivo a \"<xliff:g id="RECIPIENT">%1$s</xliff:g>\""</string>
-    <string name="bt_sm_2_1_nosdcard" msgid="1791835163301501637">"No hay suficiente espacio en el almacenamiento USB para guardar el archivo de \"<xliff:g id="SENDER">%1$s</xliff:g>\""</string>
-    <string name="bt_sm_2_1_default" msgid="9115512207909504071">"No hay suficiente espacio en la tarjeta SD para guardar el archivo de \"<xliff:g id="SENDER">%1$s</xliff:g>\""</string>
+    <string name="bt_sm_2_1_nosdcard" msgid="5354343190503952837">"No hay suficiente espacio en el almacenamiento USB para guardar el archivo."</string>
+    <string name="bt_sm_2_1_default" msgid="2497541206648973852">"No hay suficiente espacio en la tarjeta SD para guardar el archivo."</string>
     <string name="bt_sm_2_2" msgid="2965243265852680543">"Espacio necesario: <xliff:g id="SIZE">%1$s</xliff:g>"</string>
     <string name="ErrorTooManyRequests" msgid="8578277541472944529">"Se están procesando demasiadas solicitudes. Vuelve a intentarlo más tarde."</string>
     <string name="status_pending" msgid="2503691772030877944">"Aún no se ha iniciado la transferencia de archivos."</string>
diff --git a/res/values-et/strings.xml b/res/values-et/strings.xml
index be8f504..a87f7c0 100644
--- a/res/values-et/strings.xml
+++ b/res/values-et/strings.xml
@@ -18,8 +18,8 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="permlab_bluetoothShareManager" msgid="311492132450338925">"Pääs allalaadimishalduri juurde."</string>
     <string name="permdesc_bluetoothShareManager" msgid="8930572979123190223">"Võimaldab rakendusel pääseda BluetoothShare\'i haldurisse ja kasutada seda failide edastamiseks."</string>
-    <string name="permlab_bluetoothWhitelist" msgid="7091552898592306386">"Lubage Bluetooth-seadme juurdepääs."</string>
-    <string name="permdesc_bluetoothWhitelist" msgid="5494513855192170109">"Lubab rakendusel lisada Bluetooth-seadme ajutiselt lubatud seadmete loendisse, mis võimaldab seadmel saata faile sellesse seadmesse ilma kasutaja kinnituseta."</string>
+    <string name="permlab_bluetoothAcceptlist" msgid="2647575807648622053">"Bluetooth-seadmele juurdepääsu andmine."</string>
+    <string name="permdesc_bluetoothAcceptlist" msgid="2406423665674766442">"Lubab rakendusel lisada Bluetooth-seadme ajutiselt lubatud seadmete loendisse, mis võimaldab seadmel saata faile sellesse seadmesse ilma kasutaja kinnituseta."</string>
     <string name="bt_share_picker_label" msgid="6268100924487046932">"Bluetooth"</string>
     <string name="unknown_device" msgid="9221903979877041009">"Tundmatu seade"</string>
     <string name="unknownNumber" msgid="4994750948072751566">"Tundmatu"</string>
@@ -37,7 +37,7 @@
     <string name="incoming_file_confirm_timeout_ok" msgid="1414676773249857278">"OK"</string>
     <string name="incoming_file_confirm_timeout_content" msgid="172779756093975981">"Esines ajalõpp sissetuleva faili aktsepteerimisel saatjalt „<xliff:g id="SENDER">%1$s</xliff:g>”"</string>
     <string name="incoming_file_confirm_Notification_title" msgid="5573329005298936903">"Sissetulev fail"</string>
-    <string name="incoming_file_confirm_Notification_content" msgid="3359694069319644738">"Saatja <xliff:g id="SENDER">%1$s</xliff:g> on faili <xliff:g id="FILE">%2$s</xliff:g> saatmiseks valmis"</string>
+    <string name="incoming_file_confirm_Notification_content" msgid="6237487486781004992">"<xliff:g id="SENDER">%1$s</xliff:g> on valmis faili <xliff:g id="FILE">%2$s</xliff:g> saatma"</string>
     <string name="notification_receiving" msgid="4674648179652543984">"Bluetoothi jagamine: faili <xliff:g id="FILE">%1$s</xliff:g> vastuvõtmine"</string>
     <string name="notification_received" msgid="3324588019186687985">"Bluetoothi jagamine: <xliff:g id="FILE">%1$s</xliff:g> vastu võetud"</string>
     <string name="notification_received_fail" msgid="3619350997285714746">"Bluetoothi jagamine: faili <xliff:g id="FILE">%1$s</xliff:g> pole saadud"</string>
@@ -83,8 +83,8 @@
     <string name="bt_toast_4" msgid="4678812947604395649">"Faili saatmine saajale „<xliff:g id="RECIPIENT">%1$s</xliff:g>”"</string>
     <string name="bt_toast_5" msgid="2846870992823019494">"<xliff:g id="NUMBER">%1$s</xliff:g> faili saatmine saajale „<xliff:g id="RECIPIENT">%2$s</xliff:g>”"</string>
     <string name="bt_toast_6" msgid="1855266596936622458">"Faili saatmine saajale „<xliff:g id="RECIPIENT">%1$s</xliff:g>” peatatud"</string>
-    <string name="bt_sm_2_1_nosdcard" msgid="1791835163301501637">"USB-salvestusruumis pole saatjalt „<xliff:g id="SENDER">%1$s</xliff:g>” saadud faili salvestamiseks piisavalt ruumi"</string>
-    <string name="bt_sm_2_1_default" msgid="9115512207909504071">"SD-kaardil pole saatjalt „<xliff:g id="SENDER">%1$s</xliff:g>” saadud faili salvestamiseks piisavalt ruumi"</string>
+    <string name="bt_sm_2_1_nosdcard" msgid="5354343190503952837">"USB-salvestusseadmes pole faili salvestamiseks piisavalt ruumi."</string>
+    <string name="bt_sm_2_1_default" msgid="2497541206648973852">"SD-kaardil pole faili salvestamiseks piisavalt ruumi."</string>
     <string name="bt_sm_2_2" msgid="2965243265852680543">"Vajalik ruum: <xliff:g id="SIZE">%1$s</xliff:g>"</string>
     <string name="ErrorTooManyRequests" msgid="8578277541472944529">"Liiga palju taotlusi on töötlemisel. Proovige hiljem uuesti."</string>
     <string name="status_pending" msgid="2503691772030877944">"Failiedastust pole veel käivitatud."</string>
diff --git a/res/values-eu/strings.xml b/res/values-eu/strings.xml
index 47b0c4f..e459dd3 100644
--- a/res/values-eu/strings.xml
+++ b/res/values-eu/strings.xml
@@ -18,8 +18,8 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="permlab_bluetoothShareManager" msgid="311492132450338925">"Atzitu deskargen kudeatzailea."</string>
     <string name="permdesc_bluetoothShareManager" msgid="8930572979123190223">"Bluetooth bidezko partekatzeen kudeatzailea atzitzea eta fitxategiak transferitzeko erabiltzeko baimena ematen die aplikazioei."</string>
-    <string name="permlab_bluetoothWhitelist" msgid="7091552898592306386">"Jarri onartutakoen zerrendan Bluetooth bidezko gailua."</string>
-    <string name="permdesc_bluetoothWhitelist" msgid="5494513855192170109">"Bluetooth bidezko gailu bat aldi baterako onartutakoen zerrendan jartzeko baimena ematen die aplikazioei, gailu honetara fitxategiak bidaltzeko baimena izan dezan, baina gailu honen erabiltzaileari berrespena eskatu beharrik gabe."</string>
+    <string name="permlab_bluetoothAcceptlist" msgid="2647575807648622053">"Ezarri Bluetooth bidezko gailuak onartutakoen zerrendan."</string>
+    <string name="permdesc_bluetoothAcceptlist" msgid="2406423665674766442">"Bluetooth bidezko gailu bat aldi baterako onartutakoen zerrendan ezartzeko baimena ematen die aplikazioei, gailu honetara fitxategiak bidaltzeko baimena izan dezan, baina gailu honen erabiltzaileari berrespena eskatu beharrik gabe."</string>
     <string name="bt_share_picker_label" msgid="6268100924487046932">"Bluetooth-a"</string>
     <string name="unknown_device" msgid="9221903979877041009">"Identifikatu ezin den gailua"</string>
     <string name="unknownNumber" msgid="4994750948072751566">"Ezezaguna"</string>
@@ -37,7 +37,7 @@
     <string name="incoming_file_confirm_timeout_ok" msgid="1414676773249857278">"Ados"</string>
     <string name="incoming_file_confirm_timeout_content" msgid="172779756093975981">"\"<xliff:g id="SENDER">%1$s</xliff:g>\" igorlearen sarrerako fitxategia onartzeko denbora-muga gainditu da"</string>
     <string name="incoming_file_confirm_Notification_title" msgid="5573329005298936903">"Sarrerako fitxategia"</string>
-    <string name="incoming_file_confirm_Notification_content" msgid="3359694069319644738">"<xliff:g id="SENDER">%1$s</xliff:g> prest dago <xliff:g id="FILE">%2$s</xliff:g> bidaltzeko"</string>
+    <string name="incoming_file_confirm_Notification_content" msgid="6237487486781004992">"<xliff:g id="SENDER">%1$s</xliff:g> igorlea prest dago fitxategi hau bidaltzeko: <xliff:g id="FILE">%2$s</xliff:g>"</string>
     <string name="notification_receiving" msgid="4674648179652543984">"Bluetooth bidez partekatzea: <xliff:g id="FILE">%1$s</xliff:g> fitxategia jasotzen"</string>
     <string name="notification_received" msgid="3324588019186687985">"Bluetooth bidez partekatzea: <xliff:g id="FILE">%1$s</xliff:g> fitxategia jaso da"</string>
     <string name="notification_received_fail" msgid="3619350997285714746">"Bluetooth bidez partekatzea: ez da jaso <xliff:g id="FILE">%1$s</xliff:g> fitxategia"</string>
@@ -83,8 +83,8 @@
     <string name="bt_toast_4" msgid="4678812947604395649">"\"<xliff:g id="RECIPIENT">%1$s</xliff:g>\" hartzaileari fitxategia bidaltzen"</string>
     <string name="bt_toast_5" msgid="2846870992823019494">"\"<xliff:g id="RECIPIENT">%2$s</xliff:g>\" hartzaileari <xliff:g id="NUMBER">%1$s</xliff:g> fitxategi bidaltzen"</string>
     <string name="bt_toast_6" msgid="1855266596936622458">"\"<xliff:g id="RECIPIENT">%1$s</xliff:g>\" hartzaileari fitxategia bidaltzeari utzi zaio."</string>
-    <string name="bt_sm_2_1_nosdcard" msgid="1791835163301501637">"Ez dago \"<xliff:g id="SENDER">%1$s</xliff:g>\" erabiltzailearen fitxategia gordetzeko behar adina toki USB bidezko memorian"</string>
-    <string name="bt_sm_2_1_default" msgid="9115512207909504071">"Ez dago \"<xliff:g id="SENDER">%1$s</xliff:g>\" erabiltzailearen fitxategia gordetzeko behar adina toki SD txartelean"</string>
+    <string name="bt_sm_2_1_nosdcard" msgid="5354343190503952837">"Ez dago fitxategia gordetzeko behar adina toki USB bidezko memorian."</string>
+    <string name="bt_sm_2_1_default" msgid="2497541206648973852">"Ez dago fitxategia gordetzeko behar adina toki SD txartelean."</string>
     <string name="bt_sm_2_2" msgid="2965243265852680543">"Beharrezko memoria: <xliff:g id="SIZE">%1$s</xliff:g>"</string>
     <string name="ErrorTooManyRequests" msgid="8578277541472944529">"Eskaera gehiegi prozesatzen ari dira. Saiatu berriro geroago."</string>
     <string name="status_pending" msgid="2503691772030877944">"Ez da fitxategi-transferentzia oraindik hasi."</string>
diff --git a/res/values-fa/strings.xml b/res/values-fa/strings.xml
index 165a501..4299d69 100644
--- a/res/values-fa/strings.xml
+++ b/res/values-fa/strings.xml
@@ -18,8 +18,8 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="permlab_bluetoothShareManager" msgid="311492132450338925">"‏دسترسی به Download Manager."</string>
     <string name="permdesc_bluetoothShareManager" msgid="8930572979123190223">"‏به برنامه برای دسترسی به مدیر BluetoothShare و استفاده از آن برای انتقال فایل‌ها اجازه می‌دهد."</string>
-    <string name="permlab_bluetoothWhitelist" msgid="7091552898592306386">"مجاز کردن دسترسی به دستگاه بلوتوثی."</string>
-    <string name="permdesc_bluetoothWhitelist" msgid="5494513855192170109">"به برنامه اجازه می‌دهد تا موقتاً یک دستگاه بلوتوث را در فهرست سفید وارد کند، و به آن دستگاه اجازه می‌دهد بدون تأیید کاربر به این دستگاه فایل ارسال کند."</string>
+    <string name="permlab_bluetoothAcceptlist" msgid="2647575807648622053">"مجاز کردن دسترسی به دستگاه بلوتوثی."</string>
+    <string name="permdesc_bluetoothAcceptlist" msgid="2406423665674766442">"به برنامه اجازه می‌دهد موقتاً یک دستگاه بلوتوث را در فهرست مجاز قرار دهد؛ با این کار دستگاه موردنظر مجاز خواهد بود بدون تأیید کاربر برای این دستگاه فایل ارسال کند."</string>
     <string name="bt_share_picker_label" msgid="6268100924487046932">"بلوتوث"</string>
     <string name="unknown_device" msgid="9221903979877041009">"دستگاه ناشناس"</string>
     <string name="unknownNumber" msgid="4994750948072751566">"ناشناس"</string>
@@ -37,7 +37,7 @@
     <string name="incoming_file_confirm_timeout_ok" msgid="1414676773249857278">"تأیید"</string>
     <string name="incoming_file_confirm_timeout_content" msgid="172779756093975981">"هنگام پذیرش یک فایل ورودی از \"<xliff:g id="SENDER">%1$s</xliff:g>\" درنگ پیش آمد"</string>
     <string name="incoming_file_confirm_Notification_title" msgid="5573329005298936903">"فایل ورودی"</string>
-    <string name="incoming_file_confirm_Notification_content" msgid="3359694069319644738">"<xliff:g id="SENDER">%1$s</xliff:g> آماده ارسال <xliff:g id="FILE">%2$s</xliff:g> است"</string>
+    <string name="incoming_file_confirm_Notification_content" msgid="6237487486781004992">"<xliff:g id="SENDER">%1$s</xliff:g> آماده ارسال فایل است: <xliff:g id="FILE">%2$s</xliff:g>"</string>
     <string name="notification_receiving" msgid="4674648179652543984">"اشتراک بلوتوث: در حال دریافت <xliff:g id="FILE">%1$s</xliff:g>"</string>
     <string name="notification_received" msgid="3324588019186687985">"اشتراک بلوتوث: <xliff:g id="FILE">%1$s</xliff:g> دریافت شد"</string>
     <string name="notification_received_fail" msgid="3619350997285714746">"اشتراک بلوتوث: فایل <xliff:g id="FILE">%1$s</xliff:g>دریافت نشد"</string>
@@ -83,8 +83,8 @@
     <string name="bt_toast_4" msgid="4678812947604395649">"ارسال فایل به \"<xliff:g id="RECIPIENT">%1$s</xliff:g>\""</string>
     <string name="bt_toast_5" msgid="2846870992823019494">"ارسال <xliff:g id="NUMBER">%1$s</xliff:g> فایل به \"<xliff:g id="RECIPIENT">%2$s</xliff:g>\""</string>
     <string name="bt_toast_6" msgid="1855266596936622458">"ارسال فایل به \"<xliff:g id="RECIPIENT">%1$s</xliff:g>\" متوقف شد"</string>
-    <string name="bt_sm_2_1_nosdcard" msgid="1791835163301501637">"‏برای ذخیره فایل از «<xliff:g id="SENDER">%1$s</xliff:g>» فضای کافی در حافظه USB موجود نیست"</string>
-    <string name="bt_sm_2_1_default" msgid="9115512207909504071">"‏برای ذخیره فایل «<xliff:g id="SENDER">%1$s</xliff:g>» فضای کافی در کارت SD موجود نیست"</string>
+    <string name="bt_sm_2_1_nosdcard" msgid="5354343190503952837">"‏فضای کافی در حافظه USB برای ذخیره کردن فایل موجود نیست."</string>
+    <string name="bt_sm_2_1_default" msgid="2497541206648973852">"‏فضای کافی در کارت SD برای ذخیره کردن فایل موجود نیست."</string>
     <string name="bt_sm_2_2" msgid="2965243265852680543">"فضای مورد نیاز: <xliff:g id="SIZE">%1$s</xliff:g>"</string>
     <string name="ErrorTooManyRequests" msgid="8578277541472944529">"درخواست‌های بسیاری در حال انجام هستند. بعداً دوباره امتحان کنید."</string>
     <string name="status_pending" msgid="2503691772030877944">"انتقال فایل هنوز شروع نشده است."</string>
diff --git a/res/values-fi/strings.xml b/res/values-fi/strings.xml
index 84d9dca..aeb4260 100644
--- a/res/values-fi/strings.xml
+++ b/res/values-fi/strings.xml
@@ -18,8 +18,8 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="permlab_bluetoothShareManager" msgid="311492132450338925">"Lataustenhallinnan käyttö."</string>
     <string name="permdesc_bluetoothShareManager" msgid="8930572979123190223">"Antaa sovelluksen käyttää BluetoothShare-hallintaa tiedostojen siirtoon."</string>
-    <string name="permlab_bluetoothWhitelist" msgid="7091552898592306386">"Käyttö Bluetooth-laitteella, jolla on tilapäinen käyttöoikeus."</string>
-    <string name="permdesc_bluetoothWhitelist" msgid="5494513855192170109">"Mahdollistaa tilapäisten käyttöoikeuksien antamisen Bluetooth-laitteelle, jolloin tiedostojen lähettäminen laitteesta tähän laitteeseen ei vaadi käyttäjän hyväksyntää."</string>
+    <string name="permlab_bluetoothAcceptlist" msgid="2647575807648622053">"Käyttö Bluetooth-laitteella, jolla on tilapäinen lupa."</string>
+    <string name="permdesc_bluetoothAcceptlist" msgid="2406423665674766442">"Mahdollistaa tilapäisen luvan antamisen Bluetooth-laitteelle, jolloin tiedostojen lähettäminen laitteesta tähän laitteeseen ei vaadi käyttäjän hyväksyntää."</string>
     <string name="bt_share_picker_label" msgid="6268100924487046932">"Bluetooth"</string>
     <string name="unknown_device" msgid="9221903979877041009">"Tuntematon laite"</string>
     <string name="unknownNumber" msgid="4994750948072751566">"Tuntematon"</string>
@@ -37,7 +37,7 @@
     <string name="incoming_file_confirm_timeout_ok" msgid="1414676773249857278">"OK"</string>
     <string name="incoming_file_confirm_timeout_content" msgid="172779756093975981">"Yhteys aikakatkaistiin vastaanotettaessa tiedostoa lähettäjältä <xliff:g id="SENDER">%1$s</xliff:g>"</string>
     <string name="incoming_file_confirm_Notification_title" msgid="5573329005298936903">"Saapuva tiedosto"</string>
-    <string name="incoming_file_confirm_Notification_content" msgid="3359694069319644738">"<xliff:g id="SENDER">%1$s</xliff:g> on valmis lähettämään kohteen <xliff:g id="FILE">%2$s</xliff:g>."</string>
+    <string name="incoming_file_confirm_Notification_content" msgid="6237487486781004992">"<xliff:g id="SENDER">%1$s</xliff:g> on valmis lähettämään tiedoston: <xliff:g id="FILE">%2$s</xliff:g>"</string>
     <string name="notification_receiving" msgid="4674648179652543984">"Bluetooth-jako: vastaanotetaan tiedostoa <xliff:g id="FILE">%1$s</xliff:g>"</string>
     <string name="notification_received" msgid="3324588019186687985">"Bluetooth-jako: <xliff:g id="FILE">%1$s</xliff:g> vastaanotettu"</string>
     <string name="notification_received_fail" msgid="3619350997285714746">"Bluetooth-jako: tiedostoa <xliff:g id="FILE">%1$s</xliff:g> ei vastaanotettu"</string>
@@ -83,8 +83,8 @@
     <string name="bt_toast_4" msgid="4678812947604395649">"Lähetetään tiedosto vastaanottajalle <xliff:g id="RECIPIENT">%1$s</xliff:g>"</string>
     <string name="bt_toast_5" msgid="2846870992823019494">"Lähetetään <xliff:g id="NUMBER">%1$s</xliff:g> tiedostoa vastaanottajalle <xliff:g id="RECIPIENT">%2$s</xliff:g>"</string>
     <string name="bt_toast_6" msgid="1855266596936622458">"Tiedoston lähettäminen vastaanottajalle <xliff:g id="RECIPIENT">%1$s</xliff:g> pysäytetty"</string>
-    <string name="bt_sm_2_1_nosdcard" msgid="1791835163301501637">"<xliff:g id="SENDER">%1$s</xliff:g> lähettää tiedostoa, jolle ei ole tilaa USB-tallennustilassa"</string>
-    <string name="bt_sm_2_1_default" msgid="9115512207909504071">"<xliff:g id="SENDER">%1$s</xliff:g> lähettää tiedostoa, jolle ei ole tilaa SD-kortilla"</string>
+    <string name="bt_sm_2_1_nosdcard" msgid="5354343190503952837">"USB-tallennustila ei riitä tiedoston tallentamiseen."</string>
+    <string name="bt_sm_2_1_default" msgid="2497541206648973852">"SD-kortin tila ei riitä tiedoston tallentamiseen."</string>
     <string name="bt_sm_2_2" msgid="2965243265852680543">"Tilaa tarvitaan: <xliff:g id="SIZE">%1$s</xliff:g>"</string>
     <string name="ErrorTooManyRequests" msgid="8578277541472944529">"Liian monta käsiteltävää pyyntöä. Yritä myöhemmin uudelleen."</string>
     <string name="status_pending" msgid="2503691772030877944">"Tiedostonsiirto ei ole vielä alkanut."</string>
diff --git a/res/values-fr-rCA/strings.xml b/res/values-fr-rCA/strings.xml
index 9e96251..640c2a4 100644
--- a/res/values-fr-rCA/strings.xml
+++ b/res/values-fr-rCA/strings.xml
@@ -18,8 +18,8 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="permlab_bluetoothShareManager" msgid="311492132450338925">"Accéder au gestionnaire de téléchargement."</string>
     <string name="permdesc_bluetoothShareManager" msgid="8930572979123190223">"Permet à l\'application d\'accéder au gestionnaire BluetoothShare et de l\'utiliser pour le transfert de fichiers."</string>
-    <string name="permlab_bluetoothWhitelist" msgid="7091552898592306386">"Ajouter des appareils Bluetooth à la liste blanche."</string>
-    <string name="permdesc_bluetoothWhitelist" msgid="5494513855192170109">"Permet à l\'application d\'ajouter temporairement un appareil Bluetooth à la liste blanche. Ainsi, celui-ci peut envoyer des fichiers à cet appareil sans que la confirmation de l\'utilisateur soit nécessaire."</string>
+    <string name="permlab_bluetoothAcceptlist" msgid="2647575807648622053">"Ajouter l\'accès d\'un appareil Bluetooth à la liste verte."</string>
+    <string name="permdesc_bluetoothAcceptlist" msgid="2406423665674766442">"Permet à l\'application d\'ajouter temporairement un appareil Bluetooth à la liste verte afin que celui-ci puisse envoyer des fichiers à cet appareil sans que la confirmation de l\'utilisateur soit nécessaire."</string>
     <string name="bt_share_picker_label" msgid="6268100924487046932">"Bluetooth"</string>
     <string name="unknown_device" msgid="9221903979877041009">"Périphérique inconnu"</string>
     <string name="unknownNumber" msgid="4994750948072751566">"Inconnu"</string>
@@ -37,7 +37,7 @@
     <string name="incoming_file_confirm_timeout_ok" msgid="1414676773249857278">"OK"</string>
     <string name="incoming_file_confirm_timeout_content" msgid="172779756093975981">"Expiration du délai de réception du fichier de \"<xliff:g id="SENDER">%1$s</xliff:g>\""</string>
     <string name="incoming_file_confirm_Notification_title" msgid="5573329005298936903">"Fichier entrant"</string>
-    <string name="incoming_file_confirm_Notification_content" msgid="3359694069319644738">"<xliff:g id="SENDER">%1$s</xliff:g> est prêt à envoyer le fichier « <xliff:g id="FILE">%2$s</xliff:g> »"</string>
+    <string name="incoming_file_confirm_Notification_content" msgid="6237487486781004992">"<xliff:g id="SENDER">%1$s</xliff:g> est prêt à vous envoyer un fichier : <xliff:g id="FILE">%2$s</xliff:g>"</string>
     <string name="notification_receiving" msgid="4674648179652543984">"Partage Bluetooth : réception de <xliff:g id="FILE">%1$s</xliff:g>"</string>
     <string name="notification_received" msgid="3324588019186687985">"Partage Bluetooth : <xliff:g id="FILE">%1$s</xliff:g> reçu(s)"</string>
     <string name="notification_received_fail" msgid="3619350997285714746">"Partage Bluetooth : fichier <xliff:g id="FILE">%1$s</xliff:g> non reçu"</string>
@@ -83,8 +83,8 @@
     <string name="bt_toast_4" msgid="4678812947604395649">"Envoi du fichier à \"<xliff:g id="RECIPIENT">%1$s</xliff:g>\""</string>
     <string name="bt_toast_5" msgid="2846870992823019494">"Envoi de <xliff:g id="NUMBER">%1$s</xliff:g> fichiers à \"<xliff:g id="RECIPIENT">%2$s</xliff:g>\""</string>
     <string name="bt_toast_6" msgid="1855266596936622458">"Envoi du fichier à \"<xliff:g id="RECIPIENT">%1$s</xliff:g>\" interrompu"</string>
-    <string name="bt_sm_2_1_nosdcard" msgid="1791835163301501637">"Espace insuffisant sur la mémoire de stockage USB pour l\'enregistrement du fichier de « <xliff:g id="SENDER">%1$s</xliff:g> »"</string>
-    <string name="bt_sm_2_1_default" msgid="9115512207909504071">"Espace insuffisant sur la carte SD pour l\'enregistrement du fichier de « <xliff:g id="SENDER">%1$s</xliff:g> »"</string>
+    <string name="bt_sm_2_1_nosdcard" msgid="5354343190503952837">"Espace insuffisant sur la mémoire de stockage USB pour l\'enregistrement du fichier."</string>
+    <string name="bt_sm_2_1_default" msgid="2497541206648973852">"Espace insuffisant sur la carte SD pour l\'enregistrement du fichier."</string>
     <string name="bt_sm_2_2" msgid="2965243265852680543">"Espace requis : <xliff:g id="SIZE">%1$s</xliff:g>"</string>
     <string name="ErrorTooManyRequests" msgid="8578277541472944529">"Trop de requêtes sont en cours de traitement. Veuillez réessayer plus tard."</string>
     <string name="status_pending" msgid="2503691772030877944">"Le transfert de fichier n\'a pas encore commencé."</string>
diff --git a/res/values-fr/strings.xml b/res/values-fr/strings.xml
index e7a4ee5..52d02a4 100644
--- a/res/values-fr/strings.xml
+++ b/res/values-fr/strings.xml
@@ -18,8 +18,8 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="permlab_bluetoothShareManager" msgid="311492132450338925">"Accéder au gestionnaire de téléchargement"</string>
     <string name="permdesc_bluetoothShareManager" msgid="8930572979123190223">"Permet à l\'application d\'accéder au gestionnaire BluetoothShare et de l\'utiliser pour le transfert de fichiers."</string>
-    <string name="permlab_bluetoothWhitelist" msgid="7091552898592306386">"Ajouter des appareils Bluetooth à la liste blanche"</string>
-    <string name="permdesc_bluetoothWhitelist" msgid="5494513855192170109">"Permet à l\'application d\'ajouter temporairement un appareil Bluetooth à la liste blanche. Ainsi, celui-ci peut envoyer des fichiers à cet appareil sans que la confirmation de l\'utilisateur ne soit nécessaire."</string>
+    <string name="permlab_bluetoothAcceptlist" msgid="2647575807648622053">"Ajouter des appareils Bluetooth à la liste d\'autorisation"</string>
+    <string name="permdesc_bluetoothAcceptlist" msgid="2406423665674766442">"Permet à l\'application d\'ajouter temporairement un appareil Bluetooth à la liste d\'autorisation. Ainsi, celui-ci peut envoyer des fichiers à cet appareil sans que la confirmation de l\'utilisateur ne soit nécessaire."</string>
     <string name="bt_share_picker_label" msgid="6268100924487046932">"Bluetooth"</string>
     <string name="unknown_device" msgid="9221903979877041009">"Périphérique inconnu"</string>
     <string name="unknownNumber" msgid="4994750948072751566">"Inconnu"</string>
@@ -37,7 +37,7 @@
     <string name="incoming_file_confirm_timeout_ok" msgid="1414676773249857278">"OK"</string>
     <string name="incoming_file_confirm_timeout_content" msgid="172779756093975981">"Expiration du délai de réception du fichier de \"<xliff:g id="SENDER">%1$s</xliff:g>\""</string>
     <string name="incoming_file_confirm_Notification_title" msgid="5573329005298936903">"Fichier entrant"</string>
-    <string name="incoming_file_confirm_Notification_content" msgid="3359694069319644738">"<xliff:g id="SENDER">%1$s</xliff:g> est prêt à envoyer le fichier \"<xliff:g id="FILE">%2$s</xliff:g>\"."</string>
+    <string name="incoming_file_confirm_Notification_content" msgid="6237487486781004992">"<xliff:g id="SENDER">%1$s</xliff:g> est prêt à envoyer un fichier : <xliff:g id="FILE">%2$s</xliff:g>"</string>
     <string name="notification_receiving" msgid="4674648179652543984">"Partage Bluetooth : réception de <xliff:g id="FILE">%1$s</xliff:g>"</string>
     <string name="notification_received" msgid="3324588019186687985">"Partage Bluetooth : <xliff:g id="FILE">%1$s</xliff:g> reçu"</string>
     <string name="notification_received_fail" msgid="3619350997285714746">"Partage Bluetooth : fichier <xliff:g id="FILE">%1$s</xliff:g> non reçu"</string>
@@ -83,8 +83,8 @@
     <string name="bt_toast_4" msgid="4678812947604395649">"Envoi du fichier à \"<xliff:g id="RECIPIENT">%1$s</xliff:g>\""</string>
     <string name="bt_toast_5" msgid="2846870992823019494">"Envoi de <xliff:g id="NUMBER">%1$s</xliff:g> fichiers à \"<xliff:g id="RECIPIENT">%2$s</xliff:g>\""</string>
     <string name="bt_toast_6" msgid="1855266596936622458">"Envoi du fichier à \"<xliff:g id="RECIPIENT">%1$s</xliff:g>\" interrompu"</string>
-    <string name="bt_sm_2_1_nosdcard" msgid="1791835163301501637">"Espace insuffisant sur la mémoire de stockage USB pour l\'enregistrement du fichier de \"<xliff:g id="SENDER">%1$s</xliff:g>\""</string>
-    <string name="bt_sm_2_1_default" msgid="9115512207909504071">"Espace insuffisant sur la carte SD pour l\'enregistrement du fichier de \"<xliff:g id="SENDER">%1$s</xliff:g>\""</string>
+    <string name="bt_sm_2_1_nosdcard" msgid="5354343190503952837">"Espace insuffisant sur la mémoire de stockage USB pour enregistrer le fichier."</string>
+    <string name="bt_sm_2_1_default" msgid="2497541206648973852">"Espace insuffisant sur la carte SD pour enregistrer le fichier."</string>
     <string name="bt_sm_2_2" msgid="2965243265852680543">"Espace nécessaire : <xliff:g id="SIZE">%1$s</xliff:g>"</string>
     <string name="ErrorTooManyRequests" msgid="8578277541472944529">"Trop de requêtes sont en cours de traitement. Veuillez réessayer ultérieurement."</string>
     <string name="status_pending" msgid="2503691772030877944">"Le transfert de fichier n\'a pas encore commencé."</string>
diff --git a/res/values-gl/strings.xml b/res/values-gl/strings.xml
index 5f16e5e..e569734 100644
--- a/res/values-gl/strings.xml
+++ b/res/values-gl/strings.xml
@@ -18,8 +18,8 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="permlab_bluetoothShareManager" msgid="311492132450338925">"Acceso ao administrador de descargas."</string>
     <string name="permdesc_bluetoothShareManager" msgid="8930572979123190223">"Permite á aplicación acceder ao administrador BluetoothShare e usalo para transferir ficheiros."</string>
-    <string name="permlab_bluetoothWhitelist" msgid="7091552898592306386">"Acceso ao dispositivo Bluetooth da lista branca."</string>
-    <string name="permdesc_bluetoothWhitelist" msgid="5494513855192170109">"Permite á aplicación engadir un dispositivo Bluetooth a unha lista branca temporalmente, de forma que o dispositivo poida enviar ficheiros a este dispositivo sen confirmación do usuario."</string>
+    <string name="permlab_bluetoothAcceptlist" msgid="2647575807648622053">"Permitir acceso ao dispositivo mediante Bluetooth."</string>
+    <string name="permdesc_bluetoothAcceptlist" msgid="2406423665674766442">"Permite á aplicación autorizar un dispositivo Bluetooth para que poida enviarlle ficheiros a este dispositivo sen confirmación do usuario."</string>
     <string name="bt_share_picker_label" msgid="6268100924487046932">"Bluetooth"</string>
     <string name="unknown_device" msgid="9221903979877041009">"Dispositivo descoñecido"</string>
     <string name="unknownNumber" msgid="4994750948072751566">"Descoñecido"</string>
@@ -37,7 +37,7 @@
     <string name="incoming_file_confirm_timeout_ok" msgid="1414676773249857278">"Aceptar"</string>
     <string name="incoming_file_confirm_timeout_content" msgid="172779756093975981">"Superouse o tempo de espera mentres se aceptaba un ficheiro entrante de \"<xliff:g id="SENDER">%1$s</xliff:g>\""</string>
     <string name="incoming_file_confirm_Notification_title" msgid="5573329005298936903">"Ficheiro entrante"</string>
-    <string name="incoming_file_confirm_Notification_content" msgid="3359694069319644738">"<xliff:g id="SENDER">%1$s</xliff:g> está listo para enviar <xliff:g id="FILE">%2$s</xliff:g>"</string>
+    <string name="incoming_file_confirm_Notification_content" msgid="6237487486781004992">"<xliff:g id="SENDER">%1$s</xliff:g> ten todo listo para enviar un ficheiro: <xliff:g id="FILE">%2$s</xliff:g>"</string>
     <string name="notification_receiving" msgid="4674648179652543984">"Uso compartido por Bluetooth: recibindo <xliff:g id="FILE">%1$s</xliff:g>"</string>
     <string name="notification_received" msgid="3324588019186687985">"Uso compartido por Bluetooth: <xliff:g id="FILE">%1$s</xliff:g> recibido"</string>
     <string name="notification_received_fail" msgid="3619350997285714746">"Uso compartido por Bluetooth: ficheiro <xliff:g id="FILE">%1$s</xliff:g> non recibido"</string>
@@ -83,8 +83,8 @@
     <string name="bt_toast_4" msgid="4678812947604395649">"Enviando ficheiro a \"<xliff:g id="RECIPIENT">%1$s</xliff:g>\""</string>
     <string name="bt_toast_5" msgid="2846870992823019494">"Enviando <xliff:g id="NUMBER">%1$s</xliff:g> ficheiros a \"<xliff:g id="RECIPIENT">%2$s</xliff:g>\""</string>
     <string name="bt_toast_6" msgid="1855266596936622458">"Detívose o envío do ficheiro a \"<xliff:g id="RECIPIENT">%1$s</xliff:g>\""</string>
-    <string name="bt_sm_2_1_nosdcard" msgid="1791835163301501637">"Non hai espazo suficiente no almacenamento USB para gardar o ficheiro de \"<xliff:g id="SENDER">%1$s</xliff:g>\""</string>
-    <string name="bt_sm_2_1_default" msgid="9115512207909504071">"Non hai espazo suficiente na tarxeta SD para gardar o ficheiro de \"<xliff:g id="SENDER">%1$s</xliff:g>\""</string>
+    <string name="bt_sm_2_1_nosdcard" msgid="5354343190503952837">"Non hai espazo suficiente no almacenamento USB para gardar o ficheiro."</string>
+    <string name="bt_sm_2_1_default" msgid="2497541206648973852">"Non hai espazo suficiente na tarxeta SD para gardar o ficheiro."</string>
     <string name="bt_sm_2_2" msgid="2965243265852680543">"Espazo necesario: <xliff:g id="SIZE">%1$s</xliff:g>"</string>
     <string name="ErrorTooManyRequests" msgid="8578277541472944529">"Estanse procesando demasiadas solicitudes. Téntao de novo máis tarde."</string>
     <string name="status_pending" msgid="2503691772030877944">"Aínda non se iniciou a transferencia de ficheiros."</string>
diff --git a/res/values-gu/strings.xml b/res/values-gu/strings.xml
index 64568eb..4074744 100644
--- a/res/values-gu/strings.xml
+++ b/res/values-gu/strings.xml
@@ -18,8 +18,8 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="permlab_bluetoothShareManager" msgid="311492132450338925">"ઍક્સેસ ડાઉનલોડ મેનેજર."</string>
     <string name="permdesc_bluetoothShareManager" msgid="8930572979123190223">"એપ્લિકેશનને BluetoothShare મેનેજર અ‍ૅક્સેસ કરવાની અને ફાઇલો ટ્રાન્સફર કરવા માટે તેનો ઉપયોગ કરવાની મંજૂરી આપે છે."</string>
-    <string name="permlab_bluetoothWhitelist" msgid="7091552898592306386">"વ્હાઇટલિસ્ટ બ્લૂટૂથ ઉપકરણ ઍક્સેસ."</string>
-    <string name="permdesc_bluetoothWhitelist" msgid="5494513855192170109">"એપ્લિકેશનને અસ્થાયી ધોરણે બ્લૂટૂથ ઉપકરણને વ્હાઇટલિસ્ટ કરવાની મંજૂરી આપે છે, તે ઉપકરણને આ ઉપકરણ પર વપરાશકર્તા પુષ્ટિ વિના ફાઇલો મોકલવાની મંજૂરી આપીને."</string>
+    <string name="permlab_bluetoothAcceptlist" msgid="2647575807648622053">"વ્હાઇટલિસ્ટ બ્લૂટૂથ ડિવાઇસ ઍક્સેસ."</string>
+    <string name="permdesc_bluetoothAcceptlist" msgid="2406423665674766442">"ઍપને હંગામી રૂપે બ્લૂટૂથ ડિવાઇસને વ્હાઇટલિસ્ટ કરવાની મંજૂરી આપે છે, જેનાથી તે ડિવાઇસ આ ડિવાઇસ પર વપરાશકર્તાની ખાતરી વિના ફાઇલ મોકલી શકે છે."</string>
     <string name="bt_share_picker_label" msgid="6268100924487046932">"બ્લૂટૂથ"</string>
     <string name="unknown_device" msgid="9221903979877041009">"અજાણ્યું ઉપકરણ"</string>
     <string name="unknownNumber" msgid="4994750948072751566">"અજાણ્યો"</string>
@@ -37,7 +37,7 @@
     <string name="incoming_file_confirm_timeout_ok" msgid="1414676773249857278">"ઓકે"</string>
     <string name="incoming_file_confirm_timeout_content" msgid="172779756093975981">"\"<xliff:g id="SENDER">%1$s</xliff:g>\" તરફની એક આવનારી ફાઇલ સ્વીકારતી વખતે સમયસમાપ્તિ થઈ હતી"</string>
     <string name="incoming_file_confirm_Notification_title" msgid="5573329005298936903">"આવનારી ફાઇલ"</string>
-    <string name="incoming_file_confirm_Notification_content" msgid="3359694069319644738">"<xliff:g id="SENDER">%1$s</xliff:g>, <xliff:g id="FILE">%2$s</xliff:g> મોકલવા માટે તૈયાર છે"</string>
+    <string name="incoming_file_confirm_Notification_content" msgid="6237487486781004992">"<xliff:g id="SENDER">%1$s</xliff:g> આ ફાઇલ મોકલવા માટે તૈયાર છે: <xliff:g id="FILE">%2$s</xliff:g>"</string>
     <string name="notification_receiving" msgid="4674648179652543984">"બ્લૂટૂથ શેર: <xliff:g id="FILE">%1$s</xliff:g> પ્રાપ્ત કરી રહ્યું છે"</string>
     <string name="notification_received" msgid="3324588019186687985">"બ્લૂટૂથ શેર: <xliff:g id="FILE">%1$s</xliff:g> પ્રાપ્ત"</string>
     <string name="notification_received_fail" msgid="3619350997285714746">"બ્લૂટૂથ શેર: ફાઇલ <xliff:g id="FILE">%1$s</xliff:g> પ્રાપ્ત થઈ નથી"</string>
@@ -83,8 +83,8 @@
     <string name="bt_toast_4" msgid="4678812947604395649">"\"<xliff:g id="RECIPIENT">%1$s</xliff:g>\" પર ફાઇલ મોકલી રહ્યાં છે"</string>
     <string name="bt_toast_5" msgid="2846870992823019494">"<xliff:g id="NUMBER">%1$s</xliff:g> ફાઇલો \"<xliff:g id="RECIPIENT">%2$s</xliff:g>\" પર મોકલી રહ્યાં છે"</string>
     <string name="bt_toast_6" msgid="1855266596936622458">"\"<xliff:g id="RECIPIENT">%1$s</xliff:g>\" પર ફાઇલ મોકલવું બંધ કર્યું"</string>
-    <string name="bt_sm_2_1_nosdcard" msgid="1791835163301501637">"\"<xliff:g id="SENDER">%1$s</xliff:g>\"ની ફાઇલને USB સ્ટોરેજમાં સાચવવા માટે પૂરતી સ્પેસ નથી"</string>
-    <string name="bt_sm_2_1_default" msgid="9115512207909504071">"\"<xliff:g id="SENDER">%1$s</xliff:g>\"ની ફાઇલને SD કાર્ડમાં સાચવવા માટે પૂરતી સ્પેસ નથી"</string>
+    <string name="bt_sm_2_1_nosdcard" msgid="5354343190503952837">"ફાઇલને USB સ્ટોરેજમાં સાચવવા માટે પૂરતી સ્પેસ નથી."</string>
+    <string name="bt_sm_2_1_default" msgid="2497541206648973852">"ફાઇલને SD કાર્ડમાં સાચવવા માટે પૂરતી સ્પેસ નથી."</string>
     <string name="bt_sm_2_2" msgid="2965243265852680543">"સ્થાન જરૂરી: <xliff:g id="SIZE">%1$s</xliff:g>"</string>
     <string name="ErrorTooManyRequests" msgid="8578277541472944529">"ઘણી બધી વિનંતીઓ પર પ્રક્રિયા કરવામાં આવી રહી છે. પછીથી ફરી પ્રયાસ કરો."</string>
     <string name="status_pending" msgid="2503691772030877944">"ફાઇલ સ્થાનાંતરણ હજી પ્રારંભ થયું નથી."</string>
diff --git a/res/values-hi/strings.xml b/res/values-hi/strings.xml
index b1b18be..3e57a23 100644
--- a/res/values-hi/strings.xml
+++ b/res/values-hi/strings.xml
@@ -18,8 +18,8 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="permlab_bluetoothShareManager" msgid="311492132450338925">"डाउनलोड मैनेजर में पहुंच पाएं."</string>
     <string name="permdesc_bluetoothShareManager" msgid="8930572979123190223">"ऐप्लिकेशन को BluetoothShare मैनेजर के इस्तेमाल की मंज़ूरी देता है और फ़ाइलों को ट्रांसफ़र करने के लिए उसका उपयोग करने देता है."</string>
-    <string name="permlab_bluetoothWhitelist" msgid="7091552898592306386">"श्वेतसूची bluetooth डिवाइस पहुंच."</string>
-    <string name="permdesc_bluetoothWhitelist" msgid="5494513855192170109">"ऐप को, ब्लूटूथ डिवाइस को उपयोगकर्ता की पुष्टि के बिना इस डिवाइस पर फ़ाइल भेजने की‍ अनुमति देकर, कुछ देर के लिए अनुमति देता है"</string>
+    <string name="permlab_bluetoothAcceptlist" msgid="2647575807648622053">"ब्लूटूथ डिवाइस ऐक्सेस को व्हाइटलिस्ट में डालें."</string>
+    <string name="permdesc_bluetoothAcceptlist" msgid="2406423665674766442">"ऐप्लिकेशन को, यह कुछ देर के लिए किसी ब्लूटूथ डिवाइस को व्हाइटलिस्ट में डालने की सुविधा देता है. इससे उपयोगकर्ता की पुष्टि के बिना, उस डिवाइस से इस डिवाइस में फ़ाइलें भेजी जा सकती हैं."</string>
     <string name="bt_share_picker_label" msgid="6268100924487046932">"ब्लूटूथ"</string>
     <string name="unknown_device" msgid="9221903979877041009">"अज्ञात डिवाइस"</string>
     <string name="unknownNumber" msgid="4994750948072751566">"अज्ञात"</string>
@@ -37,7 +37,7 @@
     <string name="incoming_file_confirm_timeout_ok" msgid="1414676773249857278">"ठीक है"</string>
     <string name="incoming_file_confirm_timeout_content" msgid="172779756093975981">"\"<xliff:g id="SENDER">%1$s</xliff:g>\" से आने वाली फ़ाइल स्वीकार करते हुए टाइम आउट हो गया."</string>
     <string name="incoming_file_confirm_Notification_title" msgid="5573329005298936903">"आवक फ़ाइल"</string>
-    <string name="incoming_file_confirm_Notification_content" msgid="3359694069319644738">"<xliff:g id="SENDER">%1$s</xliff:g> <xliff:g id="FILE">%2$s</xliff:g> भेजने के लिए तैयार है"</string>
+    <string name="incoming_file_confirm_Notification_content" msgid="6237487486781004992">"<xliff:g id="SENDER">%1$s</xliff:g>, इस फ़ाइल को भेजने के लिए तैयार है: <xliff:g id="FILE">%2$s</xliff:g>"</string>
     <string name="notification_receiving" msgid="4674648179652543984">"ब्लूटूथ शेयर: <xliff:g id="FILE">%1$s</xliff:g> पा रहा है"</string>
     <string name="notification_received" msgid="3324588019186687985">"ब्लूटूथ शेयर: <xliff:g id="FILE">%1$s</xliff:g> पाई गई"</string>
     <string name="notification_received_fail" msgid="3619350997285714746">"ब्लूटूथ शेयर: फ़ाइल <xliff:g id="FILE">%1$s</xliff:g> नहीं मिली"</string>
@@ -83,8 +83,8 @@
     <string name="bt_toast_4" msgid="4678812947604395649">"\"<xliff:g id="RECIPIENT">%1$s</xliff:g>\" को फ़ाइल भेज रहा है"</string>
     <string name="bt_toast_5" msgid="2846870992823019494">"\"<xliff:g id="RECIPIENT">%2$s</xliff:g>\" को <xliff:g id="NUMBER">%1$s</xliff:g> फ़ाइलें भेज रहा है"</string>
     <string name="bt_toast_6" msgid="1855266596936622458">"\"<xliff:g id="RECIPIENT">%1$s</xliff:g>\" को फ़ाइल भेजना रोका गया"</string>
-    <string name="bt_sm_2_1_nosdcard" msgid="1791835163301501637">"\"<xliff:g id="SENDER">%1$s</xliff:g>\" की फ़ाइल सेव करने के लिए USB स्टोरेज में ज़रुरत के मुताबिक जगह नहीं है"</string>
-    <string name="bt_sm_2_1_default" msgid="9115512207909504071">"\"<xliff:g id="SENDER">%1$s</xliff:g>\" की फ़ाइल सेव करने के लिए SD कार्ड पर ज़रुरत के मुताबिक जगह नहीं है"</string>
+    <string name="bt_sm_2_1_nosdcard" msgid="5354343190503952837">"इस फ़ाइल को सेव करने के लिए यूएसबी मेमोरी में ज़रूरत के हिसाब से जगह खाली नहीं है."</string>
+    <string name="bt_sm_2_1_default" msgid="2497541206648973852">"इस फ़ाइल को सेव करने के लिए एसडी कार्ड में ज़रूरत के हिसाब से जगह खाली नहीं है."</string>
     <string name="bt_sm_2_2" msgid="2965243265852680543">"जगह चाहिए: <xliff:g id="SIZE">%1$s</xliff:g>"</string>
     <string name="ErrorTooManyRequests" msgid="8578277541472944529">"बहुत सारे अनुरोधों पर कार्रवाई चल रही है. बाद में फिर से प्रयास करें."</string>
     <string name="status_pending" msgid="2503691772030877944">"फ़ाइल स्थानांतरण अभी तक प्रारंभ नहीं हुआ."</string>
diff --git a/res/values-hr/strings.xml b/res/values-hr/strings.xml
index 77b38e6..b6d1b00 100644
--- a/res/values-hr/strings.xml
+++ b/res/values-hr/strings.xml
@@ -18,8 +18,8 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="permlab_bluetoothShareManager" msgid="311492132450338925">"Pristupite upravitelju za preuzimanje."</string>
     <string name="permdesc_bluetoothShareManager" msgid="8930572979123190223">"Aplikaciji omogućuje pristup upravitelju za BluetoothShare i njegovu upotrebu za prijenos datoteka."</string>
-    <string name="permlab_bluetoothWhitelist" msgid="7091552898592306386">"Stavi pristup Bluetooth uređaja na popis dopuštenih pristupa."</string>
-    <string name="permdesc_bluetoothWhitelist" msgid="5494513855192170109">"Omogućuje aplikaciji privremeno stavljanje Bluetooth uređaja na popis dopuštenih uređaja, čime mu omogućuje da šalje datoteke na ovaj uređaj bez potvrde korisnika."</string>
+    <string name="permlab_bluetoothAcceptlist" msgid="2647575807648622053">"Stavi pristup Bluetooth uređaja na popis prihvaćenih."</string>
+    <string name="permdesc_bluetoothAcceptlist" msgid="2406423665674766442">"Omogućuje aplikaciji privremeno stavljanje Bluetooth uređaja na popis prihvaćenih, čime mu omogućuje slanje datoteka na ovaj uređaj bez korisnikove potvrde."</string>
     <string name="bt_share_picker_label" msgid="6268100924487046932">"Bluetooth"</string>
     <string name="unknown_device" msgid="9221903979877041009">"Nepoznati uređaj"</string>
     <string name="unknownNumber" msgid="4994750948072751566">"Nepoznato"</string>
@@ -37,7 +37,7 @@
     <string name="incoming_file_confirm_timeout_ok" msgid="1414676773249857278">"U redu"</string>
     <string name="incoming_file_confirm_timeout_content" msgid="172779756093975981">"Za vrijeme primanja datoteke koju šalje \"<xliff:g id="SENDER">%1$s</xliff:g>\" došlo je do privremenog prekida"</string>
     <string name="incoming_file_confirm_Notification_title" msgid="5573329005298936903">"Dolazna datoteka"</string>
-    <string name="incoming_file_confirm_Notification_content" msgid="3359694069319644738">"<xliff:g id="SENDER">%1$s</xliff:g> može poslati datoteku <xliff:g id="FILE">%2$s</xliff:g>"</string>
+    <string name="incoming_file_confirm_Notification_content" msgid="6237487486781004992">"<xliff:g id="SENDER">%1$s</xliff:g> sada može poslati datoteku: <xliff:g id="FILE">%2$s</xliff:g>"</string>
     <string name="notification_receiving" msgid="4674648179652543984">"Dijeljenje Bluetoothom: Primanje datoteke <xliff:g id="FILE">%1$s</xliff:g>"</string>
     <string name="notification_received" msgid="3324588019186687985">"Dijeljenje Bluetoothom: Primljena datoteka <xliff:g id="FILE">%1$s</xliff:g>"</string>
     <string name="notification_received_fail" msgid="3619350997285714746">"Dijeljenje Bluetoothom: Datoteka <xliff:g id="FILE">%1$s</xliff:g> nije primljena"</string>
@@ -83,8 +83,8 @@
     <string name="bt_toast_4" msgid="4678812947604395649">"Slanje datoteke primatelju \"<xliff:g id="RECIPIENT">%1$s</xliff:g>\""</string>
     <string name="bt_toast_5" msgid="2846870992823019494">"Sljedeći broj datoteka: <xliff:g id="NUMBER">%1$s</xliff:g> šalje se primatelju \"<xliff:g id="RECIPIENT">%2$s</xliff:g>\""</string>
     <string name="bt_toast_6" msgid="1855266596936622458">"Zaustavljeno slanje datoteke primatelju \"<xliff:g id="RECIPIENT">%1$s</xliff:g>\""</string>
-    <string name="bt_sm_2_1_nosdcard" msgid="1791835163301501637">"Na USB pohrani nema dovoljno prostora za spremanje datoteke koju šalje \"<xliff:g id="SENDER">%1$s</xliff:g>\""</string>
-    <string name="bt_sm_2_1_default" msgid="9115512207909504071">"Na SD kartici nema dovoljno prostora za spremanje datoteke koju šalje \"<xliff:g id="SENDER">%1$s</xliff:g>\""</string>
+    <string name="bt_sm_2_1_nosdcard" msgid="5354343190503952837">"U USB pohrani nema dovoljno prostora za spremanje datoteke."</string>
+    <string name="bt_sm_2_1_default" msgid="2497541206648973852">"Na SD kartici nema dovoljno prostora za spremanje datoteke."</string>
     <string name="bt_sm_2_2" msgid="2965243265852680543">"Potrebno prostora: <xliff:g id="SIZE">%1$s</xliff:g>"</string>
     <string name="ErrorTooManyRequests" msgid="8578277541472944529">"U obradi je previše zahtjeva. Pokušajte ponovo kasnije."</string>
     <string name="status_pending" msgid="2503691772030877944">"Prijenos datoteke još nije započeo."</string>
diff --git a/res/values-hu/strings.xml b/res/values-hu/strings.xml
index 1806389..de0d38b 100644
--- a/res/values-hu/strings.xml
+++ b/res/values-hu/strings.xml
@@ -18,8 +18,8 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="permlab_bluetoothShareManager" msgid="311492132450338925">"Hozzáférés a letöltéskezelőhöz."</string>
     <string name="permdesc_bluetoothShareManager" msgid="8930572979123190223">"Lehetővé teszi az alkalmazás számára a BluetoothShare kezelő elérését és használatát fájlátvitelre."</string>
-    <string name="permlab_bluetoothWhitelist" msgid="7091552898592306386">"Bluetooth-eszköz hozzáférésének engedélyezése."</string>
-    <string name="permdesc_bluetoothWhitelist" msgid="5494513855192170109">"Lehetővé teszi az alkalmazás számára egy Bluetooth-eszköz ideiglenesen engedélyezését, amely így a felhasználó jóváhagyása nélkül küldhet fájlokat erre a készülékre."</string>
+    <string name="permlab_bluetoothAcceptlist" msgid="2647575807648622053">"Bluetooth-eszköz hozzáférésének engedélyezése"</string>
+    <string name="permdesc_bluetoothAcceptlist" msgid="2406423665674766442">"Lehetővé teszi az alkalmazás számára Bluetooth-eszközök ideiglenes engedélyezését, amelyek így a felhasználó jóváhagyása nélkül küldhetnek fájlokat erre az eszközre."</string>
     <string name="bt_share_picker_label" msgid="6268100924487046932">"Bluetooth"</string>
     <string name="unknown_device" msgid="9221903979877041009">"Ismeretlen eszköz"</string>
     <string name="unknownNumber" msgid="4994750948072751566">"Ismeretlen"</string>
@@ -37,7 +37,7 @@
     <string name="incoming_file_confirm_timeout_ok" msgid="1414676773249857278">"OK"</string>
     <string name="incoming_file_confirm_timeout_content" msgid="172779756093975981">"Időtúllépés történt \"<xliff:g id="SENDER">%1$s</xliff:g>\" feladótól érkező fájl fogadása során"</string>
     <string name="incoming_file_confirm_Notification_title" msgid="5573329005298936903">"Beérkező fájl"</string>
-    <string name="incoming_file_confirm_Notification_content" msgid="3359694069319644738">"<xliff:g id="SENDER">%1$s</xliff:g> készen áll a következő küldésére: <xliff:g id="FILE">%2$s</xliff:g>"</string>
+    <string name="incoming_file_confirm_Notification_content" msgid="6237487486781004992">"<xliff:g id="SENDER">%1$s</xliff:g> fájlt szeretne küldeni: <xliff:g id="FILE">%2$s</xliff:g>"</string>
     <string name="notification_receiving" msgid="4674648179652543984">"Bluetooth megosztás: <xliff:g id="FILE">%1$s</xliff:g> fogadása"</string>
     <string name="notification_received" msgid="3324588019186687985">"Bluetooth megosztás: <xliff:g id="FILE">%1$s</xliff:g> fogadva"</string>
     <string name="notification_received_fail" msgid="3619350997285714746">"Bluetooth megosztás: <xliff:g id="FILE">%1$s</xliff:g> fájl fogadása nem sikerült"</string>
@@ -83,8 +83,8 @@
     <string name="bt_toast_4" msgid="4678812947604395649">"Fájl küldése a következőnek: \"<xliff:g id="RECIPIENT">%1$s</xliff:g>\""</string>
     <string name="bt_toast_5" msgid="2846870992823019494">"<xliff:g id="NUMBER">%1$s</xliff:g> fájl küldése a következőnek: \"<xliff:g id="RECIPIENT">%2$s</xliff:g>\""</string>
     <string name="bt_toast_6" msgid="1855266596936622458">"A fájl küldése \"<xliff:g id="RECIPIENT">%1$s</xliff:g>\" címzettnek leállítva"</string>
-    <string name="bt_sm_2_1_nosdcard" msgid="1791835163301501637">"Nincs elég hely az USB-háttértárban a(z) „<xliff:g id="SENDER">%1$s</xliff:g>” által küldött fájl mentéséhez"</string>
-    <string name="bt_sm_2_1_default" msgid="9115512207909504071">"Nincs elég hely az SD-kártyán a(z) „<xliff:g id="SENDER">%1$s</xliff:g>” által küldött fájl mentéséhez"</string>
+    <string name="bt_sm_2_1_nosdcard" msgid="5354343190503952837">"Nincs elég hely az USB-háttértáron a fájl mentéséhez."</string>
+    <string name="bt_sm_2_1_default" msgid="2497541206648973852">"Nincs elég hely az SD-kártyán a fájl mentéséhez."</string>
     <string name="bt_sm_2_2" msgid="2965243265852680543">"Szükséges tárterület: <xliff:g id="SIZE">%1$s</xliff:g>"</string>
     <string name="ErrorTooManyRequests" msgid="8578277541472944529">"Túl sok kérés áll feldolgozás alatt. Próbálja újra később."</string>
     <string name="status_pending" msgid="2503691772030877944">"A fájlátvitel még nem kezdődött el."</string>
diff --git a/res/values-hy/strings.xml b/res/values-hy/strings.xml
index 57a6214..5efbbae 100644
--- a/res/values-hy/strings.xml
+++ b/res/values-hy/strings.xml
@@ -18,8 +18,8 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="permlab_bluetoothShareManager" msgid="311492132450338925">"Օգտվել ներբեռնման կառավարչից:"</string>
     <string name="permdesc_bluetoothShareManager" msgid="8930572979123190223">"Թույլ է տալիս, որ ծրագիրը մատչի BluetoothShare կառավարչին և այն օգտագործի ֆայլեր փոխանցելու համար:"</string>
-    <string name="permlab_bluetoothWhitelist" msgid="7091552898592306386">"Մուտք bluetooth սարքի ցուցակին:"</string>
-    <string name="permdesc_bluetoothWhitelist" msgid="5494513855192170109">"Թույլ է տալիս, որ ծրագիրը ժամանակավորապես մաքրի Bluetooth սարքի ցուցակը, ինչը թույլ է տալիս, որ այդ սարքը կարողանա ֆայլեր ուղարկել տվյալ սարքին առանց օգտատիրոջ հաստատման:"</string>
+    <string name="permlab_bluetoothAcceptlist" msgid="2647575807648622053">"Թույլատրված Bluetooth սարքի հասանելիություն։"</string>
+    <string name="permdesc_bluetoothAcceptlist" msgid="2406423665674766442">"Թույլատրում է հավելվածին ժամանակավորապես ավելացնել Bluetooth սարքը սպիտակ ցուցակում, ինչը հնարավորություն է տալիս, որ Bluetooth սարքը ֆայլեր ուղարկի այս սարքին՝ առանց օգտատիրոջ հաստատման։"</string>
     <string name="bt_share_picker_label" msgid="6268100924487046932">"Bluetooth"</string>
     <string name="unknown_device" msgid="9221903979877041009">"Անհայտ սարք"</string>
     <string name="unknownNumber" msgid="4994750948072751566">"Անհայտ"</string>
@@ -37,7 +37,7 @@
     <string name="incoming_file_confirm_timeout_ok" msgid="1414676773249857278">"Եղավ"</string>
     <string name="incoming_file_confirm_timeout_content" msgid="172779756093975981">"«<xliff:g id="SENDER">%1$s</xliff:g>»-ից մուտքային ֆայլի ընդունման ժամանակը սպառվեց"</string>
     <string name="incoming_file_confirm_Notification_title" msgid="5573329005298936903">"Մուտքային ֆայլ"</string>
-    <string name="incoming_file_confirm_Notification_content" msgid="3359694069319644738">"<xliff:g id="SENDER">%1$s</xliff:g>-ը պատրաստ է ուղարկելու <xliff:g id="FILE">%2$s</xliff:g> ֆայլը"</string>
+    <string name="incoming_file_confirm_Notification_content" msgid="6237487486781004992">"«<xliff:g id="SENDER">%1$s</xliff:g>» սարքը պատրաստ է ուղարկել այս ֆայլը՝ <xliff:g id="FILE">%2$s</xliff:g>"</string>
     <string name="notification_receiving" msgid="4674648179652543984">"Bluetooth համօգտագործում՝ <xliff:g id="FILE">%1$s</xliff:g>-ը ստացվում է"</string>
     <string name="notification_received" msgid="3324588019186687985">"Bluetooth համօգտագործում՝ ստացվեց <xliff:g id="FILE">%1$s</xliff:g>-ը"</string>
     <string name="notification_received_fail" msgid="3619350997285714746">"Bluetooth համօգտագործում՝ <xliff:g id="FILE">%1$s</xliff:g> ֆայլը չի ստացվել"</string>
@@ -83,8 +83,8 @@
     <string name="bt_toast_4" msgid="4678812947604395649">"Ֆայլն ուղարկվում է «<xliff:g id="RECIPIENT">%1$s</xliff:g>»-ին"</string>
     <string name="bt_toast_5" msgid="2846870992823019494">"<xliff:g id="NUMBER">%1$s</xliff:g> ֆայլեր ուղարկվում են «<xliff:g id="RECIPIENT">%2$s</xliff:g>»-ին"</string>
     <string name="bt_toast_6" msgid="1855266596936622458">"Դադարեցվեց ֆայլի ուղարկումը «<xliff:g id="RECIPIENT">%1$s</xliff:g>»-ին"</string>
-    <string name="bt_sm_2_1_nosdcard" msgid="1791835163301501637">"USB կրիչի վրա բավարար տեղ չկա <xliff:g id="SENDER">%1$s</xliff:g>-ի ֆայլը պահելու համար"</string>
-    <string name="bt_sm_2_1_default" msgid="9115512207909504071">"SD քարտում բավարար տեղ չկա <xliff:g id="SENDER">%1$s</xliff:g>-ի ֆայլը պահելու համար"</string>
+    <string name="bt_sm_2_1_nosdcard" msgid="5354343190503952837">"USB կրիչում բավարար տարածք չկա ֆայլը պահելու համար"</string>
+    <string name="bt_sm_2_1_default" msgid="2497541206648973852">"SD քարտում բավարար տարածք չկա ֆայլը պահելու համար"</string>
     <string name="bt_sm_2_2" msgid="2965243265852680543">"Անհրաժեշտ տեղը՝ <xliff:g id="SIZE">%1$s</xliff:g>"</string>
     <string name="ErrorTooManyRequests" msgid="8578277541472944529">"Չափից շատ հարցումներ են մշակվում: Կրկին փորձեք ավելի ուշ:"</string>
     <string name="status_pending" msgid="2503691772030877944">"Ֆայլի փոխանցումը դեռ չի մեկնարկել:"</string>
diff --git a/res/values-in/strings.xml b/res/values-in/strings.xml
index 315ec25..7223f33 100644
--- a/res/values-in/strings.xml
+++ b/res/values-in/strings.xml
@@ -18,8 +18,8 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="permlab_bluetoothShareManager" msgid="311492132450338925">"Akses pengelola download."</string>
     <string name="permdesc_bluetoothShareManager" msgid="8930572979123190223">"Izinkan apl mengakses pengelola BluetoothShare dan menggunakannya untuk mentransfer file."</string>
-    <string name="permlab_bluetoothWhitelist" msgid="7091552898592306386">"Memasukkan akses perangkat bluetooth ke dalam daftar putih."</string>
-    <string name="permdesc_bluetoothWhitelist" msgid="5494513855192170109">"Mengizinkan aplikasi memasukkan perangkat Bluetooth ke dalam daftar putih untuk sementara, yang memungkinkan perangkat tersebut mengirimkan file ke perangkat ini tanpa konfirmasi pengguna."</string>
+    <string name="permlab_bluetoothAcceptlist" msgid="2647575807648622053">"Memasukkan akses perangkat Bluetooth ke daftar yang diizinkan."</string>
+    <string name="permdesc_bluetoothAcceptlist" msgid="2406423665674766442">"Mengizinkan aplikasi memasukkan perangkat Bluetooth ke daftar yang diizinkan untuk sementara waktu, yang memungkinkan perangkat tersebut mengirimkan file ke perangkat ini tanpa konfirmasi pengguna."</string>
     <string name="bt_share_picker_label" msgid="6268100924487046932">"Bluetooth"</string>
     <string name="unknown_device" msgid="9221903979877041009">"Perangkat tidak diketahui"</string>
     <string name="unknownNumber" msgid="4994750948072751566">"Tidak diketahui"</string>
@@ -37,7 +37,7 @@
     <string name="incoming_file_confirm_timeout_ok" msgid="1414676773249857278">"Oke"</string>
     <string name="incoming_file_confirm_timeout_content" msgid="172779756093975981">"Terjadi waktu tunggu saat menerima file masuk dari \"<xliff:g id="SENDER">%1$s</xliff:g>\""</string>
     <string name="incoming_file_confirm_Notification_title" msgid="5573329005298936903">"File masuk"</string>
-    <string name="incoming_file_confirm_Notification_content" msgid="3359694069319644738">"<xliff:g id="SENDER">%1$s</xliff:g> siap mengirim <xliff:g id="FILE">%2$s</xliff:g>"</string>
+    <string name="incoming_file_confirm_Notification_content" msgid="6237487486781004992">"<xliff:g id="SENDER">%1$s</xliff:g> siap mengirim file: <xliff:g id="FILE">%2$s</xliff:g>"</string>
     <string name="notification_receiving" msgid="4674648179652543984">"Berbagi Bluetooth: Menerima <xliff:g id="FILE">%1$s</xliff:g>"</string>
     <string name="notification_received" msgid="3324588019186687985">"Berbagi Bluetooth: Telah menerima <xliff:g id="FILE">%1$s</xliff:g>"</string>
     <string name="notification_received_fail" msgid="3619350997285714746">"Berbagi Bluetooth: File <xliff:g id="FILE">%1$s</xliff:g> tidak diterima"</string>
@@ -83,8 +83,8 @@
     <string name="bt_toast_4" msgid="4678812947604395649">"Mengirim file ke \"<xliff:g id="RECIPIENT">%1$s</xliff:g>\""</string>
     <string name="bt_toast_5" msgid="2846870992823019494">"Mengirim <xliff:g id="NUMBER">%1$s</xliff:g> file ke \"<xliff:g id="RECIPIENT">%2$s</xliff:g>\""</string>
     <string name="bt_toast_6" msgid="1855266596936622458">"Berhenti mengirim file ke \"<xliff:g id="RECIPIENT">%1$s</xliff:g>\""</string>
-    <string name="bt_sm_2_1_nosdcard" msgid="1791835163301501637">"Ruang pada penyimpanan USB tidak cukup untuk menyimpan file dari \"<xliff:g id="SENDER">%1$s</xliff:g>\""</string>
-    <string name="bt_sm_2_1_default" msgid="9115512207909504071">"Ruang pada kartu SD tidak cukup untuk menyimpan file dari \"<xliff:g id="SENDER">%1$s</xliff:g>\""</string>
+    <string name="bt_sm_2_1_nosdcard" msgid="5354343190503952837">"Tidak ada ruang yang cukup di penyimpanan USB untuk menyimpan file."</string>
+    <string name="bt_sm_2_1_default" msgid="2497541206648973852">"Tidak ada ruang yang cukup di kartu SD untuk menyimpan file."</string>
     <string name="bt_sm_2_2" msgid="2965243265852680543">"Ruang yang diperlukan: <xliff:g id="SIZE">%1$s</xliff:g>"</string>
     <string name="ErrorTooManyRequests" msgid="8578277541472944529">"Terlalu banyak permintaan yang diproses. Coba lagi nanti."</string>
     <string name="status_pending" msgid="2503691772030877944">"Transfer file belum dimulai."</string>
diff --git a/res/values-is/strings.xml b/res/values-is/strings.xml
index 3e672c0..b82e107 100644
--- a/res/values-is/strings.xml
+++ b/res/values-is/strings.xml
@@ -18,8 +18,8 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="permlab_bluetoothShareManager" msgid="311492132450338925">"Aðgangur að niðurhalsstjórnun."</string>
     <string name="permdesc_bluetoothShareManager" msgid="8930572979123190223">"Leyfir forriti að fá aðgang að BluetoothShare-stjórnun og nota hana til að flytja skrár."</string>
-    <string name="permlab_bluetoothWhitelist" msgid="7091552898592306386">"Aðgangur að lista yfir leyfð Bluetooth-tæki."</string>
-    <string name="permdesc_bluetoothWhitelist" msgid="5494513855192170109">"Leyfir forrit að setja Bluetooth-tæki tímabundið á lista yfir leyfð tæki og gera því þannig kleift að senda skrár til þessa tækis án staðfestingar frá notanda."</string>
+    <string name="permlab_bluetoothAcceptlist" msgid="2647575807648622053">"Setja aðgang Bluetooth-tækis á hvítan lista."</string>
+    <string name="permdesc_bluetoothAcceptlist" msgid="2406423665674766442">"Leyfir forritinu að setja Bluetooth-tæki tímabundið á hvítan lista og gera því þannig kleift að senda skrár í þetta tæki án staðfestingar frá notanda."</string>
     <string name="bt_share_picker_label" msgid="6268100924487046932">"Bluetooth"</string>
     <string name="unknown_device" msgid="9221903979877041009">"Óþekkt tæki"</string>
     <string name="unknownNumber" msgid="4994750948072751566">"Óþekkt"</string>
@@ -37,7 +37,7 @@
     <string name="incoming_file_confirm_timeout_ok" msgid="1414676773249857278">"Í lagi"</string>
     <string name="incoming_file_confirm_timeout_content" msgid="172779756093975981">"Rann út á tíma við að samþykkja skrá sem „<xliff:g id="SENDER">%1$s</xliff:g>“ sendi"</string>
     <string name="incoming_file_confirm_Notification_title" msgid="5573329005298936903">"Skrá á innleið"</string>
-    <string name="incoming_file_confirm_Notification_content" msgid="3359694069319644738">"<xliff:g id="SENDER">%1$s</xliff:g> er tilbúin(n) að senda <xliff:g id="FILE">%2$s</xliff:g>"</string>
+    <string name="incoming_file_confirm_Notification_content" msgid="6237487486781004992">"<xliff:g id="SENDER">%1$s</xliff:g> ætlar að senda skrá: <xliff:g id="FILE">%2$s</xliff:g>"</string>
     <string name="notification_receiving" msgid="4674648179652543984">"Bluetooth-deiling: Tekur á móti <xliff:g id="FILE">%1$s</xliff:g>"</string>
     <string name="notification_received" msgid="3324588019186687985">"Bluetooth-deiling: Skráin <xliff:g id="FILE">%1$s</xliff:g> var móttekin"</string>
     <string name="notification_received_fail" msgid="3619350997285714746">"Bluetooth-deiling: Skráin <xliff:g id="FILE">%1$s</xliff:g> var ekki móttekin"</string>
@@ -83,8 +83,8 @@
     <string name="bt_toast_4" msgid="4678812947604395649">"Sendir skrá til „<xliff:g id="RECIPIENT">%1$s</xliff:g>“"</string>
     <string name="bt_toast_5" msgid="2846870992823019494">"Sendir <xliff:g id="NUMBER">%1$s</xliff:g> skrár til „<xliff:g id="RECIPIENT">%2$s</xliff:g>“"</string>
     <string name="bt_toast_6" msgid="1855266596936622458">"Sending skráar til „<xliff:g id="RECIPIENT">%1$s</xliff:g>“ stöðvuð"</string>
-    <string name="bt_sm_2_1_nosdcard" msgid="1791835163301501637">"Ekki er nægt pláss í USB-geymslunni til að vista skrána sem „<xliff:g id="SENDER">%1$s</xliff:g>“ sendi"</string>
-    <string name="bt_sm_2_1_default" msgid="9115512207909504071">"Ekki er nægt pláss á SD-kortinu til að vista skrána sem „<xliff:g id="SENDER">%1$s</xliff:g>“ sendi"</string>
+    <string name="bt_sm_2_1_nosdcard" msgid="5354343190503952837">"Ekki er nóg pláss í USB-geymslu til að vista skrána."</string>
+    <string name="bt_sm_2_1_default" msgid="2497541206648973852">"Ekki er nóg pláss á SD-kortinu til að vista skrána."</string>
     <string name="bt_sm_2_2" msgid="2965243265852680543">"Pláss sem þarf: <xliff:g id="SIZE">%1$s</xliff:g>"</string>
     <string name="ErrorTooManyRequests" msgid="8578277541472944529">"Of margir beiðnir eru í vinnslu. Reyndu aftur síðar."</string>
     <string name="status_pending" msgid="2503691772030877944">"Skráaflutningur er enn ekki hafinn."</string>
diff --git a/res/values-it/strings.xml b/res/values-it/strings.xml
index 8c89264..586176a 100644
--- a/res/values-it/strings.xml
+++ b/res/values-it/strings.xml
@@ -18,8 +18,8 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="permlab_bluetoothShareManager" msgid="311492132450338925">"Accedere alla gestione dei download."</string>
     <string name="permdesc_bluetoothShareManager" msgid="8930572979123190223">"Consente all\'applicazione di accedere al gestore BluetoothShare e di utilizzarlo per trasferire file."</string>
-    <string name="permlab_bluetoothWhitelist" msgid="7091552898592306386">"Autorizzazione dell\'accesso del dispositivo Bluetooth."</string>
-    <string name="permdesc_bluetoothWhitelist" msgid="5494513855192170109">"Consente all\'applicazione di autorizzare temporaneamente un dispositivo Bluetooth, permettendo a tale dispositivo di inviare file a questo dispositivo senza la conferma dell\'utente."</string>
+    <string name="permlab_bluetoothAcceptlist" msgid="2647575807648622053">"Aggiunta dell\'accesso del dispositivo Bluetooth alla lista consentita."</string>
+    <string name="permdesc_bluetoothAcceptlist" msgid="2406423665674766442">"Consente all\'app di aggiungere temporaneamente un dispositivo Bluetooth alla lista consentita, permettendo a tale dispositivo di inviare file a questo dispositivo senza la conferma dell\'utente."</string>
     <string name="bt_share_picker_label" msgid="6268100924487046932">"Bluetooth"</string>
     <string name="unknown_device" msgid="9221903979877041009">"Dispositivo sconosciuto"</string>
     <string name="unknownNumber" msgid="4994750948072751566">"Sconosciuto"</string>
@@ -37,7 +37,7 @@
     <string name="incoming_file_confirm_timeout_ok" msgid="1414676773249857278">"OK"</string>
     <string name="incoming_file_confirm_timeout_content" msgid="172779756093975981">"Si è verificato un timeout durante l\'accettazione di un file in arrivo da \"<xliff:g id="SENDER">%1$s</xliff:g>\""</string>
     <string name="incoming_file_confirm_Notification_title" msgid="5573329005298936903">"File in arrivo"</string>
-    <string name="incoming_file_confirm_Notification_content" msgid="3359694069319644738">"<xliff:g id="SENDER">%1$s</xliff:g> è pronto per inviare <xliff:g id="FILE">%2$s</xliff:g>"</string>
+    <string name="incoming_file_confirm_Notification_content" msgid="6237487486781004992">"<xliff:g id="SENDER">%1$s</xliff:g> vuole inviare un file: <xliff:g id="FILE">%2$s</xliff:g>"</string>
     <string name="notification_receiving" msgid="4674648179652543984">"Bluetooth: ricezione <xliff:g id="FILE">%1$s</xliff:g>"</string>
     <string name="notification_received" msgid="3324588019186687985">"Bluetooth: <xliff:g id="FILE">%1$s</xliff:g> ricevuto"</string>
     <string name="notification_received_fail" msgid="3619350997285714746">"Bluetooth: file <xliff:g id="FILE">%1$s</xliff:g> non ricevuto"</string>
@@ -83,8 +83,8 @@
     <string name="bt_toast_4" msgid="4678812947604395649">"Invio del file a \"<xliff:g id="RECIPIENT">%1$s</xliff:g>\""</string>
     <string name="bt_toast_5" msgid="2846870992823019494">"Invio di <xliff:g id="NUMBER">%1$s</xliff:g> file a \"<xliff:g id="RECIPIENT">%2$s</xliff:g>\""</string>
     <string name="bt_toast_6" msgid="1855266596936622458">"Invio del file a \"<xliff:g id="RECIPIENT">%1$s</xliff:g>\" interrotto"</string>
-    <string name="bt_sm_2_1_nosdcard" msgid="1791835163301501637">"Spazio nell\'archivio USB insufficiente per salvare il file ricevuto da \"<xliff:g id="SENDER">%1$s</xliff:g>\"."</string>
-    <string name="bt_sm_2_1_default" msgid="9115512207909504071">"Spazio sulla scheda SD insufficiente per salvare il file ricevuto da \"<xliff:g id="SENDER">%1$s</xliff:g>\"."</string>
+    <string name="bt_sm_2_1_nosdcard" msgid="5354343190503952837">"Spazio nell\'archivio USB insufficiente per salvare il file."</string>
+    <string name="bt_sm_2_1_default" msgid="2497541206648973852">"Spazio sulla scheda SD insufficiente per salvare il file."</string>
     <string name="bt_sm_2_2" msgid="2965243265852680543">"Spazio necessario: <xliff:g id="SIZE">%1$s</xliff:g>"</string>
     <string name="ErrorTooManyRequests" msgid="8578277541472944529">"Troppe richieste in fase di elaborazione. Riprova più tardi."</string>
     <string name="status_pending" msgid="2503691772030877944">"Trasferimento file non ancora iniziato."</string>
@@ -110,18 +110,18 @@
     <string name="outbound_noti_title" msgid="8051906709452260849">"Bluetooth: file inviati"</string>
     <string name="inbound_noti_title" msgid="4143352641953027595">"Bluetooth: file ricevuti"</string>
     <plurals name="noti_caption_unsuccessful" formatted="false" msgid="2020750076679526122">
+      <item quantity="one"><xliff:g id="UNSUCCESSFUL_NUMBER_1">%1$d</xliff:g> unsuccessful.</item>
       <item quantity="other">Operazioni non riuscite: <xliff:g id="UNSUCCESSFUL_NUMBER_1">%1$d</xliff:g>.</item>
-      <item quantity="one">Operazione non riuscita: <xliff:g id="UNSUCCESSFUL_NUMBER_0">%1$d</xliff:g>.</item>
     </plurals>
     <plurals name="noti_caption_success" formatted="false" msgid="1572472450257645181">
+      <item quantity="one"><xliff:g id="SUCCESSFUL_NUMBER_1">%1$d</xliff:g> successful, %2$s</item>
       <item quantity="other">Operazioni riuscite: <xliff:g id="SUCCESSFUL_NUMBER_1">%1$d</xliff:g>, %2$s</item>
-      <item quantity="one">Operazione riuscita: <xliff:g id="SUCCESSFUL_NUMBER_0">%1$d</xliff:g>, %2$s</item>
     </plurals>
     <string name="transfer_menu_clear_all" msgid="790017462957873132">"Cancella elenco"</string>
     <string name="transfer_menu_open" msgid="3368984869083107200">"Apri"</string>
     <string name="transfer_menu_clear" msgid="5854038118831427492">"Cancella da elenco"</string>
     <string name="transfer_clear_dlg_title" msgid="2953444575556460386">"Cancella"</string>
-    <string name="bluetooth_a2dp_sink_queue_name" msgid="6864149958708669766">"Now Playing"</string>
+    <string name="bluetooth_a2dp_sink_queue_name" msgid="6864149958708669766">"In riproduzione"</string>
     <string name="bluetooth_map_settings_save" msgid="7635491847388074606">"Salva"</string>
     <string name="bluetooth_map_settings_cancel" msgid="9205350798049865699">"Annulla"</string>
     <string name="bluetooth_map_settings_intro" msgid="6482369468223987562">"Seleziona gli account che desideri condividere tramite Bluetooth. Devi comunque accettare tutti gli accessi agli account durante la connessione."</string>
diff --git a/res/values-iw/strings.xml b/res/values-iw/strings.xml
index 6aa402b..330d094 100644
--- a/res/values-iw/strings.xml
+++ b/res/values-iw/strings.xml
@@ -18,8 +18,8 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="permlab_bluetoothShareManager" msgid="311492132450338925">"גישה למנהל ההורדות."</string>
     <string name="permdesc_bluetoothShareManager" msgid="8930572979123190223">"‏מאפשר לאפליקציה לגשת למנהל BluetoothShare ולהשתמש בו להעברת קבצים."</string>
-    <string name="permlab_bluetoothWhitelist" msgid="7091552898592306386">"‏יש להכניס מכשיר Bluetooth לרשימה הלבנה."</string>
-    <string name="permdesc_bluetoothWhitelist" msgid="5494513855192170109">"‏מאפשר לאפליקציה להכניס מכשיר Bluetooth באופן זמני לרשימה הלבנה, ובכך לאפשר למכשיר לשלוח קבצים למכשיר זה ללא אישור משתמש."</string>
+    <string name="permlab_bluetoothAcceptlist" msgid="2647575807648622053">"‏גישה לרשימת ההיתרים של מכשיר Bluetooth."</string>
+    <string name="permdesc_bluetoothAcceptlist" msgid="2406423665674766442">"‏התכונה הזו מאפשרת לאפליקציה להוסיף מכשיר Bluetooth לרשימת ההיתרים באופן זמני, ובכך לאפשר לאותו מכשיר לשלוח קבצים למכשיר זה ללא אישור משתמש."</string>
     <string name="bt_share_picker_label" msgid="6268100924487046932">"Bluetooth"</string>
     <string name="unknown_device" msgid="9221903979877041009">"מכשיר לא ידוע"</string>
     <string name="unknownNumber" msgid="4994750948072751566">"לא ידוע"</string>
@@ -37,7 +37,7 @@
     <string name="incoming_file_confirm_timeout_ok" msgid="1414676773249857278">"אישור"</string>
     <string name="incoming_file_confirm_timeout_content" msgid="172779756093975981">"תם הזמן הקצוב לקבלת קובץ נכנס מאת \"<xliff:g id="SENDER">%1$s</xliff:g>\""</string>
     <string name="incoming_file_confirm_Notification_title" msgid="5573329005298936903">"קובץ שהתקבל"</string>
-    <string name="incoming_file_confirm_Notification_content" msgid="3359694069319644738">"<xliff:g id="SENDER">%1$s</xliff:g> מוכן לשלוח את <xliff:g id="FILE">%2$s</xliff:g>"</string>
+    <string name="incoming_file_confirm_Notification_content" msgid="6237487486781004992">"<xliff:g id="SENDER">%1$s</xliff:g> מוכן לשליחת קובץ: <xliff:g id="FILE">%2$s</xliff:g>"</string>
     <string name="notification_receiving" msgid="4674648179652543984">"‏שיתוף Bluetooth: ‏<xliff:g id="FILE">%1$s</xliff:g> מתקבל"</string>
     <string name="notification_received" msgid="3324588019186687985">"‏שיתוף Bluetooth‏: התקבל <xliff:g id="FILE">%1$s</xliff:g>"</string>
     <string name="notification_received_fail" msgid="3619350997285714746">"‏שיתוף Bluetooth: הקובץ <xliff:g id="FILE">%1$s</xliff:g> לא התקבל"</string>
@@ -83,8 +83,8 @@
     <string name="bt_toast_4" msgid="4678812947604395649">"שליחת קובץ אל \"<xliff:g id="RECIPIENT">%1$s</xliff:g>\" מתבצעת"</string>
     <string name="bt_toast_5" msgid="2846870992823019494">"שליחת <xliff:g id="NUMBER">%1$s</xliff:g> קבצים אל \"<xliff:g id="RECIPIENT">%2$s</xliff:g>\" מתבצעת"</string>
     <string name="bt_toast_6" msgid="1855266596936622458">"שליחת קובץ אל \"<xliff:g id="RECIPIENT">%1$s</xliff:g>\" הופסקה"</string>
-    <string name="bt_sm_2_1_nosdcard" msgid="1791835163301501637">"‏אין מספיק שטח פנוי באחסון ה-USB לשמירת הקובץ מאת \"<xliff:g id="SENDER">%1$s</xliff:g>\""</string>
-    <string name="bt_sm_2_1_default" msgid="9115512207909504071">"‏אין מספיק שטח אחסון בכרטיס ה-SD לשמירת הקובץ מאת \"<xliff:g id="SENDER">%1$s</xliff:g>\""</string>
+    <string name="bt_sm_2_1_nosdcard" msgid="5354343190503952837">"‏אין מספיק שטח באחסון ה-USB כדי לשמור את הקובץ."</string>
+    <string name="bt_sm_2_1_default" msgid="2497541206648973852">"‏אין מספיק שטח אחסון בכרטיס ה-SD כדי לשמור את הקובץ."</string>
     <string name="bt_sm_2_2" msgid="2965243265852680543">"שטח דרוש: <xliff:g id="SIZE">%1$s</xliff:g>"</string>
     <string name="ErrorTooManyRequests" msgid="8578277541472944529">"בקשות רבות מדי הועברו לעיבוד. יש לנסות שוב מאוחר יותר."</string>
     <string name="status_pending" msgid="2503691772030877944">"העברת הקובץ עדיין לא החלה."</string>
diff --git a/res/values-ja/strings.xml b/res/values-ja/strings.xml
index 6cdd650..e0d9b28 100644
--- a/res/values-ja/strings.xml
+++ b/res/values-ja/strings.xml
@@ -18,8 +18,8 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="permlab_bluetoothShareManager" msgid="311492132450338925">"ダウンロード マネージャーにアクセスします。"</string>
     <string name="permdesc_bluetoothShareManager" msgid="8930572979123190223">"BluetoothShareマネージャーへのアクセスとそれを利用したファイル転送をアプリに許可します。"</string>
-    <string name="permlab_bluetoothWhitelist" msgid="7091552898592306386">"Bluetoothデバイスによるアクセスを許可します。"</string>
-    <string name="permdesc_bluetoothWhitelist" msgid="5494513855192170109">"Bluetoothデバイスによるアクセスを一時的に許可して、ユーザーの確認を受けずにそのデバイスからこのデバイスにファイルを送信することをアプリに許可します。"</string>
+    <string name="permlab_bluetoothAcceptlist" msgid="2647575807648622053">"Bluetoothデバイスによるアクセスを許可します。"</string>
+    <string name="permdesc_bluetoothAcceptlist" msgid="2406423665674766442">"Bluetooth デバイスによるアクセスを一時的に許可して、ユーザーの確認を受けずにそのデバイスからこのデバイスにファイルを送信することをアプリに許可します。"</string>
     <string name="bt_share_picker_label" msgid="6268100924487046932">"Bluetooth"</string>
     <string name="unknown_device" msgid="9221903979877041009">"不明なモバイルデバイス"</string>
     <string name="unknownNumber" msgid="4994750948072751566">"不明"</string>
@@ -37,7 +37,7 @@
     <string name="incoming_file_confirm_timeout_ok" msgid="1414676773249857278">"OK"</string>
     <string name="incoming_file_confirm_timeout_content" msgid="172779756093975981">"「<xliff:g id="SENDER">%1$s</xliff:g>」からのファイル受信中に接続がタイムアウトしました"</string>
     <string name="incoming_file_confirm_Notification_title" msgid="5573329005298936903">"着信ファイル"</string>
-    <string name="incoming_file_confirm_Notification_content" msgid="3359694069319644738">"<xliff:g id="SENDER">%1$s</xliff:g>さんが<xliff:g id="FILE">%2$s</xliff:g>を送信できるようになりました"</string>
+    <string name="incoming_file_confirm_Notification_content" msgid="6237487486781004992">"<xliff:g id="SENDER">%1$s</xliff:g> さんがファイル(<xliff:g id="FILE">%2$s</xliff:g>)を送信する準備ができました"</string>
     <string name="notification_receiving" msgid="4674648179652543984">"Bluetooth共有: <xliff:g id="FILE">%1$s</xliff:g>を受信中"</string>
     <string name="notification_received" msgid="3324588019186687985">"Bluetooth共有: <xliff:g id="FILE">%1$s</xliff:g>を受信済み"</string>
     <string name="notification_received_fail" msgid="3619350997285714746">"Bluetooth共有: ファイル<xliff:g id="FILE">%1$s</xliff:g>の受信に失敗"</string>
@@ -83,8 +83,8 @@
     <string name="bt_toast_4" msgid="4678812947604395649">"「<xliff:g id="RECIPIENT">%1$s</xliff:g>」にファイルを送信中"</string>
     <string name="bt_toast_5" msgid="2846870992823019494">"<xliff:g id="NUMBER">%1$s</xliff:g>個のファイルを「<xliff:g id="RECIPIENT">%2$s</xliff:g>」に送信中"</string>
     <string name="bt_toast_6" msgid="1855266596936622458">"「<xliff:g id="RECIPIENT">%1$s</xliff:g>」へのファイルの送信を停止しました"</string>
-    <string name="bt_sm_2_1_nosdcard" msgid="1791835163301501637">"「<xliff:g id="SENDER">%1$s</xliff:g>」からのファイルを保存するのに十分な空き領域が USB ストレージにありません"</string>
-    <string name="bt_sm_2_1_default" msgid="9115512207909504071">"「<xliff:g id="SENDER">%1$s</xliff:g>」からのファイルを保存するのに十分な空き領域が SD カードにありません"</string>
+    <string name="bt_sm_2_1_nosdcard" msgid="5354343190503952837">"USB ストレージにファイルを保存するのに十分な容量がありません。"</string>
+    <string name="bt_sm_2_1_default" msgid="2497541206648973852">"SD カードにファイルを保存するのに十分な容量がありません。"</string>
     <string name="bt_sm_2_2" msgid="2965243265852680543">"必要な空き領域: <xliff:g id="SIZE">%1$s</xliff:g>"</string>
     <string name="ErrorTooManyRequests" msgid="8578277541472944529">"処理中のリクエストが多すぎるため、しばらくしてからもう一度お試しください。"</string>
     <string name="status_pending" msgid="2503691772030877944">"ファイル転送はまだ開始されていません。"</string>
diff --git a/res/values-ka/strings.xml b/res/values-ka/strings.xml
index f0895ba..e3bba11 100644
--- a/res/values-ka/strings.xml
+++ b/res/values-ka/strings.xml
@@ -18,8 +18,8 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="permlab_bluetoothShareManager" msgid="311492132450338925">"ჩამოტვირთვების მენეჯერზე წვდომა."</string>
     <string name="permdesc_bluetoothShareManager" msgid="8930572979123190223">"ანიჭებს აპს BluetoothShare მენეჯერზე წვდომას და მისი გამოყენების უფლებას ფაილების გასაგზავნად."</string>
-    <string name="permlab_bluetoothWhitelist" msgid="7091552898592306386">"Bluetooth მოწყობილობის წვდომის თეთრ სიაში შეყვანა."</string>
-    <string name="permdesc_bluetoothWhitelist" msgid="5494513855192170109">"საშუალებას აძლევს აპს, რომ დროებით თეთრ სიაში შეიყვანოს Bluetooth მოწყობილობა, რაც ამ მოწყობილობას საშუალებას მიცემს ფაილები ამ მოწყობილობაზე მომხმარებლის დასტურის გარეშე გამოაგზავნოს."</string>
+    <string name="permlab_bluetoothAcceptlist" msgid="2647575807648622053">"შეიყვანეთ Bluetooth მოწყობილობის წვდომა მისაღებ სიაში."</string>
+    <string name="permdesc_bluetoothAcceptlist" msgid="2406423665674766442">"საშუალებას აძლევს აპს, რომ დროებით მისაღებ სიაში შეიყვანოს Bluetooth მოწყობილობა, რაც ამ მოწყობილობას საშუალებას მისცემს, ფაილები ამ მოწყობილობაზე მომხმარებლის დასტურის გარეშე გამოგზავნოს."</string>
     <string name="bt_share_picker_label" msgid="6268100924487046932">"Bluetooth"</string>
     <string name="unknown_device" msgid="9221903979877041009">"უცნობი მოწყობილობა"</string>
     <string name="unknownNumber" msgid="4994750948072751566">"უცნობი"</string>
@@ -37,7 +37,7 @@
     <string name="incoming_file_confirm_timeout_ok" msgid="1414676773249857278">"კარგი"</string>
     <string name="incoming_file_confirm_timeout_content" msgid="172779756093975981">"„<xliff:g id="SENDER">%1$s</xliff:g>“-გან ფაილის შემომავალი ფაილის მიღების დრო ამოიწურა"</string>
     <string name="incoming_file_confirm_Notification_title" msgid="5573329005298936903">"შემომავალი ფაილი"</string>
-    <string name="incoming_file_confirm_Notification_content" msgid="3359694069319644738">"<xliff:g id="SENDER">%1$s</xliff:g> მზად არის <xliff:g id="FILE">%2$s</xliff:g>-ის გასაგზავნად"</string>
+    <string name="incoming_file_confirm_Notification_content" msgid="6237487486781004992">"<xliff:g id="SENDER">%1$s</xliff:g> მზად არის შემდეგი ფაილის გასაგზავნად: <xliff:g id="FILE">%2$s</xliff:g>"</string>
     <string name="notification_receiving" msgid="4674648179652543984">"Bluetooth გაზიარება: <xliff:g id="FILE">%1$s</xliff:g> ფაილის მიღება"</string>
     <string name="notification_received" msgid="3324588019186687985">"Bluetooth გაზიარება: მიღებულია <xliff:g id="FILE">%1$s</xliff:g>"</string>
     <string name="notification_received_fail" msgid="3619350997285714746">"Bluetooth გაზიარება: ფაილი <xliff:g id="FILE">%1$s</xliff:g> არ მიღებულა"</string>
@@ -83,8 +83,8 @@
     <string name="bt_toast_4" msgid="4678812947604395649">"„<xliff:g id="RECIPIENT">%1$s</xliff:g>“-თან ფაილის გაგზავნა"</string>
     <string name="bt_toast_5" msgid="2846870992823019494">"<xliff:g id="NUMBER">%1$s</xliff:g> ფაილის „<xliff:g id="RECIPIENT">%2$s</xliff:g>“-თან გაგზავნა"</string>
     <string name="bt_toast_6" msgid="1855266596936622458">"„<xliff:g id="RECIPIENT">%1$s</xliff:g>“-თან ფაილის გაგზავნა შეჩერდა"</string>
-    <string name="bt_sm_2_1_nosdcard" msgid="1791835163301501637">"„<xliff:g id="SENDER">%1$s</xliff:g>“-დან ფაილის შესანახად USB საცავში მეხსიერება არასაკმარისია."</string>
-    <string name="bt_sm_2_1_default" msgid="9115512207909504071">"„<xliff:g id="SENDER">%1$s</xliff:g>“-დან ფაილის შესანახად SD ბარათზე მეხსიერება არასაკმარისია."</string>
+    <string name="bt_sm_2_1_nosdcard" msgid="5354343190503952837">"ფაილის შესანახად USB მეხსიერებაში საკმარისი სივრცე არ არის."</string>
+    <string name="bt_sm_2_1_default" msgid="2497541206648973852">"ფაილის შესანახად SD ბარათზე საკმარისი სივრცე არ არის."</string>
     <string name="bt_sm_2_2" msgid="2965243265852680543">"საჭირო სივრცე: <xliff:g id="SIZE">%1$s</xliff:g>"</string>
     <string name="ErrorTooManyRequests" msgid="8578277541472944529">"მუშავდება ძალიან ბევრი პროცესი. შეეცადეთ მოგვიანებით."</string>
     <string name="status_pending" msgid="2503691772030877944">"ფაილის ტრანსფერი ჯერ არ დაწყებულა."</string>
diff --git a/res/values-kk/strings.xml b/res/values-kk/strings.xml
index 8aaf747..cd8aca5 100644
--- a/res/values-kk/strings.xml
+++ b/res/values-kk/strings.xml
@@ -18,8 +18,8 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="permlab_bluetoothShareManager" msgid="311492132450338925">"Жүктеу менеджеріне қол жетімділік."</string>
     <string name="permdesc_bluetoothShareManager" msgid="8930572979123190223">"Қолданбаға BluetoothБөлісу менеджеріне кіріп, оны файлдары аудару үшін қолдану мүмкіндігін береді."</string>
-    <string name="permlab_bluetoothWhitelist" msgid="7091552898592306386">"Bluetooth құрылғысына қол жетімділікке рұқсат беру."</string>
-    <string name="permdesc_bluetoothWhitelist" msgid="5494513855192170109">"Қолданбаға Bluetooth құрылғысына уақытша рұқсат беріп, файлдарды сол құрылғыдан осы құрылғыға пайдаланушының растауынсыз жіберу мүмкіндігін береді."</string>
+    <string name="permlab_bluetoothAcceptlist" msgid="2647575807648622053">"Bluetooth құрылғысын рұқсатқа ие тізімге қосу"</string>
+    <string name="permdesc_bluetoothAcceptlist" msgid="2406423665674766442">"Қолданба Bluetooth құрылғысына уақытша рұқсат беріп, файлдарды сол құрылғыдан осы құрылғыға пайдаланушының растауынсыз жібере алады."</string>
     <string name="bt_share_picker_label" msgid="6268100924487046932">"Bluetooth"</string>
     <string name="unknown_device" msgid="9221903979877041009">"Белгісіз құрылғы"</string>
     <string name="unknownNumber" msgid="4994750948072751566">"Белгісіз"</string>
@@ -37,7 +37,7 @@
     <string name="incoming_file_confirm_timeout_ok" msgid="1414676773249857278">"Жарайды"</string>
     <string name="incoming_file_confirm_timeout_content" msgid="172779756093975981">"\"<xliff:g id="SENDER">%1$s</xliff:g>\" жіберген файлды қабылдау барысында уақыт аяқталды."</string>
     <string name="incoming_file_confirm_Notification_title" msgid="5573329005298936903">"Кіріс файл"</string>
-    <string name="incoming_file_confirm_Notification_content" msgid="3359694069319644738">"<xliff:g id="SENDER">%1$s</xliff:g> <xliff:g id="FILE">%2$s</xliff:g> жіберуге дайын"</string>
+    <string name="incoming_file_confirm_Notification_content" msgid="6237487486781004992">"<xliff:g id="SENDER">%1$s</xliff:g> келесі файлды жіберуге дайын: <xliff:g id="FILE">%2$s</xliff:g>"</string>
     <string name="notification_receiving" msgid="4674648179652543984">"Bluetooth бөлісу: <xliff:g id="FILE">%1$s</xliff:g> файлын қабылдауда"</string>
     <string name="notification_received" msgid="3324588019186687985">"Bluetooth бөлісу: <xliff:g id="FILE">%1$s</xliff:g> файлы қабылданды"</string>
     <string name="notification_received_fail" msgid="3619350997285714746">"Bluetooth бөлісу: <xliff:g id="FILE">%1$s</xliff:g> файлы қабылданбады"</string>
@@ -83,8 +83,8 @@
     <string name="bt_toast_4" msgid="4678812947604395649">"Файлды \"<xliff:g id="RECIPIENT">%1$s</xliff:g>\" байланысына жіберуде"</string>
     <string name="bt_toast_5" msgid="2846870992823019494">"<xliff:g id="NUMBER">%1$s</xliff:g> файл \"<xliff:g id="RECIPIENT">%2$s</xliff:g>\" байланысына жіберілуде"</string>
     <string name="bt_toast_6" msgid="1855266596936622458">"Файлды \"<xliff:g id="RECIPIENT">%1$s</xliff:g>\" байланысына жіберу доғарылды."</string>
-    <string name="bt_sm_2_1_nosdcard" msgid="1791835163301501637">"\"<xliff:g id="SENDER">%1$s</xliff:g>\" жіберген файлды сақтау үшін USB жадында орын жеткіліксіз"</string>
-    <string name="bt_sm_2_1_default" msgid="9115512207909504071">"\"<xliff:g id="SENDER">%1$s</xliff:g>\" жіберген файлды сақтау үшін SD картасында орын жеткіліксіз"</string>
+    <string name="bt_sm_2_1_nosdcard" msgid="5354343190503952837">"Файл сақтауға USB жадында орын жеткіліксіз."</string>
+    <string name="bt_sm_2_1_default" msgid="2497541206648973852">"Файл сақтауға SD картасында орын жеткіліксіз."</string>
     <string name="bt_sm_2_2" msgid="2965243265852680543">"Қажет орын мөлшері: <xliff:g id="SIZE">%1$s</xliff:g>"</string>
     <string name="ErrorTooManyRequests" msgid="8578277541472944529">"Тым көп өтініштер қаралуда. Кейінірек қайта әрекеттеніп көріңіз."</string>
     <string name="status_pending" msgid="2503691772030877944">"Файлды аудару әлі басталған жоқ."</string>
diff --git a/res/values-km/strings.xml b/res/values-km/strings.xml
index 9cf7178..755ec5b 100644
--- a/res/values-km/strings.xml
+++ b/res/values-km/strings.xml
@@ -18,8 +18,8 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="permlab_bluetoothShareManager" msgid="311492132450338925">"ចូល​ដំណើរ​ការ​កម្មវិធី​គ្រប់គ្រង​ការ​ទាញ​យក​។"</string>
     <string name="permdesc_bluetoothShareManager" msgid="8930572979123190223">"អនុញ្ញាត​ឲ្យ​កម្មវិធី​ត្រូវ​​ចូល​ដំណើរការ​កម្មវិធី​គ្រប់គ្រង BluetoothShare ហើយ​​ប្រើ​វា​ដើម្បី​ផ្ទេរ​ឯកសារ។"</string>
-    <string name="permlab_bluetoothWhitelist" msgid="7091552898592306386">"បញ្ជី​ស​ការ​ចូល​ដំណើរការ​ប៊្លូធូស។"</string>
-    <string name="permdesc_bluetoothWhitelist" msgid="5494513855192170109">"​ឲ្យ​កម្មវិធី​ដាក់​ឧបករណ៍​ប៊្លូធូស​ក្នុង​បញ្ជី​បណ្ដោះអាសន្ន​ ដែល​អនុញ្ញាត​ឲ្យ​ឧបករណ៍​​នោះ ​ផ្ញើ​ឯកសារ​ទៅ​​កាន់​ឧបករណ៍​​​ដោយ​មិន​ចាំបាច់​ការ​បញ្ជាក់​​អ្នក​ប្រើ។"</string>
+    <string name="permlab_bluetoothAcceptlist" msgid="2647575807648622053">"សិទ្ធិ​ចូលប្រើ​ឧបករណ៍​ប៊្លូធូស​ក្នុងបញ្ជី​អនុញ្ញាត​។"</string>
+    <string name="permdesc_bluetoothAcceptlist" msgid="2406423665674766442">"អនុញ្ញាតឱ្យ​កម្មវិធី​ដាក់​ឧបករណ៍​ប៊្លូធូស​ក្នុង​បញ្ជីអនុញ្ញាតជា​បណ្ដោះអាសន្ន​ ដែល​អនុញ្ញាត​ឱ្យ​ឧបករណ៍​នោះ​ផ្ញើ​ឯកសារ​ទៅ​ឧបករណ៍​​នេះដោយ​មិន​ចាំបាច់​បញ្ជាក់​អ្នកប្រើប្រាស់​។"</string>
     <string name="bt_share_picker_label" msgid="6268100924487046932">"ប៊្លូធូស"</string>
     <string name="unknown_device" msgid="9221903979877041009">"មិន​ស្គាល់​ឧបករណ៍"</string>
     <string name="unknownNumber" msgid="4994750948072751566">"មិន​ស្គាល់"</string>
@@ -37,7 +37,7 @@
     <string name="incoming_file_confirm_timeout_ok" msgid="1414676773249857278">"យល់​ព្រម​"</string>
     <string name="incoming_file_confirm_timeout_content" msgid="172779756093975981">"អស់​ពេល​ទទួល​​ឯកសារ​ចូល​ពី \"<xliff:g id="SENDER">%1$s</xliff:g>\""</string>
     <string name="incoming_file_confirm_Notification_title" msgid="5573329005298936903">"ឯកសារចូល"</string>
-    <string name="incoming_file_confirm_Notification_content" msgid="3359694069319644738">"<xliff:g id="SENDER">%1$s</xliff:g> ត្រៀមរួចរាល់ក្នុងការផ្ញើ <xliff:g id="FILE">%2$s</xliff:g>"</string>
+    <string name="incoming_file_confirm_Notification_content" msgid="6237487486781004992">"<xliff:g id="SENDER">%1$s</xliff:g> អាចផ្ញើឯកសារ​បានហើយ៖ <xliff:g id="FILE">%2$s</xliff:g>"</string>
     <string name="notification_receiving" msgid="4674648179652543984">"ការ​ចែក​រំលែក​ប៊្លូ​ធូ​ស៖ ទទួល <xliff:g id="FILE">%1$s</xliff:g>"</string>
     <string name="notification_received" msgid="3324588019186687985">"ការ​ចែក​រំលែក​ប៊្លូ​ធូ​ស៖ បាន​ទទួល​ <xliff:g id="FILE">%1$s</xliff:g>"</string>
     <string name="notification_received_fail" msgid="3619350997285714746">"ការ​ចែក​រំលែក​ប៊្លូ​ធូ​ស៖ មិន​បាន​ទទួល​ឯកសារ <xliff:g id="FILE">%1$s</xliff:g>"</string>
@@ -83,8 +83,8 @@
     <string name="bt_toast_4" msgid="4678812947604395649">"ផ្ញើ​ឯកសារ​ទៅ \"<xliff:g id="RECIPIENT">%1$s</xliff:g>\""</string>
     <string name="bt_toast_5" msgid="2846870992823019494">"ផ្ញើ​ឯកសារ <xliff:g id="NUMBER">%1$s</xliff:g> ទៅ \"<xliff:g id="RECIPIENT">%2$s</xliff:g>\""</string>
     <string name="bt_toast_6" msgid="1855266596936622458">"បាន​បញ្ឈប់​ការ​ផ្ញើ​ឯកសារ​ទៅ \"<xliff:g id="RECIPIENT">%1$s</xliff:g>\""</string>
-    <string name="bt_sm_2_1_nosdcard" msgid="1791835163301501637">"មិនមាន​ទំហំផ្ទុក​គ្រប់គ្រាន់​នៅក្នុង​ឧបករណ៍​ផ្ទុក USB សម្រាប់​រក្សាទុក​ឯកសារ​ពី \"<xliff:g id="SENDER">%1$s</xliff:g>\" ទេ"</string>
-    <string name="bt_sm_2_1_default" msgid="9115512207909504071">"មិនមាន​ទំហំផ្ទុក​គ្រប់គ្រាន់​នៅលើ​កាត SD សម្រាប់​រក្សាទុក​ឯកសារ​ពី \"<xliff:g id="SENDER">%1$s</xliff:g>\" ទេ"</string>
+    <string name="bt_sm_2_1_nosdcard" msgid="5354343190503952837">"មិនមាន​ទំហំផ្ទុក​គ្រប់គ្រាន់​នៅក្នុងឧបករណ៍​ផ្ទុក USB ដើម្បីរក្សាទុក​ឯកសារទេ។"</string>
+    <string name="bt_sm_2_1_default" msgid="2497541206648973852">"មិនមាន​ទំហំផ្ទុក​គ្រប់គ្រាន់​នៅលើកាត SD ដើម្បីរក្សាទុក​ឯកសារទេ។"</string>
     <string name="bt_sm_2_2" msgid="2965243265852680543">"ទំហំ​ដែល​ត្រូវ​ការ៖ <xliff:g id="SIZE">%1$s</xliff:g>"</string>
     <string name="ErrorTooManyRequests" msgid="8578277541472944529">"កំពុង​ដំណើរការ​សំណើ​ជា​ច្រើន​​។ សូម​ព្យាយាម​ម្ដង​ទៀត​នៅ​ពេល​ក្រោយ​។"</string>
     <string name="status_pending" msgid="2503691772030877944">"មិន​ទាន់​បាន​ចាប់ផ្ដើម​ផ្ទេរ​ឯកសារ​នៅ​ឡើយ​ទេ។"</string>
diff --git a/res/values-kn/strings.xml b/res/values-kn/strings.xml
index f00939f..7132171 100644
--- a/res/values-kn/strings.xml
+++ b/res/values-kn/strings.xml
@@ -18,8 +18,8 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="permlab_bluetoothShareManager" msgid="311492132450338925">"ಡೌನ್‌ಲೋಡ್‌ ನಿರ್ವಾಹಕವನ್ನು ಪ್ರವೇಶಿಸಿ."</string>
     <string name="permdesc_bluetoothShareManager" msgid="8930572979123190223">"ಬ್ಲೂಟೂತ್‌ ಹಂಚಿಕೆ ನಿರ್ವಾಹಕ ಮತ್ತು ಫೈಲ್‌ಗಳ ವರ್ಗಾವಣೆಯನ್ನು ಬಳಸಲು ಅಪ್ಲಿಕೇಶನ್‌ ಅನುಮತಿಸುತ್ತದೆ."</string>
-    <string name="permlab_bluetoothWhitelist" msgid="7091552898592306386">"ಶ್ವೇತಪಟ್ಟಿ ಬ್ಲೂಟೂತ್‌ ಸಾಧನವನ್ನು ಪ್ರವೇಶಿಸಿ."</string>
-    <string name="permdesc_bluetoothWhitelist" msgid="5494513855192170109">"ಬಳಕೆದಾರರ ದೃಢೀಕರಣ ಇಲ್ಲದೆ ಈ ಸಾಧನಕ್ಕೆ ಫೈಲ್‌ಗಳನ್ನು ಕಳುಹಿಸಲು ಬ್ಲೂಟೂತ್‌ ಸಾಧನವನ್ನು ತಾತ್ಕಾಲಿಕವಾಗಿ ಶ್ವೇತಪಟ್ಟಿ ಮಾಡಲು ಅಪ್ಲಿಕೇಶನ್‌‌ಗೆ ಅನುಮತಿಸುತ್ತದೆ."</string>
+    <string name="permlab_bluetoothAcceptlist" msgid="2647575807648622053">"ಸಮ್ಮತಿಪಟ್ಟಿ ಬ್ಲೂಟೂತ್‌ ಸಾಧನವನ್ನು ಪ್ರವೇಶಿಸಿ."</string>
+    <string name="permdesc_bluetoothAcceptlist" msgid="2406423665674766442">"ಬಳಕೆದಾರರ ದೃಢೀಕರಣ ಇಲ್ಲದೆ ಈ ಸಾಧನಕ್ಕೆ ಫೈಲ್‌ಗಳನ್ನು ಕಳುಹಿಸಲು ಬ್ಲೂಟೂತ್‌ ಸಾಧನವನ್ನು ತಾತ್ಕಾಲಿಕವಾಗಿ ಸಮ್ಮತಿಪಟ್ಟಿ ಮಾಡಲು ಅಪ್ಲಿಕೇಶನ್‌‌ಗೆ ಅನುಮತಿಸುತ್ತದೆ."</string>
     <string name="bt_share_picker_label" msgid="6268100924487046932">"ಬ್ಲೂಟೂತ್‌"</string>
     <string name="unknown_device" msgid="9221903979877041009">"ಅಪರಿಚಿತ ಸಾಧನ"</string>
     <string name="unknownNumber" msgid="4994750948072751566">"ಅಪರಿಚಿತ"</string>
@@ -37,7 +37,7 @@
     <string name="incoming_file_confirm_timeout_ok" msgid="1414676773249857278">"ಸರಿ"</string>
     <string name="incoming_file_confirm_timeout_content" msgid="172779756093975981">"\"<xliff:g id="SENDER">%1$s</xliff:g>\" ಅವರಿಂದ ಫೈಲ್‌ ಸ್ವೀಕರಿಸುವಾಗ ಕಾಲಾವಧಿ ಮುಕ್ತಾಯಗೊಂಡಿದೆ"</string>
     <string name="incoming_file_confirm_Notification_title" msgid="5573329005298936903">"ಒಳಬರುತ್ತಿರುವ ಫೈಲ್"</string>
-    <string name="incoming_file_confirm_Notification_content" msgid="3359694069319644738">"<xliff:g id="SENDER">%1$s</xliff:g> ಅವರು <xliff:g id="FILE">%2$s</xliff:g> ಫೈಲ್ ಕಳುಹಿಸಲು ಸಿದ್ಧವಾಗಿದ್ದಾರೆ"</string>
+    <string name="incoming_file_confirm_Notification_content" msgid="6237487486781004992">"<xliff:g id="SENDER">%1$s</xliff:g> ಅವರು ಫೈಲ್ ಒಂದನ್ನು ಕಳುಹಿಸಲು ಸಿದ್ಧರಾಗಿದ್ದಾರೆ: <xliff:g id="FILE">%2$s</xliff:g>"</string>
     <string name="notification_receiving" msgid="4674648179652543984">"ಬ್ಲೂಟೂತ್‌ ಹಂಚಿಕೆ: <xliff:g id="FILE">%1$s</xliff:g> ಸ್ವೀಕರಿಸಲಾಗುತ್ತಿದೆ"</string>
     <string name="notification_received" msgid="3324588019186687985">"ಬ್ಲೂಟೂತ್‌ ಹಂಚಿಕೆ: <xliff:g id="FILE">%1$s</xliff:g> ಸ್ವೀಕರಿಸಲಾಗಿದೆ"</string>
     <string name="notification_received_fail" msgid="3619350997285714746">"ಬ್ಲೂಟೂತ್‌ ಹಂಚಿಕೆ: ಫೈಲ್‌ <xliff:g id="FILE">%1$s</xliff:g> ಸ್ವೀಕರಿಸಿಲ್ಲ"</string>
@@ -83,8 +83,8 @@
     <string name="bt_toast_4" msgid="4678812947604395649">"\"<xliff:g id="RECIPIENT">%1$s</xliff:g>\" ಇವರಿಗೆ ಫೈಲ್‌‌ ಕಳುಹಿಸಲಾಗುತ್ತಿದೆ"</string>
     <string name="bt_toast_5" msgid="2846870992823019494">"\"<xliff:g id="RECIPIENT">%2$s</xliff:g>\" ಇವರಿಗೆ <xliff:g id="NUMBER">%1$s</xliff:g> ಫೈಲ್‌‌ಗಳನ್ನು ಕಳುಹಿಸಲಾಗುತ್ತಿದೆ"</string>
     <string name="bt_toast_6" msgid="1855266596936622458">"\"<xliff:g id="RECIPIENT">%1$s</xliff:g>\" ಇವರಿಗೆ ಫೈಲ್‌ ಕಳುಹಿಸುವುದನ್ನು ನಿಲ್ಲಿಸಲಾಗಿದೆ"</string>
-    <string name="bt_sm_2_1_nosdcard" msgid="1791835163301501637">"\"<xliff:g id="SENDER">%1$s</xliff:g>\" ರಿಂದ ಫೈಲ್‌ ಉಳಿಸಲು USB ಸಂಗ್ರಹಣೆಯಲ್ಲಿ ಸಾಕಷ್ಟು ಸ್ಥಳಾವಕಾಶವಿಲ್ಲ"</string>
-    <string name="bt_sm_2_1_default" msgid="9115512207909504071">"\"<xliff:g id="SENDER">%1$s</xliff:g>\" ರಿಂದ ಫೈಲ್‌ ಉಳಿಸಲು SD ಕಾರ್ಡ್‌ನಲ್ಲಿ ಸಾಕಷ್ಟು ಸ್ಥಳಾವಕಾಶವಿಲ್ಲ"</string>
+    <string name="bt_sm_2_1_nosdcard" msgid="5354343190503952837">"ಫೈಲ್ ಅನ್ನು ಉಳಿಸಲು USB ಸಂಗ್ರಹಣೆಯಲ್ಲಿ ಸಾಕಷ್ಟು ಸ್ಥಳಾವಕಾಶವಿಲ್ಲ."</string>
+    <string name="bt_sm_2_1_default" msgid="2497541206648973852">"ಫೈಲ್ ಅನ್ನು ಉಳಿಸಲು SD ಕಾರ್ಡ್‌ನಲ್ಲಿ ಸಾಕಷ್ಟು ಸ್ಥಳಾವಕಾಶವಿಲ್ಲ."</string>
     <string name="bt_sm_2_2" msgid="2965243265852680543">"ಅಗತ್ಯವಿರುವ ಸ್ಥಳಾವಕಾಶ: <xliff:g id="SIZE">%1$s</xliff:g>"</string>
     <string name="ErrorTooManyRequests" msgid="8578277541472944529">"ಹಲವಾರು ವಿನಂತಿಗಳನ್ನು ಪ್ರಕ್ರಿಯೆಗೊಳಿಸಲಾಗುತ್ತಿದೆ. ನಂತರ ಮತ್ತೆ ಪ್ರಯತ್ನಿಸಿ."</string>
     <string name="status_pending" msgid="2503691772030877944">"ಫೈಲ್‌‌ ವರ್ಗಾವಣೆ ಇನ್ನೂ ಪ್ರಾರಂಭಿಸಿಲ್ಲ."</string>
diff --git a/res/values-ko/strings.xml b/res/values-ko/strings.xml
index f8e822c..4936148 100644
--- a/res/values-ko/strings.xml
+++ b/res/values-ko/strings.xml
@@ -18,8 +18,8 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="permlab_bluetoothShareManager" msgid="311492132450338925">"다운로드 관리자에 액세스합니다."</string>
     <string name="permdesc_bluetoothShareManager" msgid="8930572979123190223">"앱에서 BluetoothShare 관리자에 액세스하고 이를 사용하여 파일을 전송할 수 있도록 합니다."</string>
-    <string name="permlab_bluetoothWhitelist" msgid="7091552898592306386">"블루투스 기기에 액세스하도록 허용합니다."</string>
-    <string name="permdesc_bluetoothWhitelist" msgid="5494513855192170109">"일시적으로 앱에서 블루투스 기기를 허용하여 사용자 확인을 받지 않고 해당 기기로 파일을 보내도록 허용합니다."</string>
+    <string name="permlab_bluetoothAcceptlist" msgid="2647575807648622053">"블루투스 기기 액세스 허용"</string>
+    <string name="permdesc_bluetoothAcceptlist" msgid="2406423665674766442">"일시적으로 앱에서 블루투스 기기를 허용하여 사용자 확인을 받지 않고 블루투스 기기에서 이 기기로 파일을 보내도록 허용합니다."</string>
     <string name="bt_share_picker_label" msgid="6268100924487046932">"블루투스"</string>
     <string name="unknown_device" msgid="9221903979877041009">"알 수 없는 장치"</string>
     <string name="unknownNumber" msgid="4994750948072751566">"알 수 없음"</string>
@@ -37,7 +37,7 @@
     <string name="incoming_file_confirm_timeout_ok" msgid="1414676773249857278">"확인"</string>
     <string name="incoming_file_confirm_timeout_content" msgid="172779756093975981">"\'<xliff:g id="SENDER">%1$s</xliff:g>\'님이 보내는 파일을 수락하는 동안 제한 시간을 초과했습니다."</string>
     <string name="incoming_file_confirm_Notification_title" msgid="5573329005298936903">"수신 파일"</string>
-    <string name="incoming_file_confirm_Notification_content" msgid="3359694069319644738">"<xliff:g id="SENDER">%1$s</xliff:g>님이 <xliff:g id="FILE">%2$s</xliff:g>을(를) 보낼 준비가 되었습니다."</string>
+    <string name="incoming_file_confirm_Notification_content" msgid="6237487486781004992">"<xliff:g id="SENDER">%1$s</xliff:g>에서 <xliff:g id="FILE">%2$s</xliff:g> 파일을 전송할 준비가 되었습니다."</string>
     <string name="notification_receiving" msgid="4674648179652543984">"블루투스 공유: <xliff:g id="FILE">%1$s</xliff:g> 받는 중"</string>
     <string name="notification_received" msgid="3324588019186687985">"블루투스 공유: <xliff:g id="FILE">%1$s</xliff:g> 받음"</string>
     <string name="notification_received_fail" msgid="3619350997285714746">"블루투스 공유: <xliff:g id="FILE">%1$s</xliff:g> 파일이 수신되지 않았습니다."</string>
@@ -83,8 +83,8 @@
     <string name="bt_toast_4" msgid="4678812947604395649">"\'<xliff:g id="RECIPIENT">%1$s</xliff:g>\'님에게 파일을 보내는 중"</string>
     <string name="bt_toast_5" msgid="2846870992823019494">"\'<xliff:g id="RECIPIENT">%2$s</xliff:g>\'에 <xliff:g id="NUMBER">%1$s</xliff:g>개 파일을 보내는 중"</string>
     <string name="bt_toast_6" msgid="1855266596936622458">"\'<xliff:g id="RECIPIENT">%1$s</xliff:g>\'님에게 파일 보내기가 중지되었습니다."</string>
-    <string name="bt_sm_2_1_nosdcard" msgid="1791835163301501637">"USB 저장소 공간이 부족해 \'<xliff:g id="SENDER">%1$s</xliff:g>\'님이 보낸 파일을 저장할 수 없습니다."</string>
-    <string name="bt_sm_2_1_default" msgid="9115512207909504071">"SD 카드 공간이 부족해 \'<xliff:g id="SENDER">%1$s</xliff:g>\'님이 보낸 파일을 저장할 수 없습니다."</string>
+    <string name="bt_sm_2_1_nosdcard" msgid="5354343190503952837">"USB 저장소에 파일을 저장할 공간이 부족합니다."</string>
+    <string name="bt_sm_2_1_default" msgid="2497541206648973852">"SD 카드에 파일을 저장할 공간이 부족합니다."</string>
     <string name="bt_sm_2_2" msgid="2965243265852680543">"필요한 공간: <xliff:g id="SIZE">%1$s</xliff:g>"</string>
     <string name="ErrorTooManyRequests" msgid="8578277541472944529">"처리 중인 요청이 너무 많습니다. 잠시 후에 다시 시도해 주세요."</string>
     <string name="status_pending" msgid="2503691772030877944">"파일 전송을 시작하지 않았습니다."</string>
diff --git a/res/values-ky/strings.xml b/res/values-ky/strings.xml
index fe94e64..d0081ac 100644
--- a/res/values-ky/strings.xml
+++ b/res/values-ky/strings.xml
@@ -18,8 +18,8 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="permlab_bluetoothShareManager" msgid="311492132450338925">"Жүктөө менежерине жетүү."</string>
     <string name="permdesc_bluetoothShareManager" msgid="8930572979123190223">"Колдонмого BluetoothАлмашуу менежерине жетип, ал аркылуу файлдарды өткөрүү уруксатын берет."</string>
-    <string name="permlab_bluetoothWhitelist" msgid="7091552898592306386">"Bluetooth түзмөгүнүн Таза тизмесине жетки берүү."</string>
-    <string name="permdesc_bluetoothWhitelist" msgid="5494513855192170109">"Колдонмого Bluetooth түзмөгүн таза тизмеге убактылуу жайгаштырып, колдонуучунун ырастоосусуз, ал түзмөктөн бул түзмөккө файлдарды жөнөтүү уруксатын берет."</string>
+    <string name="permlab_bluetoothAcceptlist" msgid="2647575807648622053">"Bluetooth түзмөгүнө кирүү мүмкүнчүлүгүн уруксат берилгендердин тизмесине жайгаштыруу."</string>
+    <string name="permdesc_bluetoothAcceptlist" msgid="2406423665674766442">"Колдонмого Bluetooth түзмөгүн уруксат берилгендердин тизмесине убактылуу жайгаштырып, колдонуучунун уруксатысыз файлдарды ал түзмөктөн бул түзмөккө жөнөтүү мүмкүнчүлүгүн берет."</string>
     <string name="bt_share_picker_label" msgid="6268100924487046932">"Bluetooth"</string>
     <string name="unknown_device" msgid="9221903979877041009">"Белгисиз түзмөк"</string>
     <string name="unknownNumber" msgid="4994750948072751566">"Белгисиз"</string>
@@ -37,7 +37,7 @@
     <string name="incoming_file_confirm_timeout_ok" msgid="1414676773249857278">"OK"</string>
     <string name="incoming_file_confirm_timeout_content" msgid="172779756093975981">"\"<xliff:g id="SENDER">%1$s</xliff:g>\" жөнөткөн файлды алуу мөөнөтү өтүп кетти."</string>
     <string name="incoming_file_confirm_Notification_title" msgid="5573329005298936903">"Кирүүчү файл"</string>
-    <string name="incoming_file_confirm_Notification_content" msgid="3359694069319644738">"<xliff:g id="SENDER">%1$s</xliff:g> <xliff:g id="FILE">%2$s</xliff:g> жөнөтүүгө даяр"</string>
+    <string name="incoming_file_confirm_Notification_content" msgid="6237487486781004992">"<xliff:g id="SENDER">%1$s</xliff:g> түзмөгү <xliff:g id="FILE">%2$s</xliff:g> файлын жөнөтүүгө даяр"</string>
     <string name="notification_receiving" msgid="4674648179652543984">"Bluetooth бөлүшүү: <xliff:g id="FILE">%1$s</xliff:g> алынууда"</string>
     <string name="notification_received" msgid="3324588019186687985">"Bluetooth бөлүшүү: <xliff:g id="FILE">%1$s</xliff:g> алынды"</string>
     <string name="notification_received_fail" msgid="3619350997285714746">"Bluetooth бөлүшүү: <xliff:g id="FILE">%1$s</xliff:g> алынган жок"</string>
@@ -83,8 +83,8 @@
     <string name="bt_toast_4" msgid="4678812947604395649">"Кийинкиге файл жөнөтүлүүдө: \"<xliff:g id="RECIPIENT">%1$s</xliff:g>\""</string>
     <string name="bt_toast_5" msgid="2846870992823019494">"\"<xliff:g id="RECIPIENT">%2$s</xliff:g>\" дарегине <xliff:g id="NUMBER">%1$s</xliff:g> файл жөнөтүлүүдө"</string>
     <string name="bt_toast_6" msgid="1855266596936622458">"\"<xliff:g id="RECIPIENT">%1$s</xliff:g>\" дарегине файл жөнөтүү токтотулду"</string>
-    <string name="bt_sm_2_1_nosdcard" msgid="1791835163301501637">"\"<xliff:g id="SENDER">%1$s</xliff:g>\" жөнөткөн файлды сактоо үчүн USB эстутумунда орун жетишсиз"</string>
-    <string name="bt_sm_2_1_default" msgid="9115512207909504071">"\"<xliff:g id="SENDER">%1$s</xliff:g>\" жөнөткөн файлды сактоо үчүн SD-картада орун жетишсиз"</string>
+    <string name="bt_sm_2_1_nosdcard" msgid="5354343190503952837">"Файлды сактоо үчүн USB сактагычында орун жетишсиз."</string>
+    <string name="bt_sm_2_1_default" msgid="2497541206648973852">"Файлды сактоо үчүн SD-картада орун жетишсиз."</string>
     <string name="bt_sm_2_2" msgid="2965243265852680543">"Керектүү орун: <xliff:g id="SIZE">%1$s</xliff:g>"</string>
     <string name="ErrorTooManyRequests" msgid="8578277541472944529">"Өтө көп талаптар иштетилүүдө. Кайта аракеттениңиз."</string>
     <string name="status_pending" msgid="2503691772030877944">"Файл өткөрүү баштала элек."</string>
diff --git a/res/values-lo/strings.xml b/res/values-lo/strings.xml
index 616ebcc..6fa15b0 100644
--- a/res/values-lo/strings.xml
+++ b/res/values-lo/strings.xml
@@ -18,8 +18,8 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="permlab_bluetoothShareManager" msgid="311492132450338925">"ເຂົ້າເຖິງໂຕຈັດການການດາວໂຫລດ​."</string>
     <string name="permdesc_bluetoothShareManager" msgid="8930572979123190223">"ອະນຸຍາດໃຫ້ແອັບຯນີ້ເຂົາເຖິງໂຕຈັດການ BluetoothShare ແລະໃຊ້ມັນເພື່ອສົ່ງໄຟລ໌."</string>
-    <string name="permlab_bluetoothWhitelist" msgid="7091552898592306386">"ເຮັດບັນຊີອະນຸຍາດການເຂົ້າເຖິງອຸປະກອນ bluetooth."</string>
-    <string name="permdesc_bluetoothWhitelist" msgid="5494513855192170109">"ອະນຸຍາດໃຫ້ແອັບຯດັ່ງກ່າວຕັ້ງບັນຊີຂາວໃຫ້ກັບອຸປະກອນ Bluetooth ຊົ່ວຄາວ, ການອະນຸຍາດໃຫ້ອຸປະກອນນັ້ນສົ່ງໄຟລ໌ມາທີ່ອຸປະກອນນີ້ໂດຍທີ່ບໍ່ຈຳເປັນຕ້ອງມີການຢືນຢັນຈາກຜູ່ໃຊ້."</string>
+    <string name="permlab_bluetoothAcceptlist" msgid="2647575807648622053">"ສ້າງລາຍຊື່ຍອມຮັບການເຂົ້າເຖິງອຸປະກອນ Bluetooth."</string>
+    <string name="permdesc_bluetoothAcceptlist" msgid="2406423665674766442">"ອະນຸຍາດໃຫ້ແອັບສ້າງລາຍຊື່ຍອມຮັບການເຂົ້າເຖິງອຸປະກອນ Bluetooth ຊົ່ວຄາວໄດ້, ເຊິ່ງຈະເຮັດໃຫ້ອຸປະກອນນັ້ນສົ່ງໄຟລ໌ໄປຫາອຸປະກອນນີ້ໄດ້ໂດຍບໍ່ຕ້ອງໃຫ້ຜູ້ໃຊ້ຢືນຢັນ."</string>
     <string name="bt_share_picker_label" msgid="6268100924487046932">"Bluetooth"</string>
     <string name="unknown_device" msgid="9221903979877041009">"ອຸປະກອນທີ່ບໍ່ຮູ້ຈັກ"</string>
     <string name="unknownNumber" msgid="4994750948072751566">"ບໍ່ຮູ້ຈັກ"</string>
@@ -37,7 +37,7 @@
     <string name="incoming_file_confirm_timeout_ok" msgid="1414676773249857278">"ຕົກລົງ"</string>
     <string name="incoming_file_confirm_timeout_content" msgid="172779756093975981">"ເກີດມີການໝົດເວລາໃນລະຫວ່າງທີ່ກຳລັງຮັບເອົາໄຟລ໌ທີ່ເຂົ້າມາຈາກ \"<xliff:g id="SENDER">%1$s</xliff:g>\""</string>
     <string name="incoming_file_confirm_Notification_title" msgid="5573329005298936903">"ໄຟ​ລ໌​ເຂົ້າ​ມາ"</string>
-    <string name="incoming_file_confirm_Notification_content" msgid="3359694069319644738">"<xliff:g id="SENDER">%1$s</xliff:g> ພ້ອມ​ທີ່​ຈະ​ສົ່ງ <xliff:g id="FILE">%2$s</xliff:g>"</string>
+    <string name="incoming_file_confirm_Notification_content" msgid="6237487486781004992">"<xliff:g id="SENDER">%1$s</xliff:g> ພ້ອມສົ່ງໄຟລ໌ແລ້ວ: <xliff:g id="FILE">%2$s</xliff:g>"</string>
     <string name="notification_receiving" msgid="4674648179652543984">"ແບ່ງປັນໃນ Bluetooth: ກຳລັງຮັບເອົາ <xliff:g id="FILE">%1$s</xliff:g>"</string>
     <string name="notification_received" msgid="3324588019186687985">"ແບ່ງປັນໃນ Bluetooth: ໄດ້ຮັບ <xliff:g id="FILE">%1$s</xliff:g> ແລ້ວ"</string>
     <string name="notification_received_fail" msgid="3619350997285714746">"ແບ່ງປັນໃນ Bluetooth: ບໍ່ໄດ້ຮັບ <xliff:g id="FILE">%1$s</xliff:g>"</string>
@@ -83,8 +83,8 @@
     <string name="bt_toast_4" msgid="4678812947604395649">"ກຳລັງສົ່ງໄຟລ໌ໄປຫາ \"<xliff:g id="RECIPIENT">%1$s</xliff:g>\""</string>
     <string name="bt_toast_5" msgid="2846870992823019494">"ກຳລັງສົ່ງ <xliff:g id="NUMBER">%1$s</xliff:g> ໄຟລ​໌​ໄປຫາ \"<xliff:g id="RECIPIENT">%2$s</xliff:g>\""</string>
     <string name="bt_toast_6" msgid="1855266596936622458">"ຢຸດການສົ່ງໄຟລ໌ໄປຫາ \"<xliff:g id="RECIPIENT">%1$s</xliff:g>\" ແລ້ວ"</string>
-    <string name="bt_sm_2_1_nosdcard" msgid="1791835163301501637">"ບ່ອນຈັດເກັບຂໍ້ມູນ USB ບໍ່ພຽງພໍທີ່ຈະບັນທຶກໄຟລ໌ດັ່ງກ່າວຈາກ \"<xliff:g id="SENDER">%1$s</xliff:g>\" ໄດ້"</string>
-    <string name="bt_sm_2_1_default" msgid="9115512207909504071">"ບ່ອນຈັດເກັບຂໍ້ມູນ SD ກາດບໍ່ພຽງພໍທີ່ຈະບັນທຶກໄຟລ໌ດັ່ງກ່າວຈາກ \"<xliff:g id="SENDER">%1$s</xliff:g>\" ໄດ້"</string>
+    <string name="bt_sm_2_1_nosdcard" msgid="5354343190503952837">"ມີພື້ນທີ່ຢູ່ບ່ອນຈັດເກັບຂໍ້ມູນ USB ບໍ່ພຽງພໍໃຫ້ບັນທຶກໄຟລ໌."</string>
+    <string name="bt_sm_2_1_default" msgid="2497541206648973852">"ມີພື້ນທີ່ຢູ່ SD card ບໍ່ພຽງພໍໃຫ້ບັນທຶກໄຟລ໌."</string>
     <string name="bt_sm_2_2" msgid="2965243265852680543">"ພື້ນທີ່ທີ່ຕ້ອງການ​: <xliff:g id="SIZE">%1$s</xliff:g>"</string>
     <string name="ErrorTooManyRequests" msgid="8578277541472944529">"ມີການຮ້ອງຂໍຫຼາຍເກີນໄປ, ກະລຸນາລອງໃໝ່ພາຍຫຼັງ."</string>
     <string name="status_pending" msgid="2503691772030877944">"ການໂອນໄຟລ໌ຍັງບໍ່ທັນໄດ້ເລີ່ມເທື່ອ."</string>
diff --git a/res/values-lt/strings.xml b/res/values-lt/strings.xml
index 0213fd0..daa7ea9 100644
--- a/res/values-lt/strings.xml
+++ b/res/values-lt/strings.xml
@@ -18,8 +18,8 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="permlab_bluetoothShareManager" msgid="311492132450338925">"Pasiekti atsisiuntimo tvarkytuvę."</string>
     <string name="permdesc_bluetoothShareManager" msgid="8930572979123190223">"Leidžiama programai pasiekti „BluetoothShare“ tvarkyklę ir naudoti ją failams perkelti."</string>
-    <string name="permlab_bluetoothWhitelist" msgid="7091552898592306386">"Į baltąjį sąrašą įtraukto „Bluetooth“ įrenginio prieiga."</string>
-    <string name="permdesc_bluetoothWhitelist" msgid="5494513855192170109">"Leidžiama programai laikinai į baltąjį sąrašą įtraukti „Bluetooth“ įrenginį, suteikiant jam galimybę siųsti failus į šį įrenginį be naudotojo patvirtinimo."</string>
+    <string name="permlab_bluetoothAcceptlist" msgid="2647575807648622053">"Į leidžiamųjų sąrašą įtraukto „Bluetooth“ įrenginio prieiga."</string>
+    <string name="permdesc_bluetoothAcceptlist" msgid="2406423665674766442">"Programai leidžiama laikinai į leidžiamųjų sąrašą įtraukti „Bluetooth“ įrenginį, suteikiant jam galimybę siųsti failus į šį įrenginį be naudotojo patvirtinimo."</string>
     <string name="bt_share_picker_label" msgid="6268100924487046932">"Bluetooth"</string>
     <string name="unknown_device" msgid="9221903979877041009">"Nežinomas įrenginys"</string>
     <string name="unknownNumber" msgid="4994750948072751566">"Nežinoma"</string>
@@ -37,7 +37,7 @@
     <string name="incoming_file_confirm_timeout_ok" msgid="1414676773249857278">"Gerai"</string>
     <string name="incoming_file_confirm_timeout_content" msgid="172779756093975981">"Baigėsi laikas priimant gaunamą failą iš <xliff:g id="SENDER">%1$s</xliff:g>"</string>
     <string name="incoming_file_confirm_Notification_title" msgid="5573329005298936903">"Gaunamas failas"</string>
-    <string name="incoming_file_confirm_Notification_content" msgid="3359694069319644738">"<xliff:g id="SENDER">%1$s</xliff:g> pasiruošęs (-usi) siųsti <xliff:g id="FILE">%2$s</xliff:g>"</string>
+    <string name="incoming_file_confirm_Notification_content" msgid="6237487486781004992">"Įrenginys („<xliff:g id="SENDER">%1$s</xliff:g>“) pasirengęs siųsti failą: „<xliff:g id="FILE">%2$s</xliff:g>“"</string>
     <string name="notification_receiving" msgid="4674648179652543984">"„Bluetooth“ bendrinimas: gaunamas <xliff:g id="FILE">%1$s</xliff:g>"</string>
     <string name="notification_received" msgid="3324588019186687985">"„Bluetooth“ bendrinimas: <xliff:g id="FILE">%1$s</xliff:g> gautas"</string>
     <string name="notification_received_fail" msgid="3619350997285714746">"„Bluetooth“ bendrinimas: <xliff:g id="FILE">%1$s</xliff:g> failas negautas"</string>
@@ -83,8 +83,8 @@
     <string name="bt_toast_4" msgid="4678812947604395649">"<xliff:g id="RECIPIENT">%1$s</xliff:g> siunčiamas failas"</string>
     <string name="bt_toast_5" msgid="2846870992823019494">"<xliff:g id="RECIPIENT">%2$s</xliff:g> siunčiami (-a) <xliff:g id="NUMBER">%1$s</xliff:g> failai (-ų)"</string>
     <string name="bt_toast_6" msgid="1855266596936622458">"Sustabdytas failo siuntimas <xliff:g id="RECIPIENT">%1$s</xliff:g>"</string>
-    <string name="bt_sm_2_1_nosdcard" msgid="1791835163301501637">"USB atmintyje nėra pakankamai vietos išsaugoti failą iš „<xliff:g id="SENDER">%1$s</xliff:g>“"</string>
-    <string name="bt_sm_2_1_default" msgid="9115512207909504071">"SD kortelėje nepakanka vietos išsaugoti failą iš „<xliff:g id="SENDER">%1$s</xliff:g>“"</string>
+    <string name="bt_sm_2_1_nosdcard" msgid="5354343190503952837">"USB atmintyje nėra pakankamai vietos, kad būtų galima išsaugoti failą."</string>
+    <string name="bt_sm_2_1_default" msgid="2497541206648973852">"SD kortelėje nepakanka vietos, kad būtų galima išsaugoti failą."</string>
     <string name="bt_sm_2_2" msgid="2965243265852680543">"Reikalinga vieta: <xliff:g id="SIZE">%1$s</xliff:g>"</string>
     <string name="ErrorTooManyRequests" msgid="8578277541472944529">"Apdorojama per daug užklausų. Vėliau bandykite dar kartą."</string>
     <string name="status_pending" msgid="2503691772030877944">"Failo perkėlimas dar nepradėtas."</string>
diff --git a/res/values-lv/strings.xml b/res/values-lv/strings.xml
index 2c95751..ab17a5f 100644
--- a/res/values-lv/strings.xml
+++ b/res/values-lv/strings.xml
@@ -18,8 +18,8 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="permlab_bluetoothShareManager" msgid="311492132450338925">"Piekļuve lietojumprogrammai Download Manager."</string>
     <string name="permdesc_bluetoothShareManager" msgid="8930572979123190223">"Ļauj lietotnei piekļūt BluetoothShare pārvaldniekam, lai to izmantotu failu pārsūtīšanai."</string>
-    <string name="permlab_bluetoothWhitelist" msgid="7091552898592306386">"Iekļaut Bluetooth ierīces piekļuvi baltajā sarakstā."</string>
-    <string name="permdesc_bluetoothWhitelist" msgid="5494513855192170109">"Ļauj lietotnei īslaicīgi iekļaut Bluetooth ierīci baltajā sarakstā, lai no šīs ierīces bez lietotāja apstiprinājuma varētu sūtīt failus uz šo ierīci."</string>
+    <string name="permlab_bluetoothAcceptlist" msgid="2647575807648622053">"Iekļaut Bluetooth ierīces piekļuvi atļaušanas sarakstā."</string>
+    <string name="permdesc_bluetoothAcceptlist" msgid="2406423665674766442">"Ļauj lietotnei īslaicīgi iekļaut Bluetooth ierīci atļaušanas sarakstā, lai Bluetooth ierīce bez lietotāja apstiprinājuma varētu sūtīt failus uz pašreizējo ierīci."</string>
     <string name="bt_share_picker_label" msgid="6268100924487046932">"Bluetooth"</string>
     <string name="unknown_device" msgid="9221903979877041009">"Nezināma ierīce"</string>
     <string name="unknownNumber" msgid="4994750948072751566">"Nezināms"</string>
@@ -37,7 +37,7 @@
     <string name="incoming_file_confirm_timeout_ok" msgid="1414676773249857278">"Labi"</string>
     <string name="incoming_file_confirm_timeout_content" msgid="172779756093975981">"Saņemot ienākošu failu no saņēmēja <xliff:g id="SENDER">%1$s</xliff:g>, radās noildze."</string>
     <string name="incoming_file_confirm_Notification_title" msgid="5573329005298936903">"Ienākošais fails"</string>
-    <string name="incoming_file_confirm_Notification_content" msgid="3359694069319644738">"Sūtītājs <xliff:g id="SENDER">%1$s</xliff:g> ir gatavs nosūtīt failu <xliff:g id="FILE">%2$s</xliff:g>."</string>
+    <string name="incoming_file_confirm_Notification_content" msgid="6237487486781004992">"Lietotājs <xliff:g id="SENDER">%1$s</xliff:g> ir gatavs nosūtīt failu: <xliff:g id="FILE">%2$s</xliff:g>"</string>
     <string name="notification_receiving" msgid="4674648179652543984">"Kopīgošana, izmantojot Bluetooth: notiek faila <xliff:g id="FILE">%1$s</xliff:g> saņemšana"</string>
     <string name="notification_received" msgid="3324588019186687985">"Kopīgošana, izmantojot Bluetooth: saņemts fails <xliff:g id="FILE">%1$s</xliff:g>"</string>
     <string name="notification_received_fail" msgid="3619350997285714746">"Kopīgošana, izmantojot Bluetooth: fails <xliff:g id="FILE">%1$s</xliff:g> netika saņemts"</string>
@@ -83,8 +83,8 @@
     <string name="bt_toast_4" msgid="4678812947604395649">"Notiek faila sūtīšana saņēmējam <xliff:g id="RECIPIENT">%1$s</xliff:g>"</string>
     <string name="bt_toast_5" msgid="2846870992823019494">"Notiek <xliff:g id="NUMBER">%1$s</xliff:g> failu sūtīšana saņēmējam <xliff:g id="RECIPIENT">%2$s</xliff:g>."</string>
     <string name="bt_toast_6" msgid="1855266596936622458">"Faila sūtīšana saņēmējam <xliff:g id="RECIPIENT">%1$s</xliff:g> tika apturēta."</string>
-    <string name="bt_sm_2_1_nosdcard" msgid="1791835163301501637">"USB atmiņā nepietiek vietas, lai saglabātu failu no sūtītāja <xliff:g id="SENDER">%1$s</xliff:g>."</string>
-    <string name="bt_sm_2_1_default" msgid="9115512207909504071">"SD kartē nepietiek vietas, lai saglabātu failu no sūtītāja <xliff:g id="SENDER">%1$s</xliff:g>."</string>
+    <string name="bt_sm_2_1_nosdcard" msgid="5354343190503952837">"USB atmiņā nepietiek vietas, lai saglabātu failu."</string>
+    <string name="bt_sm_2_1_default" msgid="2497541206648973852">"SD kartē nepietiek vietas, lai saglabātu failu."</string>
     <string name="bt_sm_2_2" msgid="2965243265852680543">"Nepieciešamā brīvā vieta: <xliff:g id="SIZE">%1$s</xliff:g>"</string>
     <string name="ErrorTooManyRequests" msgid="8578277541472944529">"Tiek apstrādāts pārāk liels pieprasījumu skaits. Vēlāk mēģiniet vēlreiz."</string>
     <string name="status_pending" msgid="2503691772030877944">"Faila pārsūtīšana vēl nav sākta."</string>
diff --git a/res/values-mk/strings.xml b/res/values-mk/strings.xml
index c5a3bbb..d16fe8e 100644
--- a/res/values-mk/strings.xml
+++ b/res/values-mk/strings.xml
@@ -18,8 +18,8 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="permlab_bluetoothShareManager" msgid="311492132450338925">"Пристапи кон управувач за преземања."</string>
     <string name="permdesc_bluetoothShareManager" msgid="8930572979123190223">"Овозможува апликацијата да пристапи до менаџерот на BluetoothShare и да го користи за пренос на датотеки."</string>
-    <string name="permlab_bluetoothWhitelist" msgid="7091552898592306386">"Пристап на Bluetooth уред од белата листа."</string>
-    <string name="permdesc_bluetoothWhitelist" msgid="5494513855192170109">"Овозможува апликацијата привремено да го стави Bluetooth уредот на белата листа, со што му овозможува на уредот да испрати датотека до овој уред без потврда на корисникот."</string>
+    <string name="permlab_bluetoothAcceptlist" msgid="2647575807648622053">"Пристап до уредот со Bluetooth на белата листа."</string>
+    <string name="permdesc_bluetoothAcceptlist" msgid="2406423665674766442">"Дозволува апликацијата привремено да стави на белата листа уред со Bluetooth на којшто ќе му овозможи испраќање датотеки до овој уред без потврда од корисникот."</string>
     <string name="bt_share_picker_label" msgid="6268100924487046932">"Bluetooth"</string>
     <string name="unknown_device" msgid="9221903979877041009">"Непознат уред"</string>
     <string name="unknownNumber" msgid="4994750948072751566">"Непознат"</string>
@@ -37,7 +37,7 @@
     <string name="incoming_file_confirm_timeout_ok" msgid="1414676773249857278">"Во ред"</string>
     <string name="incoming_file_confirm_timeout_content" msgid="172779756093975981">"Времето истече додека се прифаќаше дојдовната датотека од „<xliff:g id="SENDER">%1$s</xliff:g>“."</string>
     <string name="incoming_file_confirm_Notification_title" msgid="5573329005298936903">"Дојдовна датотека"</string>
-    <string name="incoming_file_confirm_Notification_content" msgid="3359694069319644738">"<xliff:g id="SENDER">%1$s</xliff:g> е подготвен за испраќање на <xliff:g id="FILE">%2$s</xliff:g>"</string>
+    <string name="incoming_file_confirm_Notification_content" msgid="6237487486781004992">"<xliff:g id="SENDER">%1$s</xliff:g> е подготвен да испрати датотека: <xliff:g id="FILE">%2$s</xliff:g>"</string>
     <string name="notification_receiving" msgid="4674648179652543984">"Сподели преку Bluetooth: Се прима <xliff:g id="FILE">%1$s</xliff:g>"</string>
     <string name="notification_received" msgid="3324588019186687985">"Сподели преку Bluetooth: Примена <xliff:g id="FILE">%1$s</xliff:g>"</string>
     <string name="notification_received_fail" msgid="3619350997285714746">"Сподели преку Bluetooth: Датотеката <xliff:g id="FILE">%1$s</xliff:g> не е примена."</string>
@@ -83,8 +83,8 @@
     <string name="bt_toast_4" msgid="4678812947604395649">"Се испраќа датотека до „<xliff:g id="RECIPIENT">%1$s</xliff:g>“"</string>
     <string name="bt_toast_5" msgid="2846870992823019494">"Се испраќаат <xliff:g id="NUMBER">%1$s</xliff:g> датотеки до „<xliff:g id="RECIPIENT">%2$s</xliff:g>“"</string>
     <string name="bt_toast_6" msgid="1855266596936622458">"Испраќањето на датотеката до „<xliff:g id="RECIPIENT">%1$s</xliff:g>“ запре"</string>
-    <string name="bt_sm_2_1_nosdcard" msgid="1791835163301501637">"Нема доволно простор во USB-меморијата за да се зачува датотеката од „<xliff:g id="SENDER">%1$s</xliff:g>’"</string>
-    <string name="bt_sm_2_1_default" msgid="9115512207909504071">"Нема доволно простор на SD-картичката за да се зачува датотеката од „<xliff:g id="SENDER">%1$s</xliff:g>“"</string>
+    <string name="bt_sm_2_1_nosdcard" msgid="5354343190503952837">"Нема доволно простор на USB-меморијата за да се зачува датотеката."</string>
+    <string name="bt_sm_2_1_default" msgid="2497541206648973852">"Нема доволно простор на SD-картичката за да се зачува датотеката."</string>
     <string name="bt_sm_2_2" msgid="2965243265852680543">"Потребен простор: <xliff:g id="SIZE">%1$s</xliff:g>"</string>
     <string name="ErrorTooManyRequests" msgid="8578277541472944529">"Се обработуваат премногу барања. Обидете се повторно подоцна."</string>
     <string name="status_pending" msgid="2503691772030877944">"Преносот на датотека сè уште не е започнат."</string>
diff --git a/res/values-ml/strings.xml b/res/values-ml/strings.xml
index 2ab2677..2d6843c 100644
--- a/res/values-ml/strings.xml
+++ b/res/values-ml/strings.xml
@@ -18,8 +18,8 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="permlab_bluetoothShareManager" msgid="311492132450338925">"ഡൗൺലോഡ് മാനേജർ ആക്‌സസ്സ് ചെയ്യുക."</string>
     <string name="permdesc_bluetoothShareManager" msgid="8930572979123190223">"BluetoothShare മാനേജർ ആക്‌സസ്സുചെയ്യാനും ഫയലുകൾ കൈമാറാൻ അത് ഉപയോഗിക്കാനും അപ്ലിക്കേഷനെ അനുവദിക്കുന്നു."</string>
-    <string name="permlab_bluetoothWhitelist" msgid="7091552898592306386">"വൈറ്റ്‌ലിസ്റ്റ് ബ്ലൂടൂത്ത് ഉപകരണ ആക്‌സസ്സ്."</string>
-    <string name="permdesc_bluetoothWhitelist" msgid="5494513855192170109">"ഒരു ബ്ലൂടൂത്ത് ഉപകരണം താൽക്കാലികമായി വൈറ്റ്‌ലിസ്റ്റുചെയ്യാൻ അപ്ലിക്കേഷനെ അനുവദിക്കുന്നു, അത് ഉപയോക്താവിന്റെ സ്ഥിരീകരണമില്ലാതെ ഈ ഉപകരണത്തിലേക്ക് ഫയലുകൾ അയയ്‌ക്കാൻ ആ ഉപകരണത്തെ അനുവദിക്കുന്നു."</string>
+    <string name="permlab_bluetoothAcceptlist" msgid="2647575807648622053">"Bluetooth ഉപകരണത്തിലേക്കുള്ള ആക്സസിന്റെ അനുമതി ലിസ്റ്റ്."</string>
+    <string name="permdesc_bluetoothAcceptlist" msgid="2406423665674766442">"ഒരു Bluetooth ഉപകരണം താൽക്കാലികമായി അനുമതി ലിസ്റ്റിൽ ചേർക്കാൻ ആപ്പിനെ അനുവദിക്കുന്നു, അത് ഉപയോക്താവിന്റെ സ്ഥിരീകരണമില്ലാതെ ഈ ഉപകരണത്തിലേക്ക് ഫയലുകൾ അയയ്‌ക്കാൻ ആ ഉപകരണത്തെ അനുവദിക്കുന്നു."</string>
     <string name="bt_share_picker_label" msgid="6268100924487046932">"Bluetooth"</string>
     <string name="unknown_device" msgid="9221903979877041009">"അജ്ഞാത ഉപകരണം"</string>
     <string name="unknownNumber" msgid="4994750948072751566">"അറിയില്ല"</string>
@@ -37,7 +37,7 @@
     <string name="incoming_file_confirm_timeout_ok" msgid="1414676773249857278">"ശരി"</string>
     <string name="incoming_file_confirm_timeout_content" msgid="172779756093975981">"\"<xliff:g id="SENDER">%1$s</xliff:g>\" -ൽ നിന്നും ഇൻകമിംഗ് ഫയൽ സ്വീകരിക്കുമ്പോൾ സമയപരിധി കഴിഞ്ഞിരുന്നു"</string>
     <string name="incoming_file_confirm_Notification_title" msgid="5573329005298936903">"ഇൻകമിംഗ് ഫയൽ"</string>
-    <string name="incoming_file_confirm_Notification_content" msgid="3359694069319644738">"<xliff:g id="FILE">%2$s</xliff:g> അയയ്ക്കാൻ <xliff:g id="SENDER">%1$s</xliff:g> തയ്യാറാണ്"</string>
+    <string name="incoming_file_confirm_Notification_content" msgid="6237487486781004992">"<xliff:g id="SENDER">%1$s</xliff:g>, ഫയൽ അയയ്‌ക്കാൻ തയ്യാറാണ്: <xliff:g id="FILE">%2$s</xliff:g>"</string>
     <string name="notification_receiving" msgid="4674648179652543984">"ബ്ലൂടൂത്ത് പങ്കിടൽ: <xliff:g id="FILE">%1$s</xliff:g> എന്ന ഫയൽ ലഭിക്കുന്നു"</string>
     <string name="notification_received" msgid="3324588019186687985">"ബ്ലൂടൂത്ത് പങ്കിടൽ: <xliff:g id="FILE">%1$s</xliff:g> ലഭിച്ചു"</string>
     <string name="notification_received_fail" msgid="3619350997285714746">"ബ്ലൂടൂത്ത് പങ്കിടൽ: <xliff:g id="FILE">%1$s</xliff:g> എന്ന ഫയൽ ലഭിച്ചില്ല."</string>
@@ -83,8 +83,8 @@
     <string name="bt_toast_4" msgid="4678812947604395649">"\"<xliff:g id="RECIPIENT">%1$s</xliff:g>\" എന്നയാൾക്ക് ഫയൽ അയയ്‌ക്കുന്നു"</string>
     <string name="bt_toast_5" msgid="2846870992823019494">"\"<xliff:g id="RECIPIENT">%2$s</xliff:g>\" എന്നയാൾക്ക് <xliff:g id="NUMBER">%1$s</xliff:g> ഫയലുകൾ അയയ്‌ക്കുന്നു"</string>
     <string name="bt_toast_6" msgid="1855266596936622458">"\"<xliff:g id="RECIPIENT">%1$s</xliff:g>\" എന്നയാൾക്ക് ഫയൽ അയയ്‌ക്കുന്നത് നിർത്തി"</string>
-    <string name="bt_sm_2_1_nosdcard" msgid="1791835163301501637">"\"<xliff:g id="SENDER">%1$s</xliff:g>\" എന്നയാളിൽ നിന്നുള്ള ഫയൽ സംരക്ഷിക്കാൻ USB സ്‌റ്റോറേജിൽ ആവശ്യമായ ഇടമില്ല."</string>
-    <string name="bt_sm_2_1_default" msgid="9115512207909504071">"\"<xliff:g id="SENDER">%1$s</xliff:g>\" എന്നയാളിൽ നിന്നുള്ള ഫയൽ സംരക്ഷിക്കാൻ SD കാർഡിൽ ആവശ്യമായ ഇടമില്ല."</string>
+    <string name="bt_sm_2_1_nosdcard" msgid="5354343190503952837">"ഫയൽ സംരക്ഷിക്കാൻ ആവശ്യമായ ഇടം USB സ്‌റ്റോറേജിൽ ഇല്ല."</string>
+    <string name="bt_sm_2_1_default" msgid="2497541206648973852">"ഫയൽ സംരക്ഷിക്കാൻ ആവശ്യമായ ഇടം SD കാർഡിൽ ഇല്ല."</string>
     <string name="bt_sm_2_2" msgid="2965243265852680543">"ആവശ്യമായ ഇടം: <xliff:g id="SIZE">%1$s</xliff:g>"</string>
     <string name="ErrorTooManyRequests" msgid="8578277541472944529">"നിരവധി അഭ്യർത്ഥനകൾ പ്രോസസ്സ് ചെയ്യുന്നു. പിന്നീട് വീണ്ടും ശ്രമിക്കുക."</string>
     <string name="status_pending" msgid="2503691772030877944">"ഫയൽ കൈമാറ്റം ഇതുവരെ ആരംഭിച്ചിട്ടില്ല."</string>
diff --git a/res/values-mn/strings.xml b/res/values-mn/strings.xml
index b1251bb..e7886e6 100644
--- a/res/values-mn/strings.xml
+++ b/res/values-mn/strings.xml
@@ -18,8 +18,8 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="permlab_bluetoothShareManager" msgid="311492132450338925">"Татан авалтын менежерт хандалт хийх."</string>
     <string name="permdesc_bluetoothShareManager" msgid="8930572979123190223">"Апп-д BluetoothShare менежерт хандах, үүнийг ашиглан файл дамжуулах боломж олгоно."</string>
-    <string name="permlab_bluetoothWhitelist" msgid="7091552898592306386">"Цагаан жагсаалтаар bluetooth төхөөрөмжид хандалт хийх."</string>
-    <string name="permdesc_bluetoothWhitelist" msgid="5494513855192170109">"Апп-т Bluetooth төхөөрөмжийг цагаан жагсаалтад оруулж хэрэглэгчийн баталгаажуулалт шаардахгүйгээр тус төхөөрөмж рүү файл илгээх боломж олгоно."</string>
+    <string name="permlab_bluetoothAcceptlist" msgid="2647575807648622053">"Зөвшөөрөх жагсаалтын bluetooth төхөөрөмжийн хандалт."</string>
+    <string name="permdesc_bluetoothAcceptlist" msgid="2406423665674766442">"Аппад Bluetooth төхөөрөмжийг түр хугацаанд зөвшөөрөх жагсаалтад оруулж, хэрэглэгчийн баталгаажуулалтгүйгээр энэ төхөөрөмж рүү файл илгээх боломж олгоно."</string>
     <string name="bt_share_picker_label" msgid="6268100924487046932">"Bluetooth"</string>
     <string name="unknown_device" msgid="9221903979877041009">"Үл мэдэгдэх төхөөрөмж"</string>
     <string name="unknownNumber" msgid="4994750948072751566">"Тодорхойгүй"</string>
@@ -37,7 +37,7 @@
     <string name="incoming_file_confirm_timeout_ok" msgid="1414676773249857278">"OK"</string>
     <string name="incoming_file_confirm_timeout_content" msgid="172779756093975981">"\"<xliff:g id="SENDER">%1$s</xliff:g>\"-с ирж байгаа файлыг зөвшөөрөх явцад хугацаа хэтэрсэн байна"</string>
     <string name="incoming_file_confirm_Notification_title" msgid="5573329005298936903">"Ирж буй файл"</string>
-    <string name="incoming_file_confirm_Notification_content" msgid="3359694069319644738">"<xliff:g id="SENDER">%1$s</xliff:g> нь <xliff:g id="FILE">%2$s</xliff:g>-г илгээхэд бэлэн"</string>
+    <string name="incoming_file_confirm_Notification_content" msgid="6237487486781004992">"<xliff:g id="SENDER">%1$s</xliff:g> файлыг илгээхэд бэлэн байна: <xliff:g id="FILE">%2$s</xliff:g>"</string>
     <string name="notification_receiving" msgid="4674648179652543984">"Bluetooth хуваалцах: Хүлээн авч байна <xliff:g id="FILE">%1$s</xliff:g>"</string>
     <string name="notification_received" msgid="3324588019186687985">"Bluetooth хуваалцах: Хүлээн авсан <xliff:g id="FILE">%1$s</xliff:g>"</string>
     <string name="notification_received_fail" msgid="3619350997285714746">"Bluetooth хуваалцах: Файл <xliff:g id="FILE">%1$s</xliff:g> хүлээж аваагүй"</string>
@@ -83,8 +83,8 @@
     <string name="bt_toast_4" msgid="4678812947604395649">"\"<xliff:g id="RECIPIENT">%1$s</xliff:g>\" руу файлыг илгээж байна"</string>
     <string name="bt_toast_5" msgid="2846870992823019494">"<xliff:g id="NUMBER">%1$s</xliff:g> файлуудыг \"<xliff:g id="RECIPIENT">%2$s</xliff:g>\" руу илгээж байна"</string>
     <string name="bt_toast_6" msgid="1855266596936622458">"\"<xliff:g id="RECIPIENT">%1$s</xliff:g>\" руу файл илгээхийн зогсоосон"</string>
-    <string name="bt_sm_2_1_nosdcard" msgid="1791835163301501637">"USB санд \"<xliff:g id="SENDER">%1$s</xliff:g>\"-с ирсэн файлыг хадгалах хангалттай зай алга"</string>
-    <string name="bt_sm_2_1_default" msgid="9115512207909504071">"SD картад \"<xliff:g id="SENDER">%1$s</xliff:g>\"-с ирсэн файлыг хадгалах хангалттай зай алга"</string>
+    <string name="bt_sm_2_1_nosdcard" msgid="5354343190503952837">"USB сан дээр файлыг хадгалах хангалттай зай алга."</string>
+    <string name="bt_sm_2_1_default" msgid="2497541206648973852">"SD карт дээр файлыг хадгалах хангалттай зай алга."</string>
     <string name="bt_sm_2_2" msgid="2965243265852680543">"Шаардлагатай зай: <xliff:g id="SIZE">%1$s</xliff:g>"</string>
     <string name="ErrorTooManyRequests" msgid="8578277541472944529">"Хэт олон хүсэлтийг боловсруулж байна. Дараа дахин оролдоно уу."</string>
     <string name="status_pending" msgid="2503691772030877944">"Файл дамжуулалт хараахан эхлээгүй байна."</string>
diff --git a/res/values-mr/strings.xml b/res/values-mr/strings.xml
index 7ea9269..10ec29e 100644
--- a/res/values-mr/strings.xml
+++ b/res/values-mr/strings.xml
@@ -18,8 +18,8 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="permlab_bluetoothShareManager" msgid="311492132450338925">"डाउनलोड व्यवस्थापक अ‍ॅक्सेस करा."</string>
     <string name="permdesc_bluetoothShareManager" msgid="8930572979123190223">"अ‍ॅपला BluetoothShare व्‍यवस्‍थापकामध्‍ये प्रवेश करण्‍याची आणि फाइल स्‍थानांतरित करण्‍यासाठी त्याचा वापर करण्‍याची अनुमती देते."</string>
-    <string name="permlab_bluetoothWhitelist" msgid="7091552898592306386">"व्हाइटलिस्ट ब्लूटूथ डिव्हाइस अ‍ॅक्सेस."</string>
-    <string name="permdesc_bluetoothWhitelist" msgid="5494513855192170109">"डीव्‍हाइसला वापरकर्ता पुष्‍टीशिवाय या डीव्हाइसवर फाइल पाठविण्‍याची अनुमती देऊन ब्लूटूथ डीव्हाइसला तात्पुरते व्हाइटलिस्ट करण्‍याची अ‍ॅपला अनुमती देते."</string>
+    <string name="permlab_bluetoothAcceptlist" msgid="2647575807648622053">"ब्लूटूथ डिव्‍हाइस अ‍ॅक्सेस ॲक्सेप्टलिस्ट करा."</string>
+    <string name="permdesc_bluetoothAcceptlist" msgid="2406423665674766442">"त्या डिव्हाइसला वापरकर्ता पुष्‍टीशिवाय या डिव्हाइसवर फाइल पाठवण्‍याची अनुमती देऊन ब्लूटूथ डिव्‍हाइसला तात्पुरते ॲक्सेप्टलिस्ट करण्‍याची अ‍ॅपला अनुमती देते."</string>
     <string name="bt_share_picker_label" msgid="6268100924487046932">"ब्लूटूथ"</string>
     <string name="unknown_device" msgid="9221903979877041009">"अज्ञात डिव्हाइस"</string>
     <string name="unknownNumber" msgid="4994750948072751566">"अज्ञात"</string>
@@ -37,7 +37,7 @@
     <string name="incoming_file_confirm_timeout_ok" msgid="1414676773249857278">"ठीक"</string>
     <string name="incoming_file_confirm_timeout_content" msgid="172779756093975981">"\"<xliff:g id="SENDER">%1$s</xliff:g>\" कडील फाइल स्‍वीकार करताना वेळ संपली."</string>
     <string name="incoming_file_confirm_Notification_title" msgid="5573329005298936903">"येणारी फाइल"</string>
-    <string name="incoming_file_confirm_Notification_content" msgid="3359694069319644738">"<xliff:g id="FILE">%2$s</xliff:g> पाठविण्‍यासाठी <xliff:g id="SENDER">%1$s</xliff:g> तयार आहे"</string>
+    <string name="incoming_file_confirm_Notification_content" msgid="6237487486781004992">"<xliff:g id="SENDER">%1$s</xliff:g>फाइल पाठवण्यास तयार आहे: <xliff:g id="FILE">%2$s</xliff:g>"</string>
     <string name="notification_receiving" msgid="4674648179652543984">"ब्लूटूथ शेअर: <xliff:g id="FILE">%1$s</xliff:g> मिळवत आहे"</string>
     <string name="notification_received" msgid="3324588019186687985">"ब्लूटूथ शेअर: <xliff:g id="FILE">%1$s</xliff:g> प्राप्त केली"</string>
     <string name="notification_received_fail" msgid="3619350997285714746">"ब्लूटूथ शेअर: <xliff:g id="FILE">%1$s</xliff:g> फाइल प्राप्त केली नाही"</string>
@@ -83,8 +83,8 @@
     <string name="bt_toast_4" msgid="4678812947604395649">"\"<xliff:g id="RECIPIENT">%1$s</xliff:g>\" ना फाइल पाठवित आहे"</string>
     <string name="bt_toast_5" msgid="2846870992823019494">"\"<xliff:g id="RECIPIENT">%2$s</xliff:g>\" ना <xliff:g id="NUMBER">%1$s</xliff:g> फाइल पाठवित आहे"</string>
     <string name="bt_toast_6" msgid="1855266596936622458">"\"<xliff:g id="RECIPIENT">%1$s</xliff:g>\" ना फाइल पाठविणे थांबविले"</string>
-    <string name="bt_sm_2_1_nosdcard" msgid="1791835163301501637">"\"<xliff:g id="SENDER">%1$s</xliff:g>\" कडील फाइल सेव्ह करण्‍यासाठी USB स्टोरेजवर पुरेशी जागा नाही"</string>
-    <string name="bt_sm_2_1_default" msgid="9115512207909504071">"\"<xliff:g id="SENDER">%1$s</xliff:g>\" कडील फाइल सेव्ह करण्‍यासाठी SD कार्डवर पुरेशी जागा नाही"</string>
+    <string name="bt_sm_2_1_nosdcard" msgid="5354343190503952837">"फाइल सेव्ह करण्‍यासाठी USB स्टोरेजमध्ये पुरेशी जागा नाही."</string>
+    <string name="bt_sm_2_1_default" msgid="2497541206648973852">"फाइल सेव्ह करण्‍यासाठी SD कार्डवर पुरेशी जागा नाही."</string>
     <string name="bt_sm_2_2" msgid="2965243265852680543">"स्‍थान आवश्यक: <xliff:g id="SIZE">%1$s</xliff:g>"</string>
     <string name="ErrorTooManyRequests" msgid="8578277541472944529">"बर्‍याच विनंत्यांवर प्रक्रिया होत आहे. नंतर पुन्हा प्रयत्न करा."</string>
     <string name="status_pending" msgid="2503691772030877944">"फाइल स्थानांतरणाचा अद्याप सुरू झाला नाही."</string>
diff --git a/res/values-ms/strings.xml b/res/values-ms/strings.xml
index 78fffba..0e5834e 100644
--- a/res/values-ms/strings.xml
+++ b/res/values-ms/strings.xml
@@ -18,8 +18,8 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="permlab_bluetoothShareManager" msgid="311492132450338925">"Akses pengurus muat turun."</string>
     <string name="permdesc_bluetoothShareManager" msgid="8930572979123190223">"Membenarkan aplikasi mengakses pengurus Perkongsian Bluetooth dan menggunakannya untuk memindahkan fail."</string>
-    <string name="permlab_bluetoothWhitelist" msgid="7091552898592306386">"Akses peranti bluetooth senarai putih."</string>
-    <string name="permdesc_bluetoothWhitelist" msgid="5494513855192170109">"Membenarkan apl untuk menyenarai putihkan peranti Bluetooth sementara waktu, membolehkan peranti itu menghantar fail ke peranti ini tanpa pengesahan pengguna."</string>
+    <string name="permlab_bluetoothAcceptlist" msgid="2647575807648622053">"Senarai terima akses peranti bluetooth."</string>
+    <string name="permdesc_bluetoothAcceptlist" msgid="2406423665674766442">"Membenarkan apl untuk menyenarai terima peranti Bluetooth sementara waktu, membolehkan peranti itu menghantar fail ke peranti ini tanpa pengesahan pengguna."</string>
     <string name="bt_share_picker_label" msgid="6268100924487046932">"Bluetooth"</string>
     <string name="unknown_device" msgid="9221903979877041009">"Peranti tidak diketahui"</string>
     <string name="unknownNumber" msgid="4994750948072751566">"Tidak diketahui"</string>
@@ -37,7 +37,7 @@
     <string name="incoming_file_confirm_timeout_ok" msgid="1414676773249857278">"OK"</string>
     <string name="incoming_file_confirm_timeout_content" msgid="172779756093975981">"Berlaku tamat masa semasa menerima fail masuk daripada \"<xliff:g id="SENDER">%1$s</xliff:g>\""</string>
     <string name="incoming_file_confirm_Notification_title" msgid="5573329005298936903">"Fail masuk"</string>
-    <string name="incoming_file_confirm_Notification_content" msgid="3359694069319644738">"<xliff:g id="SENDER">%1$s</xliff:g> sudah bersedia untuk menghantar <xliff:g id="FILE">%2$s</xliff:g>"</string>
+    <string name="incoming_file_confirm_Notification_content" msgid="6237487486781004992">"<xliff:g id="SENDER">%1$s</xliff:g> bersedia menghantar fail: <xliff:g id="FILE">%2$s</xliff:g>"</string>
     <string name="notification_receiving" msgid="4674648179652543984">"Perkongsian Bluetooth: Menerima <xliff:g id="FILE">%1$s</xliff:g>"</string>
     <string name="notification_received" msgid="3324588019186687985">"Perkongsian Bluetooth: Diterima <xliff:g id="FILE">%1$s</xliff:g>"</string>
     <string name="notification_received_fail" msgid="3619350997285714746">"Perkongsian Bluetooth: Fail <xliff:g id="FILE">%1$s</xliff:g> tidak diterima"</string>
@@ -83,8 +83,8 @@
     <string name="bt_toast_4" msgid="4678812947604395649">"Menghantar fail kepada \"<xliff:g id="RECIPIENT">%1$s</xliff:g>\""</string>
     <string name="bt_toast_5" msgid="2846870992823019494">"Menghantar <xliff:g id="NUMBER">%1$s</xliff:g> fail kepada \"<xliff:g id="RECIPIENT">%2$s</xliff:g>\""</string>
     <string name="bt_toast_6" msgid="1855266596936622458">"Penghantaran fail ke \"<xliff:g id="RECIPIENT">%1$s</xliff:g>\" dihentikan"</string>
-    <string name="bt_sm_2_1_nosdcard" msgid="1791835163301501637">"Ruang pada storan USB tidak mencukupi untuk menyimpan fail daripada \"<xliff:g id="SENDER">%1$s</xliff:g>\""</string>
-    <string name="bt_sm_2_1_default" msgid="9115512207909504071">"Ruang pada kad SD tidak mencukupi untuk menyimpan fail daripada \"<xliff:g id="SENDER">%1$s</xliff:g>\""</string>
+    <string name="bt_sm_2_1_nosdcard" msgid="5354343190503952837">"Ruang pada storan USB tidak mencukupi untuk menyimpan fail."</string>
+    <string name="bt_sm_2_1_default" msgid="2497541206648973852">"Ruang pada kad SD tidak mencukupi untuk menyimpan fail."</string>
     <string name="bt_sm_2_2" msgid="2965243265852680543">"Ruang diperlukan: <xliff:g id="SIZE">%1$s</xliff:g>"</string>
     <string name="ErrorTooManyRequests" msgid="8578277541472944529">"Terlalu banyak permintaan sedang diproses. Cuba sebentar lagi."</string>
     <string name="status_pending" msgid="2503691772030877944">"Pemindahan fail belum lagi dimulakan."</string>
diff --git a/res/values-my/strings.xml b/res/values-my/strings.xml
index 1f46d09..76a9973 100644
--- a/res/values-my/strings.xml
+++ b/res/values-my/strings.xml
@@ -18,8 +18,8 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="permlab_bluetoothShareManager" msgid="311492132450338925">"ဒေါင်းလုပ်မန်နေဂျာကို အသုံးပြုနိုင်မည်"</string>
     <string name="permdesc_bluetoothShareManager" msgid="8930572979123190223">"အပလီကေးရှင်းအား ဘလူးတုသ်မျှဝေမှု မန်နေဂျာကို အသုံးပြုခွင့်ပေးပြီး ဖိုင်လွှဲရန် အသုံးပြုပါ"</string>
-    <string name="permlab_bluetoothWhitelist" msgid="7091552898592306386">"ဘလူးတုသ်ယာယီခွင့်ပြုစာရင်းကို အသုံးပြုခွင့်"</string>
-    <string name="permdesc_bluetoothWhitelist" msgid="5494513855192170109">"အက်ပ်အား ဘလူးတုသ်စက်ကို ယာယီခွင့်ပြုစာရင်းထဲ ထည့်ရန်ခွင့်ပြုကာ အသုံးပြုသူ၏ အတည်ပြုချက်မရယူပဲ ဖိုင်များကို စက်ထဲသို့ ပို့ခွင့်ပြုမည်"</string>
+    <string name="permlab_bluetoothAcceptlist" msgid="2647575807648622053">"ဘလူးတုသ်ယာယီလက်ခံစာရင်းကို အသုံးပြုခွင့်။"</string>
+    <string name="permdesc_bluetoothAcceptlist" msgid="2406423665674766442">"အက်ပ်အား ဘလူးတုသ်စက်ကို ယာယီလက်ခံစာရင်းထဲ ထည့်ရန်ခွင့်ပြုကာ အသုံးပြုသူ၏ အတည်ပြုချက်မရယူဘဲ ဖိုင်များကို စက်ထဲသို့ ပို့ခွင့်ပြုမည်။"</string>
     <string name="bt_share_picker_label" msgid="6268100924487046932">"ဘလူးတုသ်"</string>
     <string name="unknown_device" msgid="9221903979877041009">"မသိသော စက်"</string>
     <string name="unknownNumber" msgid="4994750948072751566">"မသိ"</string>
@@ -37,7 +37,7 @@
     <string name="incoming_file_confirm_timeout_ok" msgid="1414676773249857278">"OK"</string>
     <string name="incoming_file_confirm_timeout_content" msgid="172779756093975981">"\"<xliff:g id="SENDER">%1$s</xliff:g>\"မှ အဝင်ဖိုင်ကို လက်ခံနေစဥ် အချိန်ကုန်ဆုံးသွားပါပြီ"</string>
     <string name="incoming_file_confirm_Notification_title" msgid="5573329005298936903">"အဝင် ဖိုင်"</string>
-    <string name="incoming_file_confirm_Notification_content" msgid="3359694069319644738">"<xliff:g id="SENDER">%1$s</xliff:g> က <xliff:g id="FILE">%2$s</xliff:g>ကို ပို့ရန် အသင့်ပါ"</string>
+    <string name="incoming_file_confirm_Notification_content" msgid="6237487486781004992">"<xliff:g id="SENDER">%1$s</xliff:g> သည် ဤဖိုင်ကို ပို့ရန်အသင့်ဖြစ်ပါပြီ- <xliff:g id="FILE">%2$s</xliff:g>"</string>
     <string name="notification_receiving" msgid="4674648179652543984">"ဘလူးတုသ် ဝေမျှမှု - <xliff:g id="FILE">%1$s</xliff:g> လက်ခံနေသည်"</string>
     <string name="notification_received" msgid="3324588019186687985">"ဘလူးတုသ် ဝေမျှမှု - ရရှိပြီး<xliff:g id="FILE">%1$s</xliff:g>"</string>
     <string name="notification_received_fail" msgid="3619350997285714746">"ဘလူးတုသ် ဝေမျှမှု - ဖိုင် <xliff:g id="FILE">%1$s</xliff:g> မရရှိပါ"</string>
@@ -83,8 +83,8 @@
     <string name="bt_toast_4" msgid="4678812947604395649">"\"<xliff:g id="RECIPIENT">%1$s</xliff:g>\"ထံသို့ ဖိုင်ကိုပို့ပါ"</string>
     <string name="bt_toast_5" msgid="2846870992823019494">"<xliff:g id="NUMBER">%1$s</xliff:g> ဖိုင်ကို \"<xliff:g id="RECIPIENT">%2$s</xliff:g>\"ထံသို့ ပို့ပါ"</string>
     <string name="bt_toast_6" msgid="1855266596936622458">"\"<xliff:g id="RECIPIENT">%1$s</xliff:g>\"ထံသို့ ဖိုင်ကိုပို့ခြင်းအား ရပ်ဆိုင်းပါ"</string>
-    <string name="bt_sm_2_1_nosdcard" msgid="1791835163301501637">"\"<xliff:g id="SENDER">%1$s</xliff:g>\" ထံမှ ဖိုင်ကိုသိမ်းရန် USB သိုလှောင်ခန်းတွင် နေရာမလုံလောက်ပါ"</string>
-    <string name="bt_sm_2_1_default" msgid="9115512207909504071">"\"<xliff:g id="SENDER">%1$s</xliff:g>\" ထံမှ ဖိုင်ကိုသိမ်းရန် SD ကတ်ထဲတွင် နေရာမလုံလောက်ပါ"</string>
+    <string name="bt_sm_2_1_nosdcard" msgid="5354343190503952837">"ဖိုင်သိမ်းရန် USB သိုလှောင်ခန်းတွင် နေရာမလောက်ပါ။"</string>
+    <string name="bt_sm_2_1_default" msgid="2497541206648973852">"ဖိုင်သိမ်းရန် SD ကတ်ပေါ်တွင် နေရာမလောက်ပါ။"</string>
     <string name="bt_sm_2_2" msgid="2965243265852680543">"လိုအပ်သော နေရာပမာဏ - <xliff:g id="SIZE">%1$s</xliff:g>"</string>
     <string name="ErrorTooManyRequests" msgid="8578277541472944529">"တောင်းခံမှုများစွာကို ပြုလုပ်နေသည်၊ ခဏနေ ပြန်ကြိုးစားကြည့်ပါ"</string>
     <string name="status_pending" msgid="2503691772030877944">"ဖိုင်လွှဲပြောင်းခြင်း မစရသေးပါ"</string>
diff --git a/res/values-nb/strings.xml b/res/values-nb/strings.xml
index cf3f8a6..88d7b66 100644
--- a/res/values-nb/strings.xml
+++ b/res/values-nb/strings.xml
@@ -18,8 +18,8 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="permlab_bluetoothShareManager" msgid="311492132450338925">"Åpne nedlastingsbehandling."</string>
     <string name="permdesc_bluetoothShareManager" msgid="8930572979123190223">"Gir appen tilgang til BluetoothShare-administratoren, og tillatelse til å bruke det til filoverføring."</string>
-    <string name="permlab_bluetoothWhitelist" msgid="7091552898592306386">"Godkjenne tilgang for Bluetooth-enheter."</string>
-    <string name="permdesc_bluetoothWhitelist" msgid="5494513855192170109">"Tillater at Bluetooth-enheter midlertidig godkjennes av appen, slik at de kan sende filer til denne enheten uten brukerbekreftelse."</string>
+    <string name="permlab_bluetoothAcceptlist" msgid="2647575807648622053">"Godkjenn tilgang for Bluetooth-enheter."</string>
+    <string name="permdesc_bluetoothAcceptlist" msgid="2406423665674766442">"Appen gis tillatelse til å godkjenne en Bluetooth-enhet midlertidig, noe som gjør at Bluetooth-enheten kan sende filer til enheten du bruker, uten brukerbekreftelse."</string>
     <string name="bt_share_picker_label" msgid="6268100924487046932">"Bluetooth"</string>
     <string name="unknown_device" msgid="9221903979877041009">"Ukjent enhet"</string>
     <string name="unknownNumber" msgid="4994750948072751566">"Ukjent"</string>
@@ -37,7 +37,7 @@
     <string name="incoming_file_confirm_timeout_ok" msgid="1414676773249857278">"OK"</string>
     <string name="incoming_file_confirm_timeout_content" msgid="172779756093975981">"Det oppstod et tidsavbrudd under mottak av fil fra «<xliff:g id="SENDER">%1$s</xliff:g>»"</string>
     <string name="incoming_file_confirm_Notification_title" msgid="5573329005298936903">"Innkommende fil"</string>
-    <string name="incoming_file_confirm_Notification_content" msgid="3359694069319644738">"<xliff:g id="SENDER">%1$s</xliff:g> er klar til å sende <xliff:g id="FILE">%2$s</xliff:g>"</string>
+    <string name="incoming_file_confirm_Notification_content" msgid="6237487486781004992">"<xliff:g id="SENDER">%1$s</xliff:g> er klar til å sende en fil: <xliff:g id="FILE">%2$s</xliff:g>"</string>
     <string name="notification_receiving" msgid="4674648179652543984">"Bluetooth-deling: Mottar <xliff:g id="FILE">%1$s</xliff:g>"</string>
     <string name="notification_received" msgid="3324588019186687985">"Bluetooth-deling: <xliff:g id="FILE">%1$s</xliff:g> er mottatt"</string>
     <string name="notification_received_fail" msgid="3619350997285714746">"Bluetooth-deling: Filen <xliff:g id="FILE">%1$s</xliff:g> er ikke mottatt"</string>
@@ -83,8 +83,8 @@
     <string name="bt_toast_4" msgid="4678812947604395649">"Sender fil til «<xliff:g id="RECIPIENT">%1$s</xliff:g>»"</string>
     <string name="bt_toast_5" msgid="2846870992823019494">"Sender <xliff:g id="NUMBER">%1$s</xliff:g> filer til «<xliff:g id="RECIPIENT">%2$s</xliff:g>»"</string>
     <string name="bt_toast_6" msgid="1855266596936622458">"Stoppet sendingen av fil til «<xliff:g id="RECIPIENT">%1$s</xliff:g>»"</string>
-    <string name="bt_sm_2_1_nosdcard" msgid="1791835163301501637">"Det er ikke nok plass på USB-lagringen til å lagre filen fra «<xliff:g id="SENDER">%1$s</xliff:g>»"</string>
-    <string name="bt_sm_2_1_default" msgid="9115512207909504071">"Det er ikke nok plass på SD-kortet til å lagre filen fra «<xliff:g id="SENDER">%1$s</xliff:g>»"</string>
+    <string name="bt_sm_2_1_nosdcard" msgid="5354343190503952837">"Det er ikke nok plass på USB-lagringen til å lagre filen."</string>
+    <string name="bt_sm_2_1_default" msgid="2497541206648973852">"Det er ikke nok plass på SD-kortet til å lagre filen."</string>
     <string name="bt_sm_2_2" msgid="2965243265852680543">"Nødvendig plass: <xliff:g id="SIZE">%1$s</xliff:g>"</string>
     <string name="ErrorTooManyRequests" msgid="8578277541472944529">"For mange forespørsler behandles for øyeblikket. Prøv på nytt senere."</string>
     <string name="status_pending" msgid="2503691772030877944">"Filoverføringen har ikke startet ennå."</string>
diff --git a/res/values-ne/strings.xml b/res/values-ne/strings.xml
index aaf6eaa..c6384d0 100644
--- a/res/values-ne/strings.xml
+++ b/res/values-ne/strings.xml
@@ -18,8 +18,8 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="permlab_bluetoothShareManager" msgid="311492132450338925">"डाउनलोड म्यानेजर पहुँच गर्नुहोस्।"</string>
     <string name="permdesc_bluetoothShareManager" msgid="8930572979123190223">"एपलाई ब्लुटुथसाझेदारी प्रबन्धक पहुँचको अनुमति दिन्छ र यसलाई फाइलहरू स्थानान्तरण गर्न प्रयोग गर्दछ।"</string>
-    <string name="permlab_bluetoothWhitelist" msgid="7091552898592306386">"स्वेतसूची ब्लुटुथ उपकरण पहुँच।"</string>
-    <string name="permdesc_bluetoothWhitelist" msgid="5494513855192170109">"त्यस उपकरणलाई यस उपकरणमा फाइलहरू पठाउन बिना कुनै पुष्टि अनुमति दिँदै एपलाई अस्थायी रूपमा ब्लुटुथ उपकरणलाई स्वेतसूचीमा राख्न अनुमति दिन्छ।"</string>
+    <string name="permlab_bluetoothAcceptlist" msgid="2647575807648622053">"ब्लुटूथ चल्ने यन्त्रको एक्सेस श्वेतसूचीमा राख्नुहोस्।"</string>
+    <string name="permdesc_bluetoothAcceptlist" msgid="2406423665674766442">"यो सेटिङले यस एपलाई केही समयका लागि ब्लुटुथ चल्ने कुनै यन्त्र श्वेतसूचीमा राख्ने अनुमति दिन्छ। यस प्रकार श्वेतसूचीमा राखिएको उक्त यन्त्रले प्रयोगकर्ताको अनुमति नलिइकन यो डिभाइसमा फाइलहरू पठाउन सक्छ।"</string>
     <string name="bt_share_picker_label" msgid="6268100924487046932">"ब्लुटुथ"</string>
     <string name="unknown_device" msgid="9221903979877041009">"अज्ञात उपकरण"</string>
     <string name="unknownNumber" msgid="4994750948072751566">"अज्ञात"</string>
@@ -37,7 +37,7 @@
     <string name="incoming_file_confirm_timeout_ok" msgid="1414676773249857278">"ठिक छ"</string>
     <string name="incoming_file_confirm_timeout_content" msgid="172779756093975981">"\"<xliff:g id="SENDER">%1$s</xliff:g>\" बाट आगमन फाइल स्वीकार्दा समय सकिएको थियो"</string>
     <string name="incoming_file_confirm_Notification_title" msgid="5573329005298936903">"आगमन फाइल"</string>
-    <string name="incoming_file_confirm_Notification_content" msgid="3359694069319644738">"<xliff:g id="SENDER">%1$s</xliff:g> ले <xliff:g id="FILE">%2$s</xliff:g> पठाउन तयार छ"</string>
+    <string name="incoming_file_confirm_Notification_content" msgid="6237487486781004992">"<xliff:g id="SENDER">%1$s</xliff:g> निम्न फाइल पठाउन तयार हुनुहुन्छ: <xliff:g id="FILE">%2$s</xliff:g>"</string>
     <string name="notification_receiving" msgid="4674648179652543984">"ब्लुटुथ साझेदारी:<xliff:g id="FILE">%1$s</xliff:g> प्राप्त गर्दै"</string>
     <string name="notification_received" msgid="3324588019186687985">"ब्लुटुथ साझेदारी: <xliff:g id="FILE">%1$s</xliff:g> प्राप्त भयो"</string>
     <string name="notification_received_fail" msgid="3619350997285714746">"ब्लुटुथ साझेदारी: फाइल: <xliff:g id="FILE">%1$s</xliff:g> प्राप्त भएन"</string>
@@ -83,8 +83,8 @@
     <string name="bt_toast_4" msgid="4678812947604395649">"\"<xliff:g id="RECIPIENT">%1$s</xliff:g>\"लाई फाइल पठाउँदै"</string>
     <string name="bt_toast_5" msgid="2846870992823019494">"<xliff:g id="NUMBER">%1$s</xliff:g> फाइल \"<xliff:g id="RECIPIENT">%2$s</xliff:g>\"लाई पठाउँदै"</string>
     <string name="bt_toast_6" msgid="1855266596936622458">"\"<xliff:g id="RECIPIENT">%1$s</xliff:g> \"लाई फाइल पठाउन बन्द भयो"</string>
-    <string name="bt_sm_2_1_nosdcard" msgid="1791835163301501637">"\"<xliff:g id="SENDER">%1$s</xliff:g>\" बाट फाइल सुरक्षित गर्न USB भण्डारणमा पर्याप्त खाली ठाउँ छैन।"</string>
-    <string name="bt_sm_2_1_default" msgid="9115512207909504071">"\"<xliff:g id="SENDER">%1$s</xliff:g>\" बाट फाइल सुरक्षित गर्न SD कार्डमा पर्याप्त खाली ठाउँ छैन"</string>
+    <string name="bt_sm_2_1_nosdcard" msgid="5354343190503952837">"USB भण्डारणमा यो फाइल सुरक्षित गर्न पुग्ने ठाउँ छैन।"</string>
+    <string name="bt_sm_2_1_default" msgid="2497541206648973852">"SD कार्डमा यो फाइल सुरक्षित गर्न पुग्ने ठाउँ छैन।"</string>
     <string name="bt_sm_2_2" msgid="2965243265852680543">"ठाउँ चाहियो: <xliff:g id="SIZE">%1$s</xliff:g>"</string>
     <string name="ErrorTooManyRequests" msgid="8578277541472944529">"निकै धेरै अनुरोधहरू प्रसोधिन भइरहेका छन्। पछि फेरि प्रयास गर्नुहोस्।"</string>
     <string name="status_pending" msgid="2503691772030877944">"फाइल स्थानान्तरण अहिलेसम्म सुरु भएको छैन।"</string>
diff --git a/res/values-night/styles.xml b/res/values-night/styles.xml
new file mode 100644
index 0000000..a251ecb
--- /dev/null
+++ b/res/values-night/styles.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2014 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<resources>
+
+    <style name="dialog" parent="android:style/Theme.Material.Dialog.Alert" />
+
+</resources>
diff --git a/res/values-nl/strings.xml b/res/values-nl/strings.xml
index c1defea..c698250 100644
--- a/res/values-nl/strings.xml
+++ b/res/values-nl/strings.xml
@@ -16,10 +16,10 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="permlab_bluetoothShareManager" msgid="311492132450338925">"Downloadbeheer weergeven."</string>
+    <string name="permlab_bluetoothShareManager" msgid="311492132450338925">"Downloadbeheer tonen."</string>
     <string name="permdesc_bluetoothShareManager" msgid="8930572979123190223">"Hiermee krijgt de app toegang tot de beheerfunctie voor delen via bluetooth om deze functie te gebruiken voor het overdragen van bestanden."</string>
-    <string name="permlab_bluetoothWhitelist" msgid="7091552898592306386">"Toegang voor Bluetooth-apparaat op witte lijst plaatsen."</string>
-    <string name="permdesc_bluetoothWhitelist" msgid="5494513855192170109">"Hiermee kan de app een bluetooth-apparaat tijdelijk op de toelatingslijst plaatsen, waardoor dat apparaat bestanden kan sturen naar dit apparaat zonder bevestiging van de gebruiker."</string>
+    <string name="permlab_bluetoothAcceptlist" msgid="2647575807648622053">"Toegang voor bluetooth-apparaat op toelatingslijst zetten."</string>
+    <string name="permdesc_bluetoothAcceptlist" msgid="2406423665674766442">"Hiermee kan de app een bluetooth-apparaat tijdelijk op de toelatingslijst zetten, waardoor dat apparaat bestanden naar dit apparaat kan sturen zonder bevestiging van de gebruiker."</string>
     <string name="bt_share_picker_label" msgid="6268100924487046932">"Bluetooth"</string>
     <string name="unknown_device" msgid="9221903979877041009">"Onbekend apparaat"</string>
     <string name="unknownNumber" msgid="4994750948072751566">"Onbekend"</string>
@@ -37,7 +37,7 @@
     <string name="incoming_file_confirm_timeout_ok" msgid="1414676773249857278">"OK"</string>
     <string name="incoming_file_confirm_timeout_content" msgid="172779756093975981">"Er is een time-out opgetreden bij het accepteren van een inkomend bestand van \'<xliff:g id="SENDER">%1$s</xliff:g>\'"</string>
     <string name="incoming_file_confirm_Notification_title" msgid="5573329005298936903">"Inkomend bestand"</string>
-    <string name="incoming_file_confirm_Notification_content" msgid="3359694069319644738">"<xliff:g id="SENDER">%1$s</xliff:g> is klaar om <xliff:g id="FILE">%2$s</xliff:g> te verzenden"</string>
+    <string name="incoming_file_confirm_Notification_content" msgid="6237487486781004992">"<xliff:g id="SENDER">%1$s</xliff:g> is klaar om een bestand te sturen: <xliff:g id="FILE">%2$s</xliff:g>"</string>
     <string name="notification_receiving" msgid="4674648179652543984">"Delen via bluetooth: <xliff:g id="FILE">%1$s</xliff:g> ontvangen"</string>
     <string name="notification_received" msgid="3324588019186687985">"Delen via bluetooth: <xliff:g id="FILE">%1$s</xliff:g> ontvangen"</string>
     <string name="notification_received_fail" msgid="3619350997285714746">"Delen via bluetooth: bestand <xliff:g id="FILE">%1$s</xliff:g> niet ontvangen"</string>
@@ -83,8 +83,8 @@
     <string name="bt_toast_4" msgid="4678812947604395649">"Bestand verzenden naar \'<xliff:g id="RECIPIENT">%1$s</xliff:g>\'"</string>
     <string name="bt_toast_5" msgid="2846870992823019494">"<xliff:g id="NUMBER">%1$s</xliff:g> bestanden verzenden naar \'<xliff:g id="RECIPIENT">%2$s</xliff:g>\'"</string>
     <string name="bt_toast_6" msgid="1855266596936622458">"Verzenden van bestand naar \'<xliff:g id="RECIPIENT">%1$s</xliff:g>\' is beëindigd"</string>
-    <string name="bt_sm_2_1_nosdcard" msgid="1791835163301501637">"Er is onvoldoende ruimte in de USB-opslag beschikbaar om het bestand van \'<xliff:g id="SENDER">%1$s</xliff:g>\' op te slaan"</string>
-    <string name="bt_sm_2_1_default" msgid="9115512207909504071">"Er is onvoldoende ruimte op de SD-kaart beschikbaar om het bestand van \'<xliff:g id="SENDER">%1$s</xliff:g>\' op te slaan"</string>
+    <string name="bt_sm_2_1_nosdcard" msgid="5354343190503952837">"Er is onvoldoende ruimte in de USB-opslag beschikbaar om het bestand op te slaan."</string>
+    <string name="bt_sm_2_1_default" msgid="2497541206648973852">"Er is onvoldoende ruimte op de SD-kaart beschikbaar om het bestand op te slaan."</string>
     <string name="bt_sm_2_2" msgid="2965243265852680543">"Benodigde ruimte: <xliff:g id="SIZE">%1$s</xliff:g>"</string>
     <string name="ErrorTooManyRequests" msgid="8578277541472944529">"Er worden te veel aanvragen verwerkt. Probeer het later opnieuw."</string>
     <string name="status_pending" msgid="2503691772030877944">"Bestandsoverdracht nog niet gestart."</string>
diff --git a/res/values-or/strings.xml b/res/values-or/strings.xml
index 9a371ad..3752b96 100644
--- a/res/values-or/strings.xml
+++ b/res/values-or/strings.xml
@@ -18,8 +18,8 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="permlab_bluetoothShareManager" msgid="311492132450338925">"ଡାଉନଲୋଡ୍‌ ମ୍ୟାନେଜର୍‌କୁ ଆକ୍ସେସ୍‌ କରନ୍ତୁ।"</string>
     <string name="permdesc_bluetoothShareManager" msgid="8930572979123190223">"BluetoothShare ମ୍ୟାନେଜର୍‌ ଆକ୍ସେସ୍‌ କରି ଫାଇଲ୍‌ଗୁଡ଼ିକ ଟ୍ରାନ୍ସଫର୍‌ କରିବାକୁ ବ୍ୟବହାର କରିବା ପାଇଁ ଆପ୍‌କୁ ଅନୁମତି ଦିଅନ୍ତୁ।"</string>
-    <string name="permlab_bluetoothWhitelist" msgid="7091552898592306386">"ବ୍ଲୁଟୂଥ୍‍‌ ଡିଭାଇସ୍‌ ଆକ୍ସେସ୍‌କୁ ସ୍ୱୀକୃତି ଦିଅନ୍ତୁ।"</string>
-    <string name="permdesc_bluetoothWhitelist" msgid="5494513855192170109">"ୟୁଜର୍‌ଙ୍କ ସୁନିଶ୍ଚିତତା ବିନା ଏହି ଡିଭାଇସ୍‌କୁ ଫାଇଲ୍‌ ପଠାଇବା ନିମନ୍ତେ ଗୋଟିଏ ବ୍ଲୁଟୂଥ୍‍‌ ଡିଭାଇସ୍‌କୁ ଅନୁମତି ଦେଇ କିଛି ସମୟ ପାଇଁ ଆପ୍‌କୁ ସ୍ୱୀକୃତି ଦେଇଥାଏ।"</string>
+    <string name="permlab_bluetoothAcceptlist" msgid="2647575807648622053">"ବ୍ଲୁଟୁଥ୍ ଡିଭାଇସର ଆକ୍ସେସକୁ ଗ୍ରହଣ-ସୂଚୀରେ ରଖନ୍ତୁ।"</string>
+    <string name="permdesc_bluetoothAcceptlist" msgid="2406423665674766442">"ଏକ ବ୍ଲୁଟୁଥ୍ ଡିଭାଇସକୁ ଅସ୍ଥାୟୀ ଭାବେ ଗ୍ରହଣ-ସୂଚୀରେ ରଖିବାକୁ ଆପଟିକୁ ଅନୁମତି ଦେଇଥାଏ, ଯାହା ଦ୍ୱାରା ଉପଯୋଗକର୍ତ୍ତାଙ୍କ ସୁନିଶ୍ଚିତକରଣ ବିନା ଏହି ଡିଭାଇସକୁ ଫାଇଲ୍ ପଠାଇବା ପାଇଁ ସେହି ଡିଭାଇସକୁ ଅନୁମତି ମିଳିଥାଏ।"</string>
     <string name="bt_share_picker_label" msgid="6268100924487046932">"ବ୍ଲୁଟୁଥ"</string>
     <string name="unknown_device" msgid="9221903979877041009">"ଅଜଣା ଡିଭାଇସ୍"</string>
     <string name="unknownNumber" msgid="4994750948072751566">"ଅଜଣା"</string>
@@ -37,7 +37,7 @@
     <string name="incoming_file_confirm_timeout_ok" msgid="1414676773249857278">"ଠିକ୍‌ ଅଛି"</string>
     <string name="incoming_file_confirm_timeout_content" msgid="172779756093975981">"\"<xliff:g id="SENDER">%1$s</xliff:g>\"ଙ୍କଠାରୁ ଆସୁଥିବା ଫାଇଲ୍‌ ସ୍ୱୀକାର କରୁଥିବାବେଳେ ସମୟ ସମାପ୍ତ ହୋଇଗଲା"</string>
     <string name="incoming_file_confirm_Notification_title" msgid="5573329005298936903">"ଆସୁଥିବା ଫାଇଲ୍‌"</string>
-    <string name="incoming_file_confirm_Notification_content" msgid="3359694069319644738">"<xliff:g id="FILE">%2$s</xliff:g> ପଠାଇବା ପାଇଁ <xliff:g id="SENDER">%1$s</xliff:g> ପ୍ରସ୍ତୁତ"</string>
+    <string name="incoming_file_confirm_Notification_content" msgid="6237487486781004992">"<xliff:g id="SENDER">%1$s</xliff:g> ଏକ ଫାଇଲ୍ ପଠାଇବାକୁ ପ୍ରସ୍ତୁତ ଅଛନ୍ତି: <xliff:g id="FILE">%2$s</xliff:g>"</string>
     <string name="notification_receiving" msgid="4674648179652543984">"ବ୍ଲୁଟୂଥ୍‍‌ ସେୟାର୍‌: <xliff:g id="FILE">%1$s</xliff:g> ପ୍ରାପ୍ତ କରୁଛି"</string>
     <string name="notification_received" msgid="3324588019186687985">"ବ୍ଲୁଟୂଥ୍‍‌ ସେୟାର୍‌: <xliff:g id="FILE">%1$s</xliff:g> ପ୍ରାପ୍ତ କରାଯାଇଛି"</string>
     <string name="notification_received_fail" msgid="3619350997285714746">"ବ୍ଲୁଟୂଥ୍‍‌ ସେୟାର୍‌: <xliff:g id="FILE">%1$s</xliff:g> ଫାଇଲ୍‌ ପ୍ରାପ୍ତ କରାଯାଇନାହିଁ"</string>
@@ -83,8 +83,8 @@
     <string name="bt_toast_4" msgid="4678812947604395649">"\"<xliff:g id="RECIPIENT">%1$s</xliff:g>\"ଙ୍କୁ ଫାଇଲ୍‌ ପଠାଯାଉଛି"</string>
     <string name="bt_toast_5" msgid="2846870992823019494">"\"<xliff:g id="RECIPIENT">%2$s</xliff:g>\"ଙ୍କୁ <xliff:g id="NUMBER">%1$s</xliff:g>ଟି ଫାଇଲ୍‌ ପଠାଯାଉଛି"</string>
     <string name="bt_toast_6" msgid="1855266596936622458">"\"<xliff:g id="RECIPIENT">%1$s</xliff:g>\"ଙ୍କୁ ଫାଇଲ୍‌ ପଠାଇବା ବନ୍ଦ ହୋଇଗଲା"</string>
-    <string name="bt_sm_2_1_nosdcard" msgid="1791835163301501637">"\"<xliff:g id="SENDER">%1$s</xliff:g>\"ଙ୍କ ଠାରୁ ଫାଇଲ୍‌ ସେଭ୍‌ କରିବା ପାଇଁ USB ଷ୍ଟୋରେଜ୍‌ରେ ଯଥେଷ୍ଟ ସ୍ପେସ୍‌ ନାହିଁ"</string>
-    <string name="bt_sm_2_1_default" msgid="9115512207909504071">"\"<xliff:g id="SENDER">%1$s</xliff:g>\"ଙ୍କ ଠାରୁ ଫାଇଲ୍‌ ସେଭ୍‌ କରିବା ପାଇଁ SD କାର୍ଡରେ ଯଥେଷ୍ଟ ସ୍ପେସ୍‌ ନାହିଁ"</string>
+    <string name="bt_sm_2_1_nosdcard" msgid="5354343190503952837">"ଫାଇଲ୍ ସେଭ୍ କରିବାକୁ USB ଷ୍ଟୋରେଜରେ ଯଥେଷ୍ଟ ଜାଗା ନାହିଁ।"</string>
+    <string name="bt_sm_2_1_default" msgid="2497541206648973852">"ଫାଇଲ୍ ସେଭ୍ କରିବାକୁ SD କାର୍ଡରେ ଯଥେଷ୍ଟ ଜାଗା ନାହିଁ।"</string>
     <string name="bt_sm_2_2" msgid="2965243265852680543">"ସ୍ପେସ୍‌ ଆବଶ୍ୟକ: <xliff:g id="SIZE">%1$s</xliff:g>"</string>
     <string name="ErrorTooManyRequests" msgid="8578277541472944529">"ଅନେକ ଅନୁରୋଧ ଉପରେ କାମ ଚାଲୁଛି। ପରେ ପୁଣି ଚେଷ୍ଟା କରନ୍ତୁ।"</string>
     <string name="status_pending" msgid="2503691772030877944">"ଏପର୍ଯ୍ୟନ୍ତ ଫାଇଲ୍‌ ଟ୍ରାନ୍ସଫର୍‌ ଆରମ୍ଭ ହୋଇନାହିଁ।"</string>
@@ -127,7 +127,7 @@
     <string name="bluetooth_map_settings_intro" msgid="6482369468223987562">"ବ୍ଲୁଟୂଥ୍‍‌ ମାଧ୍ୟମରେ ସେୟାର୍‌ କରିବାକୁ ଚାହୁଁଥିବା ଆକାଉଣ୍ଟଗୁଡ଼ିକୁ ଚୟନ କରନ୍ତୁ। ଆପଣଙ୍କୁ ତଥାପି ସଂଯୋଗ କରୁଥିବା ସମୟରେ ଆକାଉଣ୍ଟଗୁଡ଼ିକ ପ୍ରତି ଯେକୌଣସି ଆକ୍ସେସ୍‌କୁ ସ୍ୱୀକାର କରିବାକୁ ପଡ଼ିବ।"</string>
     <string name="bluetooth_map_settings_count" msgid="4557473074937024833">"ବଳକା ସ୍ଲଟ୍‌:"</string>
     <string name="bluetooth_map_settings_app_icon" msgid="7105805610929114707">"ଆପ୍ଲିକେଶନ୍‌ ଆଇକନ୍‌"</string>
-    <string name="bluetooth_map_settings_title" msgid="7420332483392851321">"ବ୍ଲୁଟୁଥ‌ ମେସେଜ୍‌ ସେୟାରିଂ ସେଟିଂସ୍"</string>
+    <string name="bluetooth_map_settings_title" msgid="7420332483392851321">"ବ୍ଲୁଟୁଥ‌ ମେସେଜ ସେୟାରିଂ ସେଟିଂସ"</string>
     <string name="bluetooth_map_settings_no_account_slots_left" msgid="1796029082612965251">"ଆକାଉଣ୍ଟ ଚୟନ କରାଯାଇପାରିବ ନାହିଁ। 0 ସ୍ଲଟ୍‌ ବଳକା ରହିଲା"</string>
     <string name="bluetooth_connected" msgid="6718623220072656906">"ବ୍ଲୁଟୂଥ୍‍‌ ଅଡିଓ ସଂଯୁକ୍ତ କରାଗଲା"</string>
     <string name="bluetooth_disconnected" msgid="3318303728981478873">"ବ୍ଲୁଟୂଥ୍‍‌ ଅଡିଓ ବିଚ୍ଛିନ୍ନ କରାଗଲା"</string>
diff --git a/res/values-pa/strings.xml b/res/values-pa/strings.xml
index 63f7917..604bf79 100644
--- a/res/values-pa/strings.xml
+++ b/res/values-pa/strings.xml
@@ -18,8 +18,8 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="permlab_bluetoothShareManager" msgid="311492132450338925">"ਡਾਊਨਲੋਡ ਪ੍ਰਬੰਧਕ ਤੱਕ ਪਹੁੰਚ।"</string>
     <string name="permdesc_bluetoothShareManager" msgid="8930572979123190223">"ਐਪ ਨੂੰ BluetoothShare ਪ੍ਰਬੰਧਕ ਤੱਕ ਪਹੁੰਚ ਅਤੇ ਫ਼ਾਈਲਾਂ ਟ੍ਰਾਂਸਫਰ ਕਰਨ ਲਈ ਇਸਦੀ ਵਰਤੋਂ ਕਰਨ ਦਿੰਦਾ ਹੈ।"</string>
-    <string name="permlab_bluetoothWhitelist" msgid="7091552898592306386">"ਵਾਈਟਲਿਸਟ ਬਲੂਟੱਥ ਡੀਵਾਈਸ ਪਹੁੰਚ।"</string>
-    <string name="permdesc_bluetoothWhitelist" msgid="5494513855192170109">"ਐਪ ਨੂੰ ਇਹ ਆਗਿਆ ਦਿੰਦੇ ਹੋਏ ਕਿ ਡੀਵਾਈਸ ਵਰਤੋਂਕਾਰ ਦੀ ਪੁਸ਼ਟੀ ਤੋਂ ਬਿਨਾਂ ਇਸ ਡੀਵਾਈਸ ਨੂੰ ਫ਼ਾਈਲਾਂ ਭੇਜੇ, ਇੱਕ ਬਲੂਟੁੱਥ ਡੀਵਾਈਸ ਨੂੰ ਅਸਥਾਈ ਤੌਰ ਤੇ ਵਾਈਟਲਿਸਟ ਕਰਨ ਦੀ ਆਗਿਆ ਦਿੰਦਾ ਹੈ।"</string>
+    <string name="permlab_bluetoothAcceptlist" msgid="2647575807648622053">"ਅਕਸੈਪਟਲਿਸਟ ਬਲੂਟੁੱਥ ਡੀਵਾਈਸ ਪਹੁੰਚ।"</string>
+    <string name="permdesc_bluetoothAcceptlist" msgid="2406423665674766442">"ਐਪ ਨੂੰ ਕਿਸੇ ਬਲੂਟੁੱਥ ਡੀਵਾਈਸ ਨੂੰ ਆਰਜ਼ੀ ਤੌਰ \'ਤੇ ਅਕਸੈਪਟਲਿਸਟ ਕਰਨ ਦਿੰਦਾ ਹੈ, ਅਤੇ ਉਸ ਡੀਵਾਈਸ ਨੂੰ ਇਸ ਡੀਵਾਈਸ ਤੱਕ ਵਰਤੋਂਕਾਰ ਦੀ ਪੁਸ਼ਟੀ ਦੇ ਬਿਨਾਂ ਫ਼ਾਈਲਾਂ ਭੇਜਣ ਦਿੰਦਾ ਹੈ।"</string>
     <string name="bt_share_picker_label" msgid="6268100924487046932">"ਬਲੂਟੁੱਥ"</string>
     <string name="unknown_device" msgid="9221903979877041009">"ਅਗਿਆਤ ਡੀਵਾਈਸ"</string>
     <string name="unknownNumber" msgid="4994750948072751566">"ਅਗਿਆਤ"</string>
@@ -37,7 +37,7 @@
     <string name="incoming_file_confirm_timeout_ok" msgid="1414676773249857278">"ਠੀਕ"</string>
     <string name="incoming_file_confirm_timeout_content" msgid="172779756093975981">"\"<xliff:g id="SENDER">%1$s</xliff:g>\" ਦੀ ਇੱਕ ਇਨਕਮਿੰਗ ਫਾਈਲ ਸਵੀਕਾਰ ਕਰਨ ਵੇਲੇ ਇੱਕ ਟਾਈਮਆਊਟ ਹੋਇਆ ਸੀ।"</string>
     <string name="incoming_file_confirm_Notification_title" msgid="5573329005298936903">"ਇਨਕਮਿੰਗ ਫਾਈਲ"</string>
-    <string name="incoming_file_confirm_Notification_content" msgid="3359694069319644738">"<xliff:g id="SENDER">%1$s</xliff:g> <xliff:g id="FILE">%2$s</xliff:g> ਭੇਜਣ ਲਈ ਤਿਆਰ ਹੈ"</string>
+    <string name="incoming_file_confirm_Notification_content" msgid="6237487486781004992">"<xliff:g id="SENDER">%1$s</xliff:g> ਫ਼ਾਈਲ ਭੇਜਣ ਲਈ ਤਿਆਰ ਹੈ: <xliff:g id="FILE">%2$s</xliff:g>"</string>
     <string name="notification_receiving" msgid="4674648179652543984">"Bluetooth ਸ਼ੇਅਰ: <xliff:g id="FILE">%1$s</xliff:g> ਪ੍ਰਾਪਤ ਕਰ ਰਿਹਾ ਹੈ"</string>
     <string name="notification_received" msgid="3324588019186687985">"Bluetooth ਸ਼ੇਅਰ: <xliff:g id="FILE">%1$s</xliff:g> ਪ੍ਰਾਪਤ ਕੀਤੀ"</string>
     <string name="notification_received_fail" msgid="3619350997285714746">"Bluetooth ਸ਼ੇਅਰ: ਫਾਈਲ <xliff:g id="FILE">%1$s</xliff:g> ਪ੍ਰਾਪਤ ਨਹੀਂ ਕੀਤੀ"</string>
@@ -81,10 +81,10 @@
     <string name="bt_toast_2" msgid="8602553334099066582">"ਫਾਈਲ ਪ੍ਰਾਪਤ ਨਹੀਂ ਕੀਤੀ ਜਾ ਸਕਦੀ।"</string>
     <string name="bt_toast_3" msgid="6707884165086862518">"\"<xliff:g id="SENDER">%1$s</xliff:g>\" ਤੋਂ ਫਾਈਲ ਪ੍ਰਾਪਤ ਕਰਨਾ ਰੋਕਿਆ ਗਿਆ"</string>
     <string name="bt_toast_4" msgid="4678812947604395649">"\"<xliff:g id="RECIPIENT">%1$s</xliff:g>\" ਨੂੰ ਫਾਈਲ ਭੇਜ ਰਿਹਾ ਹੈ"</string>
-    <string name="bt_toast_5" msgid="2846870992823019494">"\"<xliff:g id="RECIPIENT">%2$s</xliff:g>\" ਨੂੰ <xliff:g id="NUMBER">%1$s</xliff:g> ਫਾਈਲਾਂ ਭੇਜ ਰਿਹਾ ਹੈ"</string>
+    <string name="bt_toast_5" msgid="2846870992823019494">"\"<xliff:g id="RECIPIENT">%2$s</xliff:g>\" ਨੂੰ <xliff:g id="NUMBER">%1$s</xliff:g> ਫ਼ਾਈਲਾਂ ਭੇਜੀਆਂ ਜਾ ਰਹੀਆਂ ਹਨ"</string>
     <string name="bt_toast_6" msgid="1855266596936622458">"\"<xliff:g id="RECIPIENT">%1$s</xliff:g>\" ਨੂੰ ਫਾਈਲ ਭੇਜਣਾ ਰੋਕਿਆ ਗਿਆ"</string>
-    <string name="bt_sm_2_1_nosdcard" msgid="1791835163301501637">"\"<xliff:g id="SENDER">%1$s</xliff:g>\" ਦੀ ਫ਼ਾਈਲ ਰੱਖਿਅਤ ਕਰਨ ਲਈ USB ਸਟੋਰੇਜ ਵਿੱਚ ਪੂਰੀ ਜਗ੍ਹਾ ਨਹੀਂ ਹੈ।"</string>
-    <string name="bt_sm_2_1_default" msgid="9115512207909504071">"\"<xliff:g id="SENDER">%1$s</xliff:g>\" ਦੀ ਫ਼ਾਈਲ ਰੱਖਿਅਤ ਕਰਨ ਲਈ SD ਕਾਰਡ ਵਿੱਚ ਪੂਰੀ ਜਗ੍ਹਾ ਨਹੀਂ ਹੈ।"</string>
+    <string name="bt_sm_2_1_nosdcard" msgid="5354343190503952837">"ਦੀ ਫ਼ਾਈਲ ਰੱਖਿਅਤ ਕਰਨ ਲਈ USB ਸਟੋਰੇਜ ਵਿੱਚ ਲੋੜੀਂਦੀ ਜਗ੍ਹਾ ਨਹੀਂ ਹੈ।"</string>
+    <string name="bt_sm_2_1_default" msgid="2497541206648973852">"ਫ਼ਾਈਲ ਰੱਖਿਅਤ ਕਰਨ ਲਈ SD ਕਾਰਡ ਵਿੱਚ ਲੋੜੀਂਦੀ ਜਗ੍ਹਾ ਨਹੀਂ ਹੈ।"</string>
     <string name="bt_sm_2_2" msgid="2965243265852680543">"ਲੁੜੀਂਦਾ ਸਪੇਸ: <xliff:g id="SIZE">%1$s</xliff:g>"</string>
     <string name="ErrorTooManyRequests" msgid="8578277541472944529">"ਬਹੁਤ ਜ਼ਿਆਦਾ ਬੇਨਤੀਆਂ ਦੀ ਪ੍ਰਕਿਰਿਆ ਕੀਤੀ ਜਾ ਰਹੀ ਹੈ। ਬਾਅਦ ਵਿੱਚ ਦੁਬਾਰਾ ਕੋਸ਼ਿਸ਼ ਕਰੋ।"</string>
     <string name="status_pending" msgid="2503691772030877944">"ਫਾਈਲ ਟ੍ਰਾਂਸਫਰ ਅਜੇ ਚਾਲੂ ਨਹੀਂ ਹੋਈ।"</string>
@@ -107,8 +107,8 @@
     <string name="outbound_history_title" msgid="4279418703178140526">"ਆਊਟਬਾਊਂਡ ਟ੍ਰਾਂਸਫਰ"</string>
     <string name="no_transfers" msgid="3482965619151865672">"ਟ੍ਰਾਂਸਫ਼ਰ ਇਤਿਹਾਸ ਵਿੱਚ ਕੁਝ ਵੀ ਨਹੀਂ ਹੈ।"</string>
     <string name="transfer_clear_dlg_msg" msgid="1712376797268438075">"ਸਾਰੀਆਂ ਆਈਟਮਾਂ ਸੂਚੀ ਵਿੱਚੋਂ ਹਟਾਈਆਂ ਜਾਣਗੀਆਂ।"</string>
-    <string name="outbound_noti_title" msgid="8051906709452260849">"Bluetooth ਸ਼ੇਅਰ: ਭੇਜੀਆਂ ਗਈਆਂ ਫਾਈਲਾਂ"</string>
-    <string name="inbound_noti_title" msgid="4143352641953027595">"Bluetooth ਸ਼ੇਅਰ: ਪ੍ਰਾਪਤ ਕੀਤੀਆਂ ਫਾਈਲਾਂ"</string>
+    <string name="outbound_noti_title" msgid="8051906709452260849">"ਬਲੂਟੁੱਥ ਸਾਂਝਾਕਰਨ: ਭੇਜੀਆਂ ਗਈਆਂ ਫ਼ਾਈਲਾਂ"</string>
+    <string name="inbound_noti_title" msgid="4143352641953027595">"ਬਲੂਟੁੱਥ ਸਾਂਝਾਕਰਨ: ਪ੍ਰਾਪਤ ਕੀਤੀਆਂ ਫ਼ਾਈਲਾਂ"</string>
     <plurals name="noti_caption_unsuccessful" formatted="false" msgid="2020750076679526122">
       <item quantity="one"><xliff:g id="UNSUCCESSFUL_NUMBER_1">%1$d</xliff:g> ਅਸਫਲ।</item>
       <item quantity="other"><xliff:g id="UNSUCCESSFUL_NUMBER_1">%1$d</xliff:g> ਅਸਫਲ।</item>
@@ -132,6 +132,6 @@
     <string name="bluetooth_connected" msgid="6718623220072656906">"ਬਲੂਟੁੱਥ  ਆਡੀਓ  ਕਨੈਕਟ ਕੀਤੀ ਗਈ"</string>
     <string name="bluetooth_disconnected" msgid="3318303728981478873">"ਬਲੂਟੁੱਥ  ਆਡੀਓ  ਡਿਸਕਨੈਕਟ ਕੀਤੀ ਗਈ"</string>
     <string name="a2dp_sink_mbs_label" msgid="7566075853395412558">"ਬਲੂਟੁੱਥ  ਆਡੀਓ"</string>
-    <string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"4GB ਤੋਂ ਵਧੇਰੇ ਵੱਡੀਆਂ ਫ਼ਾਈਲਾਂ ਨੂੰ ਟ੍ਰਾਂਸਫ਼ਰ ਨਹੀਂ ਕੀਤਾ ਜਾ ਸਕਦਾ"</string>
+    <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 7521bb1..f8e121c 100644
--- a/res/values-pl/strings.xml
+++ b/res/values-pl/strings.xml
@@ -18,8 +18,8 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="permlab_bluetoothShareManager" msgid="311492132450338925">"Dostęp do menedżera pobierania."</string>
     <string name="permdesc_bluetoothShareManager" msgid="8930572979123190223">"Zezwala aplikacji na dostęp do menedżera BluetoothShare i używanie go do przesyłania plików."</string>
-    <string name="permlab_bluetoothWhitelist" msgid="7091552898592306386">"Dodaj urządzenie Bluetooth do zaufanych."</string>
-    <string name="permdesc_bluetoothWhitelist" msgid="5494513855192170109">"Pozwala aplikacji na tymczasowe dodanie urządzenia Bluetooth do listy zaufanych. Dzięki temu wymiana danych z tym urządzeniem nie wymaga potwierdzenia użytkownika."</string>
+    <string name="permlab_bluetoothAcceptlist" msgid="2647575807648622053">"Dodanie urządzenia Bluetooth do zaufanych."</string>
+    <string name="permdesc_bluetoothAcceptlist" msgid="2406423665674766442">"Pozwala aplikacji na tymczasowe dodanie urządzenia Bluetooth do listy zaufanych. Dzięki temu wymiana danych z tym urządzeniem nie wymaga potwierdzenia przez użytkownika."</string>
     <string name="bt_share_picker_label" msgid="6268100924487046932">"Bluetooth"</string>
     <string name="unknown_device" msgid="9221903979877041009">"Nieznane urządzenie"</string>
     <string name="unknownNumber" msgid="4994750948072751566">"Nieznany"</string>
@@ -37,7 +37,7 @@
     <string name="incoming_file_confirm_timeout_ok" msgid="1414676773249857278">"OK"</string>
     <string name="incoming_file_confirm_timeout_content" msgid="172779756093975981">"Upłynął czas oczekiwania przy akceptowaniu przychodzącego pliku z urządzenia „<xliff:g id="SENDER">%1$s</xliff:g>”"</string>
     <string name="incoming_file_confirm_Notification_title" msgid="5573329005298936903">"Plik przychodzący"</string>
-    <string name="incoming_file_confirm_Notification_content" msgid="3359694069319644738">"<xliff:g id="SENDER">%1$s</xliff:g> może wysłać plik <xliff:g id="FILE">%2$s</xliff:g>"</string>
+    <string name="incoming_file_confirm_Notification_content" msgid="6237487486781004992">"<xliff:g id="SENDER">%1$s</xliff:g> może już wysłać plik: <xliff:g id="FILE">%2$s</xliff:g>"</string>
     <string name="notification_receiving" msgid="4674648179652543984">"Udostępnianie Bluetooth: odbieranie <xliff:g id="FILE">%1$s</xliff:g>"</string>
     <string name="notification_received" msgid="3324588019186687985">"Udostępnianie Bluetooth: odebrano plik <xliff:g id="FILE">%1$s</xliff:g>"</string>
     <string name="notification_received_fail" msgid="3619350997285714746">"Udostępnianie Bluetooth: nie odebrano pliku <xliff:g id="FILE">%1$s</xliff:g>"</string>
@@ -83,8 +83,8 @@
     <string name="bt_toast_4" msgid="4678812947604395649">"Wysyłanie pliku do urządzenia „<xliff:g id="RECIPIENT">%1$s</xliff:g>”"</string>
     <string name="bt_toast_5" msgid="2846870992823019494">"Wysyłanie <xliff:g id="NUMBER">%1$s</xliff:g> plików do urządzenia „<xliff:g id="RECIPIENT">%2$s</xliff:g>”"</string>
     <string name="bt_toast_6" msgid="1855266596936622458">"Zatrzymano wysyłanie pliku do urządzenia „<xliff:g id="RECIPIENT">%1$s</xliff:g>”"</string>
-    <string name="bt_sm_2_1_nosdcard" msgid="1791835163301501637">"Za mało miejsca na nośniku USB do zapisania pliku z urządzenia „<xliff:g id="SENDER">%1$s</xliff:g>”."</string>
-    <string name="bt_sm_2_1_default" msgid="9115512207909504071">"Za mało miejsca na karcie SD do zapisania pliku z urządzenia „<xliff:g id="SENDER">%1$s</xliff:g>”."</string>
+    <string name="bt_sm_2_1_nosdcard" msgid="5354343190503952837">"Za mało miejsca na nośniku USB, by zapisać plik."</string>
+    <string name="bt_sm_2_1_default" msgid="2497541206648973852">"Za mało miejsca na karcie SD, by zapisać plik."</string>
     <string name="bt_sm_2_2" msgid="2965243265852680543">"Wymagane miejsce: <xliff:g id="SIZE">%1$s</xliff:g>"</string>
     <string name="ErrorTooManyRequests" msgid="8578277541472944529">"Przetwarzanych jest zbyt wiele żądań. Spróbuj ponownie później."</string>
     <string name="status_pending" msgid="2503691772030877944">"Przesyłanie pliku jeszcze się nie rozpoczęło."</string>
diff --git a/res/values-pt-rPT/strings.xml b/res/values-pt-rPT/strings.xml
index d0bdc52..eb99087 100644
--- a/res/values-pt-rPT/strings.xml
+++ b/res/values-pt-rPT/strings.xml
@@ -18,8 +18,8 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="permlab_bluetoothShareManager" msgid="311492132450338925">"Aceder ao gestor de transferências."</string>
     <string name="permdesc_bluetoothShareManager" msgid="8930572979123190223">"Permite à app aceder ao gestor BluetoothShare e utilizá-lo para transferir ficheiros."</string>
-    <string name="permlab_bluetoothWhitelist" msgid="7091552898592306386">"Colocar na lista de autorizações o acesso do dispositivo Bluetooth."</string>
-    <string name="permdesc_bluetoothWhitelist" msgid="5494513855192170109">"Permite que a app coloque temporariamente um dispositivo Bluetooth na lista de autorizações, permitindo que esse dispositivo envie ficheiros para este dispositivo sem a confirmação do utilizador."</string>
+    <string name="permlab_bluetoothAcceptlist" msgid="2647575807648622053">"Colocar na lista de autorizações o acesso do dispositivo Bluetooth."</string>
+    <string name="permdesc_bluetoothAcceptlist" msgid="2406423665674766442">"Permite que a app coloque temporariamente um dispositivo Bluetooth na lista de autorizações, permitindo que esse dispositivo envie ficheiros para este dispositivo sem a confirmação do utilizador."</string>
     <string name="bt_share_picker_label" msgid="6268100924487046932">"Bluetooth"</string>
     <string name="unknown_device" msgid="9221903979877041009">"Dispositivo desconhecido"</string>
     <string name="unknownNumber" msgid="4994750948072751566">"Desconhecido"</string>
@@ -37,7 +37,7 @@
     <string name="incoming_file_confirm_timeout_ok" msgid="1414676773249857278">"OK"</string>
     <string name="incoming_file_confirm_timeout_content" msgid="172779756093975981">"Foi excedido o tempo limite durante a aceitação de um ficheiro de \"<xliff:g id="SENDER">%1$s</xliff:g>\""</string>
     <string name="incoming_file_confirm_Notification_title" msgid="5573329005298936903">"Ficheiro a receber"</string>
-    <string name="incoming_file_confirm_Notification_content" msgid="3359694069319644738">"<xliff:g id="SENDER">%1$s</xliff:g> está pronto para enviar <xliff:g id="FILE">%2$s</xliff:g>"</string>
+    <string name="incoming_file_confirm_Notification_content" msgid="6237487486781004992">"<xliff:g id="SENDER">%1$s</xliff:g> tem tudo pronto para enviar um ficheiro: <xliff:g id="FILE">%2$s</xliff:g>"</string>
     <string name="notification_receiving" msgid="4674648179652543984">"Partilha Bluetooth: a receber <xliff:g id="FILE">%1$s</xliff:g>"</string>
     <string name="notification_received" msgid="3324588019186687985">"Partilha Bluetooth: <xliff:g id="FILE">%1$s</xliff:g> recebido"</string>
     <string name="notification_received_fail" msgid="3619350997285714746">"Partilha Bluetooth: Ficheiro <xliff:g id="FILE">%1$s</xliff:g> não recebido"</string>
@@ -83,8 +83,8 @@
     <string name="bt_toast_4" msgid="4678812947604395649">"A enviar ficheiro para \"<xliff:g id="RECIPIENT">%1$s</xliff:g>\""</string>
     <string name="bt_toast_5" msgid="2846870992823019494">"A enviar <xliff:g id="NUMBER">%1$s</xliff:g> ficheiros para \"<xliff:g id="RECIPIENT">%2$s</xliff:g>\""</string>
     <string name="bt_toast_6" msgid="1855266596936622458">"O envio do ficheiro para \"<xliff:g id="RECIPIENT">%1$s</xliff:g>\" foi interrompido"</string>
-    <string name="bt_sm_2_1_nosdcard" msgid="1791835163301501637">"Não existe espaço suficiente na memória USB para guardar o ficheiro de \"<xliff:g id="SENDER">%1$s</xliff:g>\"."</string>
-    <string name="bt_sm_2_1_default" msgid="9115512207909504071">"Não existe espaço suficiente no cartão SD para guardar o ficheiro de \"<xliff:g id="SENDER">%1$s</xliff:g>\"."</string>
+    <string name="bt_sm_2_1_nosdcard" msgid="5354343190503952837">"Não existe espaço suficiente na memória USB para guardar o ficheiro."</string>
+    <string name="bt_sm_2_1_default" msgid="2497541206648973852">"Não existe espaço suficiente no cartão SD para guardar o ficheiro."</string>
     <string name="bt_sm_2_2" msgid="2965243265852680543">"Espaço necessário: <xliff:g id="SIZE">%1$s</xliff:g>"</string>
     <string name="ErrorTooManyRequests" msgid="8578277541472944529">"Existem demasiados pedidos em processamento. Tente novamente mais tarde."</string>
     <string name="status_pending" msgid="2503691772030877944">"Ainda não foi iniciada a transferência do ficheiro."</string>
@@ -110,12 +110,12 @@
     <string name="outbound_noti_title" msgid="8051906709452260849">"Partilha por Bluetooth: ficheiros enviados"</string>
     <string name="inbound_noti_title" msgid="4143352641953027595">"Partilha por Bluetooth: ficheiros recebidos"</string>
     <plurals name="noti_caption_unsuccessful" formatted="false" msgid="2020750076679526122">
-      <item quantity="other"><xliff:g id="UNSUCCESSFUL_NUMBER_1">%1$d</xliff:g> sem êxito.</item>
       <item quantity="one"><xliff:g id="UNSUCCESSFUL_NUMBER_0">%1$d</xliff:g> sem êxito.</item>
+      <item quantity="other"><xliff:g id="UNSUCCESSFUL_NUMBER_1">%1$d</xliff:g> sem êxito.</item>
     </plurals>
     <plurals name="noti_caption_success" formatted="false" msgid="1572472450257645181">
-      <item quantity="other"><xliff:g id="SUCCESSFUL_NUMBER_1">%1$d</xliff:g> com êxito, %2$s</item>
       <item quantity="one"><xliff:g id="SUCCESSFUL_NUMBER_0">%1$d</xliff:g> com êxito, %2$s</item>
+      <item quantity="other"><xliff:g id="SUCCESSFUL_NUMBER_1">%1$d</xliff:g> com êxito, %2$s</item>
     </plurals>
     <string name="transfer_menu_clear_all" msgid="790017462957873132">"Limpar lista"</string>
     <string name="transfer_menu_open" msgid="3368984869083107200">"Abrir"</string>
diff --git a/res/values-pt/strings.xml b/res/values-pt/strings.xml
index ec56835..f16e7c6 100644
--- a/res/values-pt/strings.xml
+++ b/res/values-pt/strings.xml
@@ -18,8 +18,8 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="permlab_bluetoothShareManager" msgid="311492132450338925">"Acessar o gerenciador de download."</string>
     <string name="permdesc_bluetoothShareManager" msgid="8930572979123190223">"Permite que o app acesse e use o gerenciador BluetoothShare para transferir arquivos."</string>
-    <string name="permlab_bluetoothWhitelist" msgid="7091552898592306386">"Colocar o acesso do dispositivo Bluetooth na lista de permissões."</string>
-    <string name="permdesc_bluetoothWhitelist" msgid="5494513855192170109">"Permite que o app adicione um dispositivo Bluetooth à uma lista de permissões temporariamente. Assim, o dispositivo Bluetooth poderá enviar arquivos para este dispositivo sem precisar de confirmação do usuário."</string>
+    <string name="permlab_bluetoothAcceptlist" msgid="2647575807648622053">"Adicionar o acesso do dispositivo Bluetooth à lista de permissões."</string>
+    <string name="permdesc_bluetoothAcceptlist" msgid="2406423665674766442">"Permite que o app adicione temporariamente um dispositivo Bluetooth a uma lista de permissões. Assim, o dispositivo poderá enviar arquivos para este aparelho sem precisar da confirmação do usuário."</string>
     <string name="bt_share_picker_label" msgid="6268100924487046932">"Bluetooth"</string>
     <string name="unknown_device" msgid="9221903979877041009">"Dispositivo desconhecido"</string>
     <string name="unknownNumber" msgid="4994750948072751566">"Desconhecido"</string>
@@ -37,7 +37,7 @@
     <string name="incoming_file_confirm_timeout_ok" msgid="1414676773249857278">"OK"</string>
     <string name="incoming_file_confirm_timeout_content" msgid="172779756093975981">"Tempo limite excedido ao receber um arquivo de \"<xliff:g id="SENDER">%1$s</xliff:g>\""</string>
     <string name="incoming_file_confirm_Notification_title" msgid="5573329005298936903">"Arquivo recebido"</string>
-    <string name="incoming_file_confirm_Notification_content" msgid="3359694069319644738">"<xliff:g id="SENDER">%1$s</xliff:g> está pronto para enviar <xliff:g id="FILE">%2$s</xliff:g>"</string>
+    <string name="incoming_file_confirm_Notification_content" msgid="6237487486781004992">"<xliff:g id="SENDER">%1$s</xliff:g> já pode enviar um arquivo: <xliff:g id="FILE">%2$s</xliff:g>"</string>
     <string name="notification_receiving" msgid="4674648179652543984">"Compart. Bluetooth: recebendo <xliff:g id="FILE">%1$s</xliff:g>"</string>
     <string name="notification_received" msgid="3324588019186687985">"Compart. Bluetooth: <xliff:g id="FILE">%1$s</xliff:g> recebido"</string>
     <string name="notification_received_fail" msgid="3619350997285714746">"Compart. Bluetooth: o arquivo <xliff:g id="FILE">%1$s</xliff:g> não foi recebido"</string>
@@ -83,8 +83,8 @@
     <string name="bt_toast_4" msgid="4678812947604395649">"Enviando arquivo para \"<xliff:g id="RECIPIENT">%1$s</xliff:g>\""</string>
     <string name="bt_toast_5" msgid="2846870992823019494">"Enviando <xliff:g id="NUMBER">%1$s</xliff:g> arquivos para \"<xliff:g id="RECIPIENT">%2$s</xliff:g>\""</string>
     <string name="bt_toast_6" msgid="1855266596936622458">"Envio de arquivo para \"<xliff:g id="RECIPIENT">%1$s</xliff:g>\" interrompido"</string>
-    <string name="bt_sm_2_1_nosdcard" msgid="1791835163301501637">"Não há espaço suficiente no armazenamento USB para salvar o arquivo de \"<xliff:g id="SENDER">%1$s</xliff:g>\""</string>
-    <string name="bt_sm_2_1_default" msgid="9115512207909504071">"Não há espaço suficiente no cartão SD para salvar o arquivo de \"<xliff:g id="SENDER">%1$s</xliff:g>\""</string>
+    <string name="bt_sm_2_1_nosdcard" msgid="5354343190503952837">"Não há espaço suficiente no armazenamento USB para salvar o arquivo."</string>
+    <string name="bt_sm_2_1_default" msgid="2497541206648973852">"Não há espaço suficiente no cartão SD para salvar o arquivo."</string>
     <string name="bt_sm_2_2" msgid="2965243265852680543">"Espaço necessário: <xliff:g id="SIZE">%1$s</xliff:g>"</string>
     <string name="ErrorTooManyRequests" msgid="8578277541472944529">"Há muitas solicitações sendo processadas. Tente novamente mais tarde."</string>
     <string name="status_pending" msgid="2503691772030877944">"A transferência de arquivo ainda não foi iniciada."</string>
diff --git a/res/values-ro/strings.xml b/res/values-ro/strings.xml
index d2825e3..16b17aa 100644
--- a/res/values-ro/strings.xml
+++ b/res/values-ro/strings.xml
@@ -18,8 +18,8 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="permlab_bluetoothShareManager" msgid="311492132450338925">"Accesați managerul de descărcare."</string>
     <string name="permdesc_bluetoothShareManager" msgid="8930572979123190223">"Permite aplicațiilor să acceseze managerul Distribuire prin Bluetooth și să-l utilizeze la transferul fișierelor."</string>
-    <string name="permlab_bluetoothWhitelist" msgid="7091552898592306386">"Acces la dispozitivele Bluetooth din lista albă."</string>
-    <string name="permdesc_bluetoothWhitelist" msgid="5494513855192170109">"Permite aplicației să treacă temporar pe lista albă un dispozitiv Bluetooth, permițându-i să trimită fișiere la acest dispozitiv fără confirmare din partea utilizatorului."</string>
+    <string name="permlab_bluetoothAcceptlist" msgid="2647575807648622053">"Acceptați accesul la dispozitivul Bluetooth."</string>
+    <string name="permdesc_bluetoothAcceptlist" msgid="2406423665674766442">"Permite aplicației să accepte temporar un dispozitiv Bluetooth, permițându-i să trimită fișiere pe acest dispozitiv fără confirmare din partea utilizatorului."</string>
     <string name="bt_share_picker_label" msgid="6268100924487046932">"Bluetooth"</string>
     <string name="unknown_device" msgid="9221903979877041009">"Dispozitiv necunoscut"</string>
     <string name="unknownNumber" msgid="4994750948072751566">"Necunoscut"</string>
@@ -37,7 +37,7 @@
     <string name="incoming_file_confirm_timeout_ok" msgid="1414676773249857278">"OK"</string>
     <string name="incoming_file_confirm_timeout_content" msgid="172779756093975981">"A fost atins timpul limită pentru acceptarea unui fișier primit de la „<xliff:g id="SENDER">%1$s</xliff:g>”"</string>
     <string name="incoming_file_confirm_Notification_title" msgid="5573329005298936903">"Fișier primit"</string>
-    <string name="incoming_file_confirm_Notification_content" msgid="3359694069319644738">"<xliff:g id="SENDER">%1$s</xliff:g> este gata să trimită <xliff:g id="FILE">%2$s</xliff:g>"</string>
+    <string name="incoming_file_confirm_Notification_content" msgid="6237487486781004992">"<xliff:g id="SENDER">%1$s</xliff:g> este gata să trimită un fișier: <xliff:g id="FILE">%2$s</xliff:g>"</string>
     <string name="notification_receiving" msgid="4674648179652543984">"Distribuire prin Bluetooth: se primește <xliff:g id="FILE">%1$s</xliff:g>"</string>
     <string name="notification_received" msgid="3324588019186687985">"Distribuire prin Bluetooth: s-a primit <xliff:g id="FILE">%1$s</xliff:g>"</string>
     <string name="notification_received_fail" msgid="3619350997285714746">"Distribuire prin Bluetooth: fișierul <xliff:g id="FILE">%1$s</xliff:g> nu s-a primit"</string>
@@ -83,8 +83,8 @@
     <string name="bt_toast_4" msgid="4678812947604395649">"Se trimite fișierul către „<xliff:g id="RECIPIENT">%1$s</xliff:g>”"</string>
     <string name="bt_toast_5" msgid="2846870992823019494">"Se trimit <xliff:g id="NUMBER">%1$s</xliff:g>   fișiere către „<xliff:g id="RECIPIENT">%2$s</xliff:g>”"</string>
     <string name="bt_toast_6" msgid="1855266596936622458">"Trimiterea fișierului către „<xliff:g id="RECIPIENT">%1$s</xliff:g>” a fost anulată"</string>
-    <string name="bt_sm_2_1_nosdcard" msgid="1791835163301501637">"Nu există spațiu suficient pe spațiul de stocare USB pentru a salva fișierul de la „<xliff:g id="SENDER">%1$s</xliff:g>”"</string>
-    <string name="bt_sm_2_1_default" msgid="9115512207909504071">"Nu există suficient spațiu pe cardul SD pentru a salva fișierul de la „<xliff:g id="SENDER">%1$s</xliff:g>”"</string>
+    <string name="bt_sm_2_1_nosdcard" msgid="5354343190503952837">"Nu există suficient spațiu de stocare USB pentru a salva fișierul."</string>
+    <string name="bt_sm_2_1_default" msgid="2497541206648973852">"Nu există spațiu suficient pe cardul SD pentru a salva fișierul."</string>
     <string name="bt_sm_2_2" msgid="2965243265852680543">"Spațiu necesar: <xliff:g id="SIZE">%1$s</xliff:g>"</string>
     <string name="ErrorTooManyRequests" msgid="8578277541472944529">"Există prea multe solicitări în curs de procesare. Încercați din nou mai târziu."</string>
     <string name="status_pending" msgid="2503691772030877944">"Transferul fișierului nu a început încă."</string>
diff --git a/res/values-ru/strings.xml b/res/values-ru/strings.xml
index 95b2c04..9b96685 100644
--- a/res/values-ru/strings.xml
+++ b/res/values-ru/strings.xml
@@ -18,8 +18,8 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="permlab_bluetoothShareManager" msgid="311492132450338925">"Доступ к диспетчеру загрузки."</string>
     <string name="permdesc_bluetoothShareManager" msgid="8930572979123190223">"Приложение получит доступ к диспетчеру обмена файлами по Bluetooth и сможет использовать его для передачи файлов."</string>
-    <string name="permlab_bluetoothWhitelist" msgid="7091552898592306386">"Доступ для устройств Bluetooth из белого списка"</string>
-    <string name="permdesc_bluetoothWhitelist" msgid="5494513855192170109">"Приложение сможет временно внести устройство Bluetooth в белый список и разрешит ему отправлять файлы на ваше устройство, не требуя от вас подтверждения"</string>
+    <string name="permlab_bluetoothAcceptlist" msgid="2647575807648622053">"Доступ для устройств Bluetooth из списка разрешенных"</string>
+    <string name="permdesc_bluetoothAcceptlist" msgid="2406423665674766442">"Приложение сможет временно внести устройство Bluetooth в список разрешенных и позволит ему отправлять файлы на ваше устройство, не требуя от вас подтверждения"</string>
     <string name="bt_share_picker_label" msgid="6268100924487046932">"Bluetooth"</string>
     <string name="unknown_device" msgid="9221903979877041009">"Неизвестное устройство"</string>
     <string name="unknownNumber" msgid="4994750948072751566">"Неизвестно"</string>
@@ -37,7 +37,7 @@
     <string name="incoming_file_confirm_timeout_ok" msgid="1414676773249857278">"ОК"</string>
     <string name="incoming_file_confirm_timeout_content" msgid="172779756093975981">"В процессе приема файла от \"<xliff:g id="SENDER">%1$s</xliff:g>\" произошел тайм-аут"</string>
     <string name="incoming_file_confirm_Notification_title" msgid="5573329005298936903">"Входящий файл"</string>
-    <string name="incoming_file_confirm_Notification_content" msgid="3359694069319644738">"Устройство \"<xliff:g id="SENDER">%1$s</xliff:g>\" готово к отправке файла \"<xliff:g id="FILE">%2$s</xliff:g>\""</string>
+    <string name="incoming_file_confirm_Notification_content" msgid="6237487486781004992">"Устройство \"<xliff:g id="SENDER">%1$s</xliff:g>\" готово к отправке файла \"<xliff:g id="FILE">%2$s</xliff:g>\""</string>
     <string name="notification_receiving" msgid="4674648179652543984">"Передача по Bluetooth: <xliff:g id="FILE">%1$s</xliff:g>"</string>
     <string name="notification_received" msgid="3324588019186687985">"Получено по Bluetooth: <xliff:g id="FILE">%1$s</xliff:g>"</string>
     <string name="notification_received_fail" msgid="3619350997285714746">"Файл не передан: <xliff:g id="FILE">%1$s</xliff:g>"</string>
@@ -83,8 +83,8 @@
     <string name="bt_toast_4" msgid="4678812947604395649">"Отправка файла на \"<xliff:g id="RECIPIENT">%1$s</xliff:g>\""</string>
     <string name="bt_toast_5" msgid="2846870992823019494">"Отправка файлов (<xliff:g id="NUMBER">%1$s</xliff:g>) на \"<xliff:g id="RECIPIENT">%2$s</xliff:g>\""</string>
     <string name="bt_toast_6" msgid="1855266596936622458">"Отправка файла на \"<xliff:g id="RECIPIENT">%1$s</xliff:g>\" остановлена"</string>
-    <string name="bt_sm_2_1_nosdcard" msgid="1791835163301501637">"На USB-накопителе нет места, чтобы сохранить файл с устройства \"<xliff:g id="SENDER">%1$s</xliff:g>\"."</string>
-    <string name="bt_sm_2_1_default" msgid="9115512207909504071">"На SD-карте нет места, чтобы сохранить файл с устройства \"<xliff:g id="SENDER">%1$s</xliff:g>\"."</string>
+    <string name="bt_sm_2_1_nosdcard" msgid="5354343190503952837">"На USB-накопителе недостаточно места, чтобы сохранить файл."</string>
+    <string name="bt_sm_2_1_default" msgid="2497541206648973852">"На SD-карте недостаточно места, чтобы сохранить файл."</string>
     <string name="bt_sm_2_2" msgid="2965243265852680543">"Необходимое свободное место: <xliff:g id="SIZE">%1$s</xliff:g>"</string>
     <string name="ErrorTooManyRequests" msgid="8578277541472944529">"Обрабатывается слишком много запросов. Повторите попытку позднее."</string>
     <string name="status_pending" msgid="2503691772030877944">"Передача файла еще не началась."</string>
diff --git a/res/values-si/strings.xml b/res/values-si/strings.xml
index 9c995fe..899e1cb 100644
--- a/res/values-si/strings.xml
+++ b/res/values-si/strings.xml
@@ -18,8 +18,8 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="permlab_bluetoothShareManager" msgid="311492132450338925">"බාගැනීම් කළමනාකරු ප්‍රවේශ වන්න."</string>
     <string name="permdesc_bluetoothShareManager" msgid="8930572979123190223">"යෙදුමට බ්ලූටූත් බෙදා ගැනීම් කළමනාකරු වෙත ප්‍රවේශ වීමට ඉඩ දී එය ගොනු මාරු කිරීමට භාවිතා කරන්න."</string>
-    <string name="permlab_bluetoothWhitelist" msgid="7091552898592306386">"බ්ලූටූත් උපාංග ප්‍රවේශය සුදු ලැයිස්තු ගත කරන්න."</string>
-    <string name="permdesc_bluetoothWhitelist" msgid="5494513855192170109">"පරිශීලක සහතිකය රහිතව එම උපාංගයට මෙම උපාංගය වෙත ගොනු යැවීමට ඉඩ දෙමින්, බ්ලූටූත් උපාංගයක් තාවකාලිකව සුදු ලැයිස්තු ගත කිරීමට යෙදුමට ඉඩ දෙන්න."</string>
+    <string name="permlab_bluetoothAcceptlist" msgid="2647575807648622053">"පිළිගත් ලැයිස්තු බ්ලූටූත් උපාංග ප්‍රවේශය."</string>
+    <string name="permdesc_bluetoothAcceptlist" msgid="2406423665674766442">"පරිශීලක තහවුරු කිරීම නොමැතිව එම උපාංගයට මෙම උපාංගය වෙත ගොනු එවීමට ඉඩ දෙමින්, බ්ලූටූත් උපාංගයක් තාවකාලිකව පිළිගත් ලැයිස්තුගත කිරීමට යෙදුමට ඉඩ දෙයි."</string>
     <string name="bt_share_picker_label" msgid="6268100924487046932">"බ්ලූටූත්"</string>
     <string name="unknown_device" msgid="9221903979877041009">"නොදන්නා උපාංගයකි"</string>
     <string name="unknownNumber" msgid="4994750948072751566">"නොදනී"</string>
@@ -37,7 +37,7 @@
     <string name="incoming_file_confirm_timeout_ok" msgid="1414676773249857278">"හරි"</string>
     <string name="incoming_file_confirm_timeout_content" msgid="172779756093975981">"\"<xliff:g id="SENDER">%1$s</xliff:g>\" වෙතින් පැමිණෙන ගොනුවක් පිළිගන්නා අතරතුර කාල නිමාවක් විය"</string>
     <string name="incoming_file_confirm_Notification_title" msgid="5573329005298936903">"ලැබෙන ගොනුව"</string>
-    <string name="incoming_file_confirm_Notification_content" msgid="3359694069319644738">"<xliff:g id="SENDER">%1$s</xliff:g> <xliff:g id="FILE">%2$s</xliff:g> යැවීමට සූදානම්ය"</string>
+    <string name="incoming_file_confirm_Notification_content" msgid="6237487486781004992">"<xliff:g id="SENDER">%1$s</xliff:g> ගොනුවක් යැවීමට සූදානම්ය: <xliff:g id="FILE">%2$s</xliff:g>"</string>
     <string name="notification_receiving" msgid="4674648179652543984">"බ්ලූටූත් බෙදා ගැනීම: <xliff:g id="FILE">%1$s</xliff:g> ලැබේ"</string>
     <string name="notification_received" msgid="3324588019186687985">"බ්ලූටූත් බෙදා ගැනීම: <xliff:g id="FILE">%1$s</xliff:g> ලැබිණි"</string>
     <string name="notification_received_fail" msgid="3619350997285714746">"බ්ලූටූත් බෙදා ගැනීම: <xliff:g id="FILE">%1$s</xliff:g> ගොනුව නොලැබිණි"</string>
@@ -83,8 +83,8 @@
     <string name="bt_toast_4" msgid="4678812947604395649">"\"<xliff:g id="RECIPIENT">%1$s</xliff:g>\" වෙත ගොනුව යැවේ"</string>
     <string name="bt_toast_5" msgid="2846870992823019494">"\"<xliff:g id="RECIPIENT">%2$s</xliff:g>\" වෙත <xliff:g id="NUMBER">%1$s</xliff:g> ගොනු යැවේ"</string>
     <string name="bt_toast_6" msgid="1855266596936622458">"\"<xliff:g id="RECIPIENT">%1$s</xliff:g>\" වෙත ගොනුව යැවීම නතර විය"</string>
-    <string name="bt_sm_2_1_nosdcard" msgid="1791835163301501637">"\"<xliff:g id="SENDER">%1$s</xliff:g>\" වෙතින් ගොනුව සුරැකීමට USB ආචයනය තුළ ප්‍රමාණවත් ඉඩක් නැත"</string>
-    <string name="bt_sm_2_1_default" msgid="9115512207909504071">"\"<xliff:g id="SENDER">%1$s</xliff:g>\" වෙතින් ගොනුව සුරැකීමට SD කාඩ්පතේ ප්‍රමාණවත් ඉඩක් නැත"</string>
+    <string name="bt_sm_2_1_nosdcard" msgid="5354343190503952837">"ගොනුව සුරැකීමට USB ගබඩාවේ ප්‍රමාණවත් ඉඩක් නැත"</string>
+    <string name="bt_sm_2_1_default" msgid="2497541206648973852">"ගොනුව සුරැකීමට SD කාඩ්පතේ ප්‍රමාණවත් ඉඩක් නැත"</string>
     <string name="bt_sm_2_2" msgid="2965243265852680543">"අවශ්‍ය ඉඩ: <xliff:g id="SIZE">%1$s</xliff:g>"</string>
     <string name="ErrorTooManyRequests" msgid="8578277541472944529">"ඉල්ලීම් බොහෝ ගණනක් ක්‍රියාත්මක වෙමින් පවතී. පසුව නැවත උත්සාහ කරන්න."</string>
     <string name="status_pending" msgid="2503691772030877944">"ගොනු මාරුව තවම ආරම්භ වී නැත."</string>
diff --git a/res/values-sk/strings.xml b/res/values-sk/strings.xml
index c532140..ffe0af5 100644
--- a/res/values-sk/strings.xml
+++ b/res/values-sk/strings.xml
@@ -18,8 +18,8 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="permlab_bluetoothShareManager" msgid="311492132450338925">"Získať prístup k správcovi sťahovania."</string>
     <string name="permdesc_bluetoothShareManager" msgid="8930572979123190223">"Umožňuje aplikácii pristupovať k Správcovi BluetoothShare a použiť ho na prenos súborov."</string>
-    <string name="permlab_bluetoothWhitelist" msgid="7091552898592306386">"Prístup povoleného zariadenia Bluetooth."</string>
-    <string name="permdesc_bluetoothWhitelist" msgid="5494513855192170109">"Umožňuje aplikácii dočasne povoliť zariadenie Bluetooth, čím sa povolí zariadeniu odosielať súbory do tohto zariadenia bez potvrdenia používateľa."</string>
+    <string name="permlab_bluetoothAcceptlist" msgid="2647575807648622053">"Prístup povoleného zariadenia Bluetooth."</string>
+    <string name="permdesc_bluetoothAcceptlist" msgid="2406423665674766442">"Umožňuje aplikácii dočasne povoliť zariadenie Bluetooth, čím sa povolí zariadeniu odosielať súbory do tohto zariadenia bez potvrdenia používateľa."</string>
     <string name="bt_share_picker_label" msgid="6268100924487046932">"Bluetooth"</string>
     <string name="unknown_device" msgid="9221903979877041009">"Neznáme zariadenie"</string>
     <string name="unknownNumber" msgid="4994750948072751566">"Neznáme"</string>
@@ -37,7 +37,7 @@
     <string name="incoming_file_confirm_timeout_ok" msgid="1414676773249857278">"OK"</string>
     <string name="incoming_file_confirm_timeout_content" msgid="172779756093975981">"Pri prijímaní prichádzajúceho súboru od používateľa <xliff:g id="SENDER">%1$s</xliff:g> vypršal časový limit."</string>
     <string name="incoming_file_confirm_Notification_title" msgid="5573329005298936903">"Prichádzajúci súbor"</string>
-    <string name="incoming_file_confirm_Notification_content" msgid="3359694069319644738">"<xliff:g id="SENDER">%1$s</xliff:g> je pripravený/-á odoslať súbor <xliff:g id="FILE">%2$s</xliff:g>"</string>
+    <string name="incoming_file_confirm_Notification_content" msgid="6237487486781004992">"Používateľ <xliff:g id="SENDER">%1$s</xliff:g> je pripravený odoslať súbor <xliff:g id="FILE">%2$s</xliff:g>"</string>
     <string name="notification_receiving" msgid="4674648179652543984">"Bluetooth: Prijíma sa <xliff:g id="FILE">%1$s</xliff:g>"</string>
     <string name="notification_received" msgid="3324588019186687985">"Bluetooth: <xliff:g id="FILE">%1$s</xliff:g> prijatý"</string>
     <string name="notification_received_fail" msgid="3619350997285714746">"Bluetooth: <xliff:g id="FILE">%1$s</xliff:g> neprijatý"</string>
@@ -83,8 +83,8 @@
     <string name="bt_toast_4" msgid="4678812947604395649">"Odosielanie súboru používateľovi <xliff:g id="RECIPIENT">%1$s</xliff:g>"</string>
     <string name="bt_toast_5" msgid="2846870992823019494">"Odosielanie súborov (počet: <xliff:g id="NUMBER">%1$s</xliff:g>) používateľovi <xliff:g id="RECIPIENT">%2$s</xliff:g>"</string>
     <string name="bt_toast_6" msgid="1855266596936622458">"Odosielanie súboru používateľovi <xliff:g id="RECIPIENT">%1$s</xliff:g> bolo zastavené"</string>
-    <string name="bt_sm_2_1_nosdcard" msgid="1791835163301501637">"V úložisku USB nie je dostatok miesta na uloženie súboru od odosielateľa <xliff:g id="SENDER">%1$s</xliff:g>"</string>
-    <string name="bt_sm_2_1_default" msgid="9115512207909504071">"Na SD karte nie je dostatok miesta na uloženie súboru od odosielateľa <xliff:g id="SENDER">%1$s</xliff:g>"</string>
+    <string name="bt_sm_2_1_nosdcard" msgid="5354343190503952837">"V úložisku USB nie je dostatok priestoru na uloženie súboru."</string>
+    <string name="bt_sm_2_1_default" msgid="2497541206648973852">"Na SD karte nie je dostatok priestoru na uloženie súboru."</string>
     <string name="bt_sm_2_2" msgid="2965243265852680543">"Požadované miesto v pamäti: <xliff:g id="SIZE">%1$s</xliff:g>"</string>
     <string name="ErrorTooManyRequests" msgid="8578277541472944529">"Spracúva sa príliš veľa žiadostí. Opakujte akciu neskôr."</string>
     <string name="status_pending" msgid="2503691772030877944">"Prenos súborov ešte nebol spustený."</string>
diff --git a/res/values-sl/strings.xml b/res/values-sl/strings.xml
index 7d518a3..c823378 100644
--- a/res/values-sl/strings.xml
+++ b/res/values-sl/strings.xml
@@ -18,8 +18,8 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="permlab_bluetoothShareManager" msgid="311492132450338925">"Dostop do upravitelja prenosov."</string>
     <string name="permdesc_bluetoothShareManager" msgid="8930572979123190223">"Aplikaciji omogoča dostop do upravitelja BluetoothShare in njegovo uporabo za prenašanje datotek."</string>
-    <string name="permlab_bluetoothWhitelist" msgid="7091552898592306386">"Doda napravo Bluetooth na seznam z dovoljenim dostopom."</string>
-    <string name="permdesc_bluetoothWhitelist" msgid="5494513855192170109">"Aplikaciji omogoča, da začasno uvrsti napravo Bluetooth na seznam dovoljenih, kar omogoči napravi pošiljanje datotek v to napravo brez potrditve uporabnika."</string>
+    <string name="permlab_bluetoothAcceptlist" msgid="2647575807648622053">"Napravo Bluetooth doda na seznam z dovoljenim dostopom."</string>
+    <string name="permdesc_bluetoothAcceptlist" msgid="2406423665674766442">"Aplikaciji omogoča, da napravo Bluetooth začasno uvrsti na seznam dovoljenih, kar ji omogoči pošiljanje datotek v to napravo brez potrditve uporabnika."</string>
     <string name="bt_share_picker_label" msgid="6268100924487046932">"Bluetooth"</string>
     <string name="unknown_device" msgid="9221903979877041009">"Neznana naprava"</string>
     <string name="unknownNumber" msgid="4994750948072751566">"Neznano"</string>
@@ -37,7 +37,7 @@
     <string name="incoming_file_confirm_timeout_ok" msgid="1414676773249857278">"V redu"</string>
     <string name="incoming_file_confirm_timeout_content" msgid="172779756093975981">"Pri sprejemanju datoteke pošiljatelja »<xliff:g id="SENDER">%1$s</xliff:g>« je potekla časovna omejitev"</string>
     <string name="incoming_file_confirm_Notification_title" msgid="5573329005298936903">"Dohodna datoteka"</string>
-    <string name="incoming_file_confirm_Notification_content" msgid="3359694069319644738">"Uporabnik <xliff:g id="SENDER">%1$s</xliff:g> je pripravljen za pošiljanje datoteke <xliff:g id="FILE">%2$s</xliff:g>"</string>
+    <string name="incoming_file_confirm_Notification_content" msgid="6237487486781004992">"Naprava <xliff:g id="SENDER">%1$s</xliff:g> je pripravljena na pošiljanje datoteke <xliff:g id="FILE">%2$s</xliff:g>."</string>
     <string name="notification_receiving" msgid="4674648179652543984">"Bluetooth: Prejemanje <xliff:g id="FILE">%1$s</xliff:g>"</string>
     <string name="notification_received" msgid="3324588019186687985">"Bluetooth: Prejeto <xliff:g id="FILE">%1$s</xliff:g>"</string>
     <string name="notification_received_fail" msgid="3619350997285714746">"Bluetooth: Datoteka <xliff:g id="FILE">%1$s</xliff:g> ni bila prejeta"</string>
@@ -83,8 +83,8 @@
     <string name="bt_toast_4" msgid="4678812947604395649">"Pošiljanje datoteke prejemniku »<xliff:g id="RECIPIENT">%1$s</xliff:g>«"</string>
     <string name="bt_toast_5" msgid="2846870992823019494">"Pošiljanje <xliff:g id="NUMBER">%1$s</xliff:g> datotek prejemniku »<xliff:g id="RECIPIENT">%2$s</xliff:g>«"</string>
     <string name="bt_toast_6" msgid="1855266596936622458">"Pošiljanje datoteke prejemniku »<xliff:g id="RECIPIENT">%1$s</xliff:g>« je ustavljeno"</string>
-    <string name="bt_sm_2_1_nosdcard" msgid="1791835163301501637">"V shrambi USB ni dovolj prostora za shranjevanje datoteke pošiljatelja »<xliff:g id="SENDER">%1$s</xliff:g>«"</string>
-    <string name="bt_sm_2_1_default" msgid="9115512207909504071">"Na kartici SD ni dovolj prostora za shranjevanje datoteke pošiljatelja »<xliff:g id="SENDER">%1$s</xliff:g>«"</string>
+    <string name="bt_sm_2_1_nosdcard" msgid="5354343190503952837">"V shrambi USB ni dovolj prostora za shranjevanje datoteke."</string>
+    <string name="bt_sm_2_1_default" msgid="2497541206648973852">"Na kartici SD ni dovolj prostora za shranjevanje datoteke."</string>
     <string name="bt_sm_2_2" msgid="2965243265852680543">"Zahtevani prostor: <xliff:g id="SIZE">%1$s</xliff:g>"</string>
     <string name="ErrorTooManyRequests" msgid="8578277541472944529">"V obdelavi je preveč zahtev. Poskusite znova pozneje."</string>
     <string name="status_pending" msgid="2503691772030877944">"Prenos datoteke se še ni začel."</string>
diff --git a/res/values-sq/strings.xml b/res/values-sq/strings.xml
index 2aa3128..a3bd54e 100644
--- a/res/values-sq/strings.xml
+++ b/res/values-sq/strings.xml
@@ -18,8 +18,8 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="permlab_bluetoothShareManager" msgid="311492132450338925">"Qasu në menaxherin e shkarkimit."</string>
     <string name="permdesc_bluetoothShareManager" msgid="8930572979123190223">"Lejon aplikacionin të ketë qasje në menaxherin \"Shpërndarje përmes bluetooth-it\" (BluetoothShare) dhe ta përdorë për transferim skedarësh."</string>
-    <string name="permlab_bluetoothWhitelist" msgid="7091552898592306386">"Qasje në listën e miratimeve të pajisjes me bluetooth."</string>
-    <string name="permdesc_bluetoothWhitelist" msgid="5494513855192170109">"Lejon aplikacionin të vërë përkohësisht në listën e miratimeve një pajisje bluetooth-i, duke e lejuar atë t\'i dërgojë skedarë kësaj pajisjeje pa konfirmimin e përdoruesit."</string>
+    <string name="permlab_bluetoothAcceptlist" msgid="2647575807648622053">"Autorizimi në listën e pranimit të pajisjes me Bluetooth."</string>
+    <string name="permdesc_bluetoothAcceptlist" msgid="2406423665674766442">"Lejon aplikacionin të vërë përkohësisht në listën e miratimeve një pajisje me Bluetooth, duke i lejuar asaj t\'i dërgojë skedarë kësaj pajisjeje pa konfirmimin e përdoruesit."</string>
     <string name="bt_share_picker_label" msgid="6268100924487046932">"Bluetooth"</string>
     <string name="unknown_device" msgid="9221903979877041009">"Pajisje e panjohur"</string>
     <string name="unknownNumber" msgid="4994750948072751566">"I panjohur"</string>
@@ -37,7 +37,7 @@
     <string name="incoming_file_confirm_timeout_ok" msgid="1414676773249857278">"Në rregull!"</string>
     <string name="incoming_file_confirm_timeout_content" msgid="172779756093975981">"Përfundoi koha e veprimit për pranimin e skedarit hyrës nga \"<xliff:g id="SENDER">%1$s</xliff:g>\""</string>
     <string name="incoming_file_confirm_Notification_title" msgid="5573329005298936903">"Skedari në ardhje"</string>
-    <string name="incoming_file_confirm_Notification_content" msgid="3359694069319644738">"<xliff:g id="SENDER">%1$s</xliff:g> është gati për të dërguar <xliff:g id="FILE">%2$s</xliff:g>"</string>
+    <string name="incoming_file_confirm_Notification_content" msgid="6237487486781004992">"<xliff:g id="SENDER">%1$s</xliff:g> është gati të dërgojë një skedar: <xliff:g id="FILE">%2$s</xliff:g>"</string>
     <string name="notification_receiving" msgid="4674648179652543984">"Shpërndarja përmes bluetooth-it: Po merret skedari <xliff:g id="FILE">%1$s</xliff:g>"</string>
     <string name="notification_received" msgid="3324588019186687985">"Shpërndarja përmes bluetooth-it: U pranua skedari <xliff:g id="FILE">%1$s</xliff:g>"</string>
     <string name="notification_received_fail" msgid="3619350997285714746">"Shpërndarja përmes bluetooth-it: Skedari <xliff:g id="FILE">%1$s</xliff:g> nuk u pranua"</string>
@@ -83,8 +83,8 @@
     <string name="bt_toast_4" msgid="4678812947604395649">"Po e dërgon skedarin te \"<xliff:g id="RECIPIENT">%1$s</xliff:g>\""</string>
     <string name="bt_toast_5" msgid="2846870992823019494">"Po dërgon <xliff:g id="NUMBER">%1$s</xliff:g> skedarë te \"<xliff:g id="RECIPIENT">%2$s</xliff:g>\""</string>
     <string name="bt_toast_6" msgid="1855266596936622458">"Ndaloi dërgimin e skedarit te \"<xliff:g id="RECIPIENT">%1$s</xliff:g>\""</string>
-    <string name="bt_sm_2_1_nosdcard" msgid="1791835163301501637">"Nuk ka hapësirë të mjaftueshme në USB për të ruajtur skedarin nga \"<xliff:g id="SENDER">%1$s</xliff:g>\""</string>
-    <string name="bt_sm_2_1_default" msgid="9115512207909504071">"Nuk ka hapësirë të mjaftueshme në kartën SD për të ruajtur skedarin nga \"<xliff:g id="SENDER">%1$s</xliff:g>\""</string>
+    <string name="bt_sm_2_1_nosdcard" msgid="5354343190503952837">"Nuk ka hapësirë të mjaftueshme në USB për të ruajtur skedarin."</string>
+    <string name="bt_sm_2_1_default" msgid="2497541206648973852">"Nuk ka hapësirë të mjaftueshme në kartën SD për ta ruajtur skedarin."</string>
     <string name="bt_sm_2_2" msgid="2965243265852680543">"Hapësira e nevojshme: <xliff:g id="SIZE">%1$s</xliff:g>"</string>
     <string name="ErrorTooManyRequests" msgid="8578277541472944529">"Po përpunohen shumë kërkesa. Provo sërish më vonë."</string>
     <string name="status_pending" msgid="2503691772030877944">"Transferimi i skedarit nuk ka filluar ende."</string>
diff --git a/res/values-sr/strings.xml b/res/values-sr/strings.xml
index 9a649c5..ce2e530 100644
--- a/res/values-sr/strings.xml
+++ b/res/values-sr/strings.xml
@@ -18,8 +18,8 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="permlab_bluetoothShareManager" msgid="311492132450338925">"Приступ менаџеру преузимања."</string>
     <string name="permdesc_bluetoothShareManager" msgid="8930572979123190223">"Омогућава апликацији да приступа менаџеру за дељење преко Bluetooth-а и да га користи за пренос датотека."</string>
-    <string name="permlab_bluetoothWhitelist" msgid="7091552898592306386">"Приступ Bluetooth уређаја са беле листе."</string>
-    <string name="permdesc_bluetoothWhitelist" msgid="5494513855192170109">"Дозвољава апликацији да привремено стави Bluetooth уређај на белу листу, што омогућава том уређају да шаље датотеке овом уређају без потврде корисника."</string>
+    <string name="permlab_bluetoothAcceptlist" msgid="2647575807648622053">"Стави приступ Bluetooth уређаја на листу прихваћених уређаја."</string>
+    <string name="permdesc_bluetoothAcceptlist" msgid="2406423665674766442">"Дозвољава апликацији да привремено стави Bluetooth уређај на листу прихваћених уређаја, што омогућава том уређају да шаље датотеке овом уређају без одобрења корисника."</string>
     <string name="bt_share_picker_label" msgid="6268100924487046932">"Bluetooth"</string>
     <string name="unknown_device" msgid="9221903979877041009">"Непознати уређај"</string>
     <string name="unknownNumber" msgid="4994750948072751566">"Непознато"</string>
@@ -37,7 +37,7 @@
     <string name="incoming_file_confirm_timeout_ok" msgid="1414676773249857278">"Потврди"</string>
     <string name="incoming_file_confirm_timeout_content" msgid="172779756093975981">"Дошло је до временског ограничења током пријема долазне датотеке од „<xliff:g id="SENDER">%1$s</xliff:g>“"</string>
     <string name="incoming_file_confirm_Notification_title" msgid="5573329005298936903">"Долазна датотека"</string>
-    <string name="incoming_file_confirm_Notification_content" msgid="3359694069319644738">"<xliff:g id="SENDER">%1$s</xliff:g> је спреман/на да пошаље <xliff:g id="FILE">%2$s</xliff:g>"</string>
+    <string name="incoming_file_confirm_Notification_content" msgid="6237487486781004992">"<xliff:g id="SENDER">%1$s</xliff:g> је спреман/на за слање фајла: <xliff:g id="FILE">%2$s</xliff:g>"</string>
     <string name="notification_receiving" msgid="4674648179652543984">"Bluetooth дељење: пријем <xliff:g id="FILE">%1$s</xliff:g>"</string>
     <string name="notification_received" msgid="3324588019186687985">"Bluetooth дељење: примљено <xliff:g id="FILE">%1$s</xliff:g>"</string>
     <string name="notification_received_fail" msgid="3619350997285714746">"Bluetooth дељење: датотека <xliff:g id="FILE">%1$s</xliff:g> није примљена"</string>
@@ -83,8 +83,8 @@
     <string name="bt_toast_4" msgid="4678812947604395649">"Слање датотеке примаоцу „<xliff:g id="RECIPIENT">%1$s</xliff:g>“"</string>
     <string name="bt_toast_5" msgid="2846870992823019494">"Слање<xliff:g id="NUMBER">%1$s</xliff:g> датотека примаоцу „<xliff:g id="RECIPIENT">%2$s</xliff:g>“"</string>
     <string name="bt_toast_6" msgid="1855266596936622458">"Заустављено слање примаоцу „<xliff:g id="RECIPIENT">%1$s</xliff:g>“"</string>
-    <string name="bt_sm_2_1_nosdcard" msgid="1791835163301501637">"Нема довољно простора у USB меморији да би се сачувала датотека пошиљаоца „<xliff:g id="SENDER">%1$s</xliff:g>“"</string>
-    <string name="bt_sm_2_1_default" msgid="9115512207909504071">"Нема довољно простора на SD картици да би се сачувала датотека пошиљаоца „<xliff:g id="SENDER">%1$s</xliff:g>“"</string>
+    <string name="bt_sm_2_1_nosdcard" msgid="5354343190503952837">"Нема довољно простора у USB меморији за чување датотеке."</string>
+    <string name="bt_sm_2_1_default" msgid="2497541206648973852">"Нема довољно простора на SD картици за чување датотеке."</string>
     <string name="bt_sm_2_2" msgid="2965243265852680543">"Потребан простор: <xliff:g id="SIZE">%1$s</xliff:g>"</string>
     <string name="ErrorTooManyRequests" msgid="8578277541472944529">"Превише захтева се обрађује. Пробајте поново касније."</string>
     <string name="status_pending" msgid="2503691772030877944">"Пренос датотеке још није почео."</string>
diff --git a/res/values-sv/strings.xml b/res/values-sv/strings.xml
index a1d9b37..a4aeb79 100644
--- a/res/values-sv/strings.xml
+++ b/res/values-sv/strings.xml
@@ -18,8 +18,8 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="permlab_bluetoothShareManager" msgid="311492132450338925">"Åtkomst till nedladdningshanterare."</string>
     <string name="permdesc_bluetoothShareManager" msgid="8930572979123190223">"Tillåter att appen använder BluetoothShare-hanteraren vid överföring av filer."</string>
-    <string name="permlab_bluetoothWhitelist" msgid="7091552898592306386">"Lägga till åtkomst för Bluetooth-enheter på den godkända listan"</string>
-    <string name="permdesc_bluetoothWhitelist" msgid="5494513855192170109">"Appen tillåts lägga till en Bluetooth-enhet tillfälligt på den godkända listan, vilket gör det möjligt att skicka filer mellan olika enheter utan bekräftelse från användaren."</string>
+    <string name="permlab_bluetoothAcceptlist" msgid="2647575807648622053">"Lägg till åtkomst för Bluetooth-enheten på godkännandelistan."</string>
+    <string name="permdesc_bluetoothAcceptlist" msgid="2406423665674766442">"Tillåter att appen tillfälligt lägger till en Bluetooth-enhet på godkännandelistan så att det går att skicka filer från den enheten till den här utan bekräftelse från användaren."</string>
     <string name="bt_share_picker_label" msgid="6268100924487046932">"Bluetooth"</string>
     <string name="unknown_device" msgid="9221903979877041009">"Okänd enhet"</string>
     <string name="unknownNumber" msgid="4994750948072751566">"Okänd"</string>
@@ -37,7 +37,7 @@
     <string name="incoming_file_confirm_timeout_ok" msgid="1414676773249857278">"OK"</string>
     <string name="incoming_file_confirm_timeout_content" msgid="172779756093975981">"Tidsgränsen överskreds när en inkommande fil från <xliff:g id="SENDER">%1$s</xliff:g> skulle tas emot"</string>
     <string name="incoming_file_confirm_Notification_title" msgid="5573329005298936903">"Inkommande fil"</string>
-    <string name="incoming_file_confirm_Notification_content" msgid="3359694069319644738">"<xliff:g id="SENDER">%1$s</xliff:g> är klar att skicka <xliff:g id="FILE">%2$s</xliff:g>"</string>
+    <string name="incoming_file_confirm_Notification_content" msgid="6237487486781004992">"<xliff:g id="SENDER">%1$s</xliff:g> är redo att skicka en fil: <xliff:g id="FILE">%2$s</xliff:g>"</string>
     <string name="notification_receiving" msgid="4674648179652543984">"Bluetooth-delning: tar emot <xliff:g id="FILE">%1$s</xliff:g>"</string>
     <string name="notification_received" msgid="3324588019186687985">"Bluetooth-delning: <xliff:g id="FILE">%1$s</xliff:g> har tagits emot"</string>
     <string name="notification_received_fail" msgid="3619350997285714746">"Bluetooth-delning: filen <xliff:g id="FILE">%1$s</xliff:g> har inte tagits emot"</string>
@@ -83,8 +83,8 @@
     <string name="bt_toast_4" msgid="4678812947604395649">"Skickar fil till <xliff:g id="RECIPIENT">%1$s</xliff:g>"</string>
     <string name="bt_toast_5" msgid="2846870992823019494">"Skickar <xliff:g id="NUMBER">%1$s</xliff:g> filer till <xliff:g id="RECIPIENT">%2$s</xliff:g>"</string>
     <string name="bt_toast_6" msgid="1855266596936622458">"Filöverföringen till <xliff:g id="RECIPIENT">%1$s</xliff:g> har avbrutits"</string>
-    <string name="bt_sm_2_1_nosdcard" msgid="1791835163301501637">"Det finns inte tillräckligt mycket utrymme på USB-lagringsenheten för att spara filen från <xliff:g id="SENDER">%1$s</xliff:g>"</string>
-    <string name="bt_sm_2_1_default" msgid="9115512207909504071">"Det finns för lite utrymme på SD-kortet för att spara filen från <xliff:g id="SENDER">%1$s</xliff:g>"</string>
+    <string name="bt_sm_2_1_nosdcard" msgid="5354343190503952837">"Det finns inte tillräckligt mycket utrymme på USB-lagringsenheten för att spara filen."</string>
+    <string name="bt_sm_2_1_default" msgid="2497541206648973852">"Det finns för lite utrymme på SD-kortet för att spara filen."</string>
     <string name="bt_sm_2_2" msgid="2965243265852680543">"Utrymmesbehov: <xliff:g id="SIZE">%1$s</xliff:g>"</string>
     <string name="ErrorTooManyRequests" msgid="8578277541472944529">"För många begäranden bearbetas. Försök igen senare."</string>
     <string name="status_pending" msgid="2503691772030877944">"Filöverföringen har inte börjat."</string>
diff --git a/res/values-sw/strings.xml b/res/values-sw/strings.xml
index b6d3e7c..d79e75a 100644
--- a/res/values-sw/strings.xml
+++ b/res/values-sw/strings.xml
@@ -18,8 +18,8 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="permlab_bluetoothShareManager" msgid="311492132450338925">"Fikia kidhibiti cha vipakuliwa."</string>
     <string name="permdesc_bluetoothShareManager" msgid="8930572979123190223">"Huruhusu programu kufikia kidhibiti cha BluetoothShare na kukitumia kuhamisha faili."</string>
-    <string name="permlab_bluetoothWhitelist" msgid="7091552898592306386">"Ruhusu ufikiaji kifaa cha bluetooth."</string>
-    <string name="permdesc_bluetoothWhitelist" msgid="5494513855192170109">"Huruhusu programu kuorodhesha kwa muda kifaa cha Bluetooth kwenye orodha ya vifaa maalum, ikiruhusu kifaa hicho kutuma faili kwa kifaa hiki bila uthibitishaji wa mtumiaji."</string>
+    <string name="permlab_bluetoothAcceptlist" msgid="2647575807648622053">"Orodha ya vifaa vyenye bluetooth vilivyoruhusiwa kufikia."</string>
+    <string name="permdesc_bluetoothAcceptlist" msgid="2406423665674766442">"Huruhusu programu kuorodhesha kwa muda Kifaa chenye Bluetooth kwenye orodha ya vifaa vilivyoruhusiwa, ikiruhusu kifaa hicho kutuma faili kwenye kifaa hiki bila uthibitishaji wa mtumiaji."</string>
     <string name="bt_share_picker_label" msgid="6268100924487046932">"Bluetooth"</string>
     <string name="unknown_device" msgid="9221903979877041009">"Kifaa kisichojulikana"</string>
     <string name="unknownNumber" msgid="4994750948072751566">"Haijulikani"</string>
@@ -37,7 +37,7 @@
     <string name="incoming_file_confirm_timeout_ok" msgid="1414676773249857278">"Sawa"</string>
     <string name="incoming_file_confirm_timeout_content" msgid="172779756093975981">"Muda ulikatika wakati wa kukubali faili inayoingia kutoka \"<xliff:g id="SENDER">%1$s</xliff:g>\""</string>
     <string name="incoming_file_confirm_Notification_title" msgid="5573329005298936903">"Faili Zinazoingia"</string>
-    <string name="incoming_file_confirm_Notification_content" msgid="3359694069319644738">"<xliff:g id="SENDER">%1$s</xliff:g> ako tayari kutuma <xliff:g id="FILE">%2$s</xliff:g>"</string>
+    <string name="incoming_file_confirm_Notification_content" msgid="6237487486781004992">"<xliff:g id="SENDER">%1$s</xliff:g> ako tayari kutuma faili: <xliff:g id="FILE">%2$s</xliff:g>"</string>
     <string name="notification_receiving" msgid="4674648179652543984">"Kushiriki kwa bluetooth: Inapokea <xliff:g id="FILE">%1$s</xliff:g>"</string>
     <string name="notification_received" msgid="3324588019186687985">"Kushiriki kwa bluetooth: Imepokea <xliff:g id="FILE">%1$s</xliff:g>"</string>
     <string name="notification_received_fail" msgid="3619350997285714746">"Kushiriki kwa Bluetooth: Faili <xliff:g id="FILE">%1$s</xliff:g> haijapokewa"</string>
@@ -83,8 +83,8 @@
     <string name="bt_toast_4" msgid="4678812947604395649">"Inatuma faili kwa \"<xliff:g id="RECIPIENT">%1$s</xliff:g>\""</string>
     <string name="bt_toast_5" msgid="2846870992823019494">"Inatuma faili <xliff:g id="NUMBER">%1$s</xliff:g> kwa \"<xliff:g id="RECIPIENT">%2$s</xliff:g>\""</string>
     <string name="bt_toast_6" msgid="1855266596936622458">"Ilikomesha utumaji faili kwa \"<xliff:g id="RECIPIENT">%1$s</xliff:g>\""</string>
-    <string name="bt_sm_2_1_nosdcard" msgid="1791835163301501637">"Hakuna nafasi ya kutosha katika hifadhi ya USB ya kuhifadhi faili kutoka kwa \"<xliff:g id="SENDER">%1$s</xliff:g>\""</string>
-    <string name="bt_sm_2_1_default" msgid="9115512207909504071">"Hakuna nafasi ya kutosha katika kadi ya SD ya kuhifadhi faili kutoka kwa \"<xliff:g id="SENDER">%1$s</xliff:g>\""</string>
+    <string name="bt_sm_2_1_nosdcard" msgid="5354343190503952837">"Nafasi haitoshi kuhifadhi faili kwenye hifadhi ya USB."</string>
+    <string name="bt_sm_2_1_default" msgid="2497541206648973852">"Nafasi haitoshi kuhifadhi faili kwenye kadi ya SD."</string>
     <string name="bt_sm_2_2" msgid="2965243265852680543">"Nafasi inayohitajika: <xliff:g id="SIZE">%1$s</xliff:g>"</string>
     <string name="ErrorTooManyRequests" msgid="8578277541472944529">"Maombi mengi sana yanashughulikiwa. Jaribu tena baadaye."</string>
     <string name="status_pending" msgid="2503691772030877944">"Uhamishaji wa faili bado haijaanzishwa."</string>
diff --git a/res/values-ta/strings.xml b/res/values-ta/strings.xml
index 061ce47..ab946a6 100644
--- a/res/values-ta/strings.xml
+++ b/res/values-ta/strings.xml
@@ -18,8 +18,8 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="permlab_bluetoothShareManager" msgid="311492132450338925">"பதிவிறக்க நிர்வாகியை அணுகவும்."</string>
     <string name="permdesc_bluetoothShareManager" msgid="8930572979123190223">"BluetoothShare நிர்வாகியை அணுகுவதற்குப் ஆப்ஸை அனுமதித்து,  ஃபைல்களைப் பரிமாற்ற அதைப் பயன்படுத்துகிறது."</string>
-    <string name="permlab_bluetoothWhitelist" msgid="7091552898592306386">"ஏற்புபட்டியல் புளூடூத் சாதன அணுகல்."</string>
-    <string name="permdesc_bluetoothWhitelist" msgid="5494513855192170109">"புளூடூத் சாதனத்தைத் தற்காலிகமாக ஏற்புபட்டியலில் சேர்க்க ஆப்ஸை அனுமதிக்கிறது, பயனரின் உறுதிபடுத்தலின்றி கோப்புகளை இந்தச் சாதனத்திற்கு அனுப்ப அதை அனுமதிக்கிறது."</string>
+    <string name="permlab_bluetoothAcceptlist" msgid="2647575807648622053">"புளூடூத் சாதன அணுகல் ஏற்புப்பட்டியல்."</string>
+    <string name="permdesc_bluetoothAcceptlist" msgid="2406423665674766442">"புளூடூத் சாதனத்தைத் தற்காலிகமாக ஏற்புப் பட்டியலில் சேர்க்க ஆப்ஸை அனுமதிக்கிறது, பயனரின் உறுதிப்படுத்தலின்றி ஃபைல்களை இந்தச் சாதனத்திற்கு அனுப்பலாம்."</string>
     <string name="bt_share_picker_label" msgid="6268100924487046932">"புளூடூத்"</string>
     <string name="unknown_device" msgid="9221903979877041009">"அறியப்படாத சாதனம்"</string>
     <string name="unknownNumber" msgid="4994750948072751566">"அறியப்படாதது"</string>
@@ -37,7 +37,7 @@
     <string name="incoming_file_confirm_timeout_ok" msgid="1414676773249857278">"சரி"</string>
     <string name="incoming_file_confirm_timeout_content" msgid="172779756093975981">"\"<xliff:g id="SENDER">%1$s</xliff:g>\" இடமிருந்து வரும் ஃபைலை ஏற்கும்போது நேரம் முடிந்தது"</string>
     <string name="incoming_file_confirm_Notification_title" msgid="5573329005298936903">"உள்வரும் ஃபைல்"</string>
-    <string name="incoming_file_confirm_Notification_content" msgid="3359694069319644738">"<xliff:g id="SENDER">%1$s</xliff:g>, <xliff:g id="FILE">%2$s</xliff:g> ஃபைலை அனுப்புவதற்குத் தயாராக உள்ளார்"</string>
+    <string name="incoming_file_confirm_Notification_content" msgid="6237487486781004992">"<xliff:g id="FILE">%2$s</xliff:g> ஃபைலை அனுப்ப <xliff:g id="SENDER">%1$s</xliff:g> தயாராக உள்ளார்"</string>
     <string name="notification_receiving" msgid="4674648179652543984">"புளூடூத் பகிர்வு: <xliff:g id="FILE">%1$s</xliff:g> ஐப் பெறுகிறது"</string>
     <string name="notification_received" msgid="3324588019186687985">"புளூடூத் பகிர்வு: <xliff:g id="FILE">%1$s</xliff:g> பெறப்பட்டது"</string>
     <string name="notification_received_fail" msgid="3619350997285714746">"புளூடூத் பகிர்வு: <xliff:g id="FILE">%1$s</xliff:g> ஐப் பெறவில்லை"</string>
@@ -72,7 +72,7 @@
     <string name="upload_fail_cancel" msgid="9118496285835687125">"மூடு"</string>
     <string name="bt_error_btn_ok" msgid="5965151173011534240">"சரி"</string>
     <string name="unknown_file" msgid="6092727753965095366">"அறியப்படாத ஃபைல்"</string>
-    <string name="unknown_file_desc" msgid="480434281415453287">"இந்த வகையான கோப்பைக் கையாள எந்தப் பயன்பாடும் இல்லை. \n"</string>
+    <string name="unknown_file_desc" msgid="480434281415453287">"இந்த வகையான ஃபைலைக் கையாள எந்தப் பயன்பாடும் இல்லை. \n"</string>
     <string name="not_exist_file" msgid="3489434189599716133">"ஃபைல் இல்லை"</string>
     <string name="not_exist_file_desc" msgid="4059531573790529229">"ஃபைல் இல்லை. \n"</string>
     <string name="enabling_progress_title" msgid="436157952334723406">"காத்திருக்கவும்…"</string>
@@ -83,8 +83,8 @@
     <string name="bt_toast_4" msgid="4678812947604395649">"\"<xliff:g id="RECIPIENT">%1$s</xliff:g>\" க்கு ஃபைல் அனுப்பப்படுகிறது"</string>
     <string name="bt_toast_5" msgid="2846870992823019494">"<xliff:g id="NUMBER">%1$s</xliff:g> ஃபைல்கள்  \"<xliff:g id="RECIPIENT">%2$s</xliff:g>\" க்கு அனுப்பப்படுகின்றன"</string>
     <string name="bt_toast_6" msgid="1855266596936622458">"\"<xliff:g id="RECIPIENT">%1$s</xliff:g>\" க்கு ஃபைல் அனுப்புவது நிறுத்தப்பட்டது"</string>
-    <string name="bt_sm_2_1_nosdcard" msgid="1791835163301501637">"\"<xliff:g id="SENDER">%1$s</xliff:g>\" அனுப்பும் ஃபைலைச் சேமிக்க USB சேமிப்பிடத்தில் போதுமான இடம் இல்லை"</string>
-    <string name="bt_sm_2_1_default" msgid="9115512207909504071">"\"<xliff:g id="SENDER">%1$s</xliff:g>\" அனுப்பும் ஃபைலைச் சேமிக்க SD கார்டில் போதுமான இடம் இல்லை"</string>
+    <string name="bt_sm_2_1_nosdcard" msgid="5354343190503952837">"ஃபைலைச் சேமிக்க USB சேமிப்பகத்தில் போதுமான இடம் இல்லை."</string>
+    <string name="bt_sm_2_1_default" msgid="2497541206648973852">"ஃபைலைச் சேமிக்க SD கார்டில் போதுமான இடம் இல்லை."</string>
     <string name="bt_sm_2_2" msgid="2965243265852680543">"தேவைப்படும் இடம்: <xliff:g id="SIZE">%1$s</xliff:g>"</string>
     <string name="ErrorTooManyRequests" msgid="8578277541472944529">"மிக அதிகமான கோரிக்கைகள் செயல்படுத்தப்படுகின்றன. பிறகு முயற்சிக்கவும்."</string>
     <string name="status_pending" msgid="2503691772030877944">"ஃபைல் பரிமாற்றம் இன்னும் தொடங்கவில்லை."</string>
diff --git a/res/values-te/strings.xml b/res/values-te/strings.xml
index 9aea3e5..9c52281 100644
--- a/res/values-te/strings.xml
+++ b/res/values-te/strings.xml
@@ -18,8 +18,8 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="permlab_bluetoothShareManager" msgid="311492132450338925">"డౌన్‌లోడ్ మేనేజర్‌ను యాక్సెస్ చేయండి."</string>
     <string name="permdesc_bluetoothShareManager" msgid="8930572979123190223">"బ్లూటూత్ భాగస్వామ్య మేనేజర్‌ను యాక్సెస్ చేయడానికి యాప్‌ను అనుమతిస్తుంది మరియు ఫైళ్లను బదిలీ చేయడానికి దీన్ని ఉపయోగిస్తుంది."</string>
-    <string name="permlab_bluetoothWhitelist" msgid="7091552898592306386">"అనుమతి లిస్ట్‌లో ఉంచిన బ్లూటూత్ పరికర యాక్సెస్."</string>
-    <string name="permdesc_bluetoothWhitelist" msgid="5494513855192170109">"బ్లూటూత్ పరికరాన్ని తాత్కాలికంగా అనుమతి లిస్ట్‌లో ఉంచడానికి యాప్‌ను అనుమతిస్తుంది, తద్వారా వినియోగదారు నిర్ధారణ లేకుండానే ఫైళ్లను ఈ పరికరానికి పంపడానికి ఆ పరికరాన్ని అనుమతిస్తుంది."</string>
+    <string name="permlab_bluetoothAcceptlist" msgid="2647575807648622053">"అనుమతి లిస్ట్‌లో ఉంచిన బ్లూటూత్ పరికర యాక్సెస్."</string>
+    <string name="permdesc_bluetoothAcceptlist" msgid="2406423665674766442">"బ్లూటూత్ పరికరాన్ని తాత్కాలికంగా అనుమతి లిస్ట్‌లో ఉంచడానికి యాప్‌ను అనుమతిస్తుంది, తద్వారా యూజర్ నిర్ధారణ లేకుండానే ఫైళ్లను ఈ పరికరానికి పంపడానికి ఆ పరికరాన్ని అనుమతిస్తుంది."</string>
     <string name="bt_share_picker_label" msgid="6268100924487046932">"బ్లూటూత్"</string>
     <string name="unknown_device" msgid="9221903979877041009">"తెలియని పరికరం"</string>
     <string name="unknownNumber" msgid="4994750948072751566">"తెలియదు"</string>
@@ -37,7 +37,7 @@
     <string name="incoming_file_confirm_timeout_ok" msgid="1414676773249857278">"సరే"</string>
     <string name="incoming_file_confirm_timeout_content" msgid="172779756093975981">"\"<xliff:g id="SENDER">%1$s</xliff:g>\" పంపిన ఇన్‌కమింగ్ ఫైల్‌ను అంగీకరిస్తున్నప్పుడు గడువు సమయం ముగిసింది"</string>
     <string name="incoming_file_confirm_Notification_title" msgid="5573329005298936903">"ఇన్‌క‌మింగ్‌ ఫైల్"</string>
-    <string name="incoming_file_confirm_Notification_content" msgid="3359694069319644738">"<xliff:g id="SENDER">%1$s</xliff:g> <xliff:g id="FILE">%2$s</xliff:g> ఫైల్ పంపడానికి సిద్ధంగా ఉన్నారు"</string>
+    <string name="incoming_file_confirm_Notification_content" msgid="6237487486781004992">"<xliff:g id="SENDER">%1$s</xliff:g> ఫైల్‌ను పంపడానికి సిద్ధంగా ఉన్నారు: <xliff:g id="FILE">%2$s</xliff:g>"</string>
     <string name="notification_receiving" msgid="4674648179652543984">"బ్లూటూత్ షేర్: <xliff:g id="FILE">%1$s</xliff:g>ను స్వీకరిస్తోంది"</string>
     <string name="notification_received" msgid="3324588019186687985">"బ్లూటూత్ షేర్: <xliff:g id="FILE">%1$s</xliff:g> స్వీకరించబడింది"</string>
     <string name="notification_received_fail" msgid="3619350997285714746">"బ్లూటూత్ షేర్: <xliff:g id="FILE">%1$s</xliff:g> ఫైల్ స్వీకరించబడలేదు"</string>
@@ -83,8 +83,8 @@
     <string name="bt_toast_4" msgid="4678812947604395649">"ఫైల్‌ను \"<xliff:g id="RECIPIENT">%1$s</xliff:g>\"కి పంపుతోంది"</string>
     <string name="bt_toast_5" msgid="2846870992823019494">"<xliff:g id="NUMBER">%1$s</xliff:g> ఫైళ్లను \"<xliff:g id="RECIPIENT">%2$s</xliff:g>\"కి పంపుతోంది"</string>
     <string name="bt_toast_6" msgid="1855266596936622458">"ఫైల్‌ను \"<xliff:g id="RECIPIENT">%1$s</xliff:g>\"కి పంపడం ఆపివేయబడింది"</string>
-    <string name="bt_sm_2_1_nosdcard" msgid="1791835163301501637">"\"<xliff:g id="SENDER">%1$s</xliff:g>\" పంపిన పైల్‌ను సేవ్ చేయడానికి USB నిల్వలో తగినంత స్థలం లేదు"</string>
-    <string name="bt_sm_2_1_default" msgid="9115512207909504071">"\"<xliff:g id="SENDER">%1$s</xliff:g>\" పంపిన పైల్‌ను సేవ్ చేయడానికి SD కార్డులో తగినంత స్థలం లేదు"</string>
+    <string name="bt_sm_2_1_nosdcard" msgid="5354343190503952837">"ఫైల్‌ను సేవ్ చేయడానికి USB స్టోరేజ్‌లో సరిపడేంత స్పేస్ లేదు."</string>
+    <string name="bt_sm_2_1_default" msgid="2497541206648973852">"ఫైల్‌ను సేవ్ చేయడానికి SD కార్డ్‌లో సరిపడేంత స్పేస్ లేదు."</string>
     <string name="bt_sm_2_2" msgid="2965243265852680543">"కావలసిన స్థలం: <xliff:g id="SIZE">%1$s</xliff:g>"</string>
     <string name="ErrorTooManyRequests" msgid="8578277541472944529">"చాలా ఎక్కువ రిక్వెస్ట్‌లు ప్రాసెస్ చేయబడుతున్నాయి. తర్వాత మళ్లీ ప్రయత్నించండి."</string>
     <string name="status_pending" msgid="2503691772030877944">"ఫైల్ బదిలీ ఇంకా ప్రారంభించబడలేదు."</string>
diff --git a/res/values-th/strings.xml b/res/values-th/strings.xml
index 869012c..2a3196c 100644
--- a/res/values-th/strings.xml
+++ b/res/values-th/strings.xml
@@ -18,8 +18,8 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="permlab_bluetoothShareManager" msgid="311492132450338925">"เข้าถึงตัวจัดการการดาวน์โหลด"</string>
     <string name="permdesc_bluetoothShareManager" msgid="8930572979123190223">"อนุญาตให้แอปพลิเคชันเข้าถึงและใช้ตัวจัดการ BluetoothShare เพื่อถ่ายโอนไฟล์"</string>
-    <string name="permlab_bluetoothWhitelist" msgid="7091552898592306386">"รายการที่อนุญาตการเข้าถึงอุปกรณ์บลูทูธ"</string>
-    <string name="permdesc_bluetoothWhitelist" msgid="5494513855192170109">"อนุญาตให้แอปบันทึกอุปกรณ์บลูทูธในรายการที่อนุญาตเป็นการชั่วคราว ซึ่งจะทำให้อุปกรณ์ดังกล่าวสามารถส่งไฟล์มายังอุปกรณ์นี้ได้โดยไม่ต้องยืนยันผู้ใช้"</string>
+    <string name="permlab_bluetoothAcceptlist" msgid="2647575807648622053">"รายการที่อนุญาตการเข้าถึงอุปกรณ์บลูทูธ"</string>
+    <string name="permdesc_bluetoothAcceptlist" msgid="2406423665674766442">"อนุญาตให้แอปบันทึกอุปกรณ์บลูทูธในรายการที่อนุญาตเป็นการชั่วคราว ซึ่งจะทำให้อุปกรณ์ดังกล่าวสามารถส่งไฟล์มายังอุปกรณ์นี้ได้โดยไม่ต้องยืนยันผู้ใช้"</string>
     <string name="bt_share_picker_label" msgid="6268100924487046932">"บลูทูธ"</string>
     <string name="unknown_device" msgid="9221903979877041009">"อุปกรณ์ที่ไม่รู้จัก"</string>
     <string name="unknownNumber" msgid="4994750948072751566">"ไม่รู้จัก"</string>
@@ -37,7 +37,7 @@
     <string name="incoming_file_confirm_timeout_ok" msgid="1414676773249857278">"ตกลง"</string>
     <string name="incoming_file_confirm_timeout_content" msgid="172779756093975981">"มีการหมดเวลาเกิดขึ้นขณะยอมรับไฟล์ขาเข้าจาก \"<xliff:g id="SENDER">%1$s</xliff:g>\""</string>
     <string name="incoming_file_confirm_Notification_title" msgid="5573329005298936903">"ไฟล์ที่เข้ามา"</string>
-    <string name="incoming_file_confirm_Notification_content" msgid="3359694069319644738">"<xliff:g id="SENDER">%1$s</xliff:g> พร้อมที่จะส่ง <xliff:g id="FILE">%2$s</xliff:g> แล้ว"</string>
+    <string name="incoming_file_confirm_Notification_content" msgid="6237487486781004992">"<xliff:g id="SENDER">%1$s</xliff:g> พร้อมที่จะส่งไฟล์: <xliff:g id="FILE">%2$s</xliff:g>"</string>
     <string name="notification_receiving" msgid="4674648179652543984">"การแชร์ทางบลูทูธ: กำลังรับ <xliff:g id="FILE">%1$s</xliff:g>"</string>
     <string name="notification_received" msgid="3324588019186687985">"การแชร์ทางบลูทูธ: รับ <xliff:g id="FILE">%1$s</xliff:g> แล้ว"</string>
     <string name="notification_received_fail" msgid="3619350997285714746">"การแชร์ทางบลูทูธ: ไม่ได้รับไฟล์ <xliff:g id="FILE">%1$s</xliff:g>"</string>
@@ -83,8 +83,8 @@
     <string name="bt_toast_4" msgid="4678812947604395649">"กำลังส่งไฟล์ถึง \"<xliff:g id="RECIPIENT">%1$s</xliff:g>\""</string>
     <string name="bt_toast_5" msgid="2846870992823019494">"กำลังส่ง <xliff:g id="NUMBER">%1$s</xliff:g> ไฟล์ถึง \"<xliff:g id="RECIPIENT">%2$s</xliff:g>\""</string>
     <string name="bt_toast_6" msgid="1855266596936622458">"หยุดส่งไฟล์ถึง \"<xliff:g id="RECIPIENT">%1$s</xliff:g>\""</string>
-    <string name="bt_sm_2_1_nosdcard" msgid="1791835163301501637">"ที่จัดเก็บข้อมูล USB มีพื้นที่ว่างไม่เพียงพอที่จะบันทึกไฟล์จาก \"<xliff:g id="SENDER">%1$s</xliff:g>\""</string>
-    <string name="bt_sm_2_1_default" msgid="9115512207909504071">"การ์ด SD มีพื้นที่ว่างไม่เพียงพอที่จะบันทึกไฟล์จาก \"<xliff:g id="SENDER">%1$s</xliff:g>\""</string>
+    <string name="bt_sm_2_1_nosdcard" msgid="5354343190503952837">"ที่จัดเก็บข้อมูล USB มีพื้นที่ว่างไม่เพียงพอที่จะบันทึกไฟล์"</string>
+    <string name="bt_sm_2_1_default" msgid="2497541206648973852">"การ์ด SD มีพื้นที่ว่างไม่เพียงพอที่จะบันทึกไฟล์"</string>
     <string name="bt_sm_2_2" msgid="2965243265852680543">"พื้นที่ที่ต้องใช้: <xliff:g id="SIZE">%1$s</xliff:g>"</string>
     <string name="ErrorTooManyRequests" msgid="8578277541472944529">"กำลังประมวลผลคำขอมากเกินไป โปรดลองใหม่อีกครั้ง"</string>
     <string name="status_pending" msgid="2503691772030877944">"ยังไม่ได้เริ่มการถ่ายโอนไฟล์"</string>
diff --git a/res/values-tl/strings.xml b/res/values-tl/strings.xml
index f0ee9c3..39f687c 100644
--- a/res/values-tl/strings.xml
+++ b/res/values-tl/strings.xml
@@ -18,8 +18,8 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="permlab_bluetoothShareManager" msgid="311492132450338925">"I-access ang tagapamahala ng pag-download."</string>
     <string name="permdesc_bluetoothShareManager" msgid="8930572979123190223">"Binibigyang-daan ang app na i-access ang BluetoothShare manager at gamitin ito upang maglipat ng mga file."</string>
-    <string name="permlab_bluetoothWhitelist" msgid="7091552898592306386">"Mag-whitelist ng access ng isang bluetooth device."</string>
-    <string name="permdesc_bluetoothWhitelist" msgid="5494513855192170109">"Pinapayagan ang app na pansamantalang mag-whitelist ng isang Bluetooth device, na pinapayagan ang device na iyon na magpadala ng mga file sa device na ito nang walang kumpirmasyon ng user."</string>
+    <string name="permlab_bluetoothAcceptlist" msgid="2647575807648622053">"Mag-acceptlist ng access ng bluetooth device."</string>
+    <string name="permdesc_bluetoothAcceptlist" msgid="2406423665674766442">"Pinapayagan ang app na pansamantalang mag-acceptlist ng Bluetooth device, na pinapayagan ang device na iyon na magpadala ng mga file sa device na ito nang walang kumpirmasyon ng user."</string>
     <string name="bt_share_picker_label" msgid="6268100924487046932">"Bluetooth"</string>
     <string name="unknown_device" msgid="9221903979877041009">"Hindi kilalang device"</string>
     <string name="unknownNumber" msgid="4994750948072751566">"Hindi alam"</string>
@@ -37,7 +37,7 @@
     <string name="incoming_file_confirm_timeout_ok" msgid="1414676773249857278">"OK"</string>
     <string name="incoming_file_confirm_timeout_content" msgid="172779756093975981">"Nagkaroon ng timeout habang tinatanggap ang papasok na file mula kay \"<xliff:g id="SENDER">%1$s</xliff:g>\""</string>
     <string name="incoming_file_confirm_Notification_title" msgid="5573329005298936903">"Papasok na file"</string>
-    <string name="incoming_file_confirm_Notification_content" msgid="3359694069319644738">"Handa nang ipadala ni <xliff:g id="SENDER">%1$s</xliff:g> ang <xliff:g id="FILE">%2$s</xliff:g>"</string>
+    <string name="incoming_file_confirm_Notification_content" msgid="6237487486781004992">"Handa nang magpadala ng file si <xliff:g id="SENDER">%1$s</xliff:g>: <xliff:g id="FILE">%2$s</xliff:g>"</string>
     <string name="notification_receiving" msgid="4674648179652543984">"Pagbahagi sa Bluetooth: Tinatanggap ang <xliff:g id="FILE">%1$s</xliff:g>"</string>
     <string name="notification_received" msgid="3324588019186687985">"Pagbahagi sa Bluetooth: Natanggap ang <xliff:g id="FILE">%1$s</xliff:g>"</string>
     <string name="notification_received_fail" msgid="3619350997285714746">"Pagbahagi sa Bluetooth: Hindi natanggap ang file na <xliff:g id="FILE">%1$s</xliff:g>"</string>
@@ -83,8 +83,8 @@
     <string name="bt_toast_4" msgid="4678812947604395649">"Nagpapadala ng file kay \"<xliff:g id="RECIPIENT">%1$s</xliff:g>\""</string>
     <string name="bt_toast_5" msgid="2846870992823019494">"Ipinapadala ang <xliff:g id="NUMBER">%1$s</xliff:g> (na) file kay \"<xliff:g id="RECIPIENT">%2$s</xliff:g>\""</string>
     <string name="bt_toast_6" msgid="1855266596936622458">"Ihininto ang pagpapadala ng file kay \"<xliff:g id="RECIPIENT">%1$s</xliff:g>\""</string>
-    <string name="bt_sm_2_1_nosdcard" msgid="1791835163301501637">"Walang sapat na espasyo sa USB storage para ma-save ang file mula kay \"<xliff:g id="SENDER">%1$s</xliff:g>\""</string>
-    <string name="bt_sm_2_1_default" msgid="9115512207909504071">"Walang sapat na espasyo sa SD card para ma-save ang file mula kay \"<xliff:g id="SENDER">%1$s</xliff:g>\""</string>
+    <string name="bt_sm_2_1_nosdcard" msgid="5354343190503952837">"Kulang ang espasyo sa USB storage para i-save ang file."</string>
+    <string name="bt_sm_2_1_default" msgid="2497541206648973852">"Kulang ang espasyo sa SD card para i-save ang file."</string>
     <string name="bt_sm_2_2" msgid="2965243265852680543">"Kailangang puwang: <xliff:g id="SIZE">%1$s</xliff:g>"</string>
     <string name="ErrorTooManyRequests" msgid="8578277541472944529">"Masyadong maraming kahilingan ang pinoproseso. Subukang muli sa ibang pagkakataon."</string>
     <string name="status_pending" msgid="2503691772030877944">"Hindi pa nasimulan ang paglilipat ng file."</string>
diff --git a/res/values-tr/strings.xml b/res/values-tr/strings.xml
index c1abc02..b8e57d4 100644
--- a/res/values-tr/strings.xml
+++ b/res/values-tr/strings.xml
@@ -18,8 +18,8 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="permlab_bluetoothShareManager" msgid="311492132450338925">"İndirme yöneticisine erişin."</string>
     <string name="permdesc_bluetoothShareManager" msgid="8930572979123190223">"Uygulamaya, BluetoothShare yöneticisine erişme ve bunu dosyaları aktarmak için kullanma izni verir."</string>
-    <string name="permlab_bluetoothWhitelist" msgid="7091552898592306386">"Bluetooth cihazı erişimine izin ver."</string>
-    <string name="permdesc_bluetoothWhitelist" msgid="5494513855192170109">"Uygulamaya, bir Bluetooth cihazını geçici olarak onaylananlar listesine ekleme izni verir. Böylece, izin verilen cihaz kullanıcının onayı olmadan bu cihaza dosya gönderebilir."</string>
+    <string name="permlab_bluetoothAcceptlist" msgid="2647575807648622053">"Onay listesi Bluetooth cihaz erişimi."</string>
+    <string name="permdesc_bluetoothAcceptlist" msgid="2406423665674766442">"Uygulamaya, bir Bluetooth cihazını geçici olarak onaylananlar listesine ekleme izni verir. Böylece, izin verilen cihaz kullanıcının onayı olmadan bu cihaza dosya gönderebilir."</string>
     <string name="bt_share_picker_label" msgid="6268100924487046932">"Bluetooth"</string>
     <string name="unknown_device" msgid="9221903979877041009">"Bilinmeyen cihaz"</string>
     <string name="unknownNumber" msgid="4994750948072751566">"Bilinmiyor"</string>
@@ -37,7 +37,7 @@
     <string name="incoming_file_confirm_timeout_ok" msgid="1414676773249857278">"Tamam"</string>
     <string name="incoming_file_confirm_timeout_content" msgid="172779756093975981">"\"<xliff:g id="SENDER">%1$s</xliff:g>\" kaynağından gelen dosyayı kabul etme süresi doldu"</string>
     <string name="incoming_file_confirm_Notification_title" msgid="5573329005298936903">"Gelen dosya"</string>
-    <string name="incoming_file_confirm_Notification_content" msgid="3359694069319644738">"<xliff:g id="SENDER">%1$s</xliff:g>, <xliff:g id="FILE">%2$s</xliff:g> adlı dosyayı göndermeye hazır"</string>
+    <string name="incoming_file_confirm_Notification_content" msgid="6237487486781004992">"<xliff:g id="SENDER">%1$s</xliff:g>, dosya göndermeye hazır: <xliff:g id="FILE">%2$s</xliff:g>"</string>
     <string name="notification_receiving" msgid="4674648179652543984">"Bluetooth paylaşımı: <xliff:g id="FILE">%1$s</xliff:g> alınıyor"</string>
     <string name="notification_received" msgid="3324588019186687985">"Bluetooth paylaşımı: <xliff:g id="FILE">%1$s</xliff:g> alındı"</string>
     <string name="notification_received_fail" msgid="3619350997285714746">"Bluetooth paylaşımı: <xliff:g id="FILE">%1$s</xliff:g> dosyası alınamadı"</string>
@@ -83,8 +83,8 @@
     <string name="bt_toast_4" msgid="4678812947604395649">"Dosya \"<xliff:g id="RECIPIENT">%1$s</xliff:g>\" hedefine gönderiliyor"</string>
     <string name="bt_toast_5" msgid="2846870992823019494">"<xliff:g id="NUMBER">%1$s</xliff:g> dosya \"<xliff:g id="RECIPIENT">%2$s</xliff:g>\" hedefine gönderiliyor"</string>
     <string name="bt_toast_6" msgid="1855266596936622458">"\"<xliff:g id="RECIPIENT">%1$s</xliff:g>\" hedefine dosya gönderme işlemi durduruldu"</string>
-    <string name="bt_sm_2_1_nosdcard" msgid="1791835163301501637">"USB depolama biriminde \"<xliff:g id="SENDER">%1$s</xliff:g>\" kaynağından gelen dosyayı kaydedecek kadar alan yok"</string>
-    <string name="bt_sm_2_1_default" msgid="9115512207909504071">"SD kartta \"<xliff:g id="SENDER">%1$s</xliff:g>\" kaynağından gelen dosyayı kaydedecek kadar alan yok"</string>
+    <string name="bt_sm_2_1_nosdcard" msgid="5354343190503952837">"USB depolamada dosyayı kaydedecek kadar alan yok."</string>
+    <string name="bt_sm_2_1_default" msgid="2497541206648973852">"SD kartta dosyayı kaydedecek kadar alan yok."</string>
     <string name="bt_sm_2_2" msgid="2965243265852680543">"Gereken alan: <xliff:g id="SIZE">%1$s</xliff:g>"</string>
     <string name="ErrorTooManyRequests" msgid="8578277541472944529">"Çok fazla sayıda istek işleniyor. Daha sonra yeniden deneyin."</string>
     <string name="status_pending" msgid="2503691772030877944">"Dosya aktarımı henüz başlatılmadı."</string>
diff --git a/res/values-uk/strings.xml b/res/values-uk/strings.xml
index 04c5a37..8f3582e 100644
--- a/res/values-uk/strings.xml
+++ b/res/values-uk/strings.xml
@@ -18,8 +18,8 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="permlab_bluetoothShareManager" msgid="311492132450338925">"Доступ до менедж. завантаж."</string>
     <string name="permdesc_bluetoothShareManager" msgid="8930572979123190223">"Дозволяє програмі отримувати доступ до менеджера BluetoothShare і використовувати його для передавання файлів."</string>
-    <string name="permlab_bluetoothWhitelist" msgid="7091552898592306386">"Доступ до дозволеного пристрою Bluetooth."</string>
-    <string name="permdesc_bluetoothWhitelist" msgid="5494513855192170109">"Дозволяє програмі тимчасово додати пристрій Bluetooth у список дозволених пристроїв, що дасть йому змогу надсилати файли на цей пристрій без підтвердження від користувача."</string>
+    <string name="permlab_bluetoothAcceptlist" msgid="2647575807648622053">"Доступ до дозволеного пристрою з Bluetooth."</string>
+    <string name="permdesc_bluetoothAcceptlist" msgid="2406423665674766442">"Дозволяє додатку тимчасово внести пристрій із Bluetooth у список дозволених, що дасть йому змогу надсилати файли на цей пристрій без підтвердження від користувача."</string>
     <string name="bt_share_picker_label" msgid="6268100924487046932">"Bluetooth"</string>
     <string name="unknown_device" msgid="9221903979877041009">"Невідомий пристрій"</string>
     <string name="unknownNumber" msgid="4994750948072751566">"Невідомий"</string>
@@ -37,7 +37,7 @@
     <string name="incoming_file_confirm_timeout_ok" msgid="1414676773249857278">"OK"</string>
     <string name="incoming_file_confirm_timeout_content" msgid="172779756093975981">"Під час приймання вхідного файлу від \"<xliff:g id="SENDER">%1$s</xliff:g>\" виникла затримка"</string>
     <string name="incoming_file_confirm_Notification_title" msgid="5573329005298936903">"Вхідний файл"</string>
-    <string name="incoming_file_confirm_Notification_content" msgid="3359694069319644738">"Користувач <xliff:g id="SENDER">%1$s</xliff:g> готовий надіслати файл <xliff:g id="FILE">%2$s</xliff:g>"</string>
+    <string name="incoming_file_confirm_Notification_content" msgid="6237487486781004992">"<xliff:g id="SENDER">%1$s</xliff:g> може надіслати файл: <xliff:g id="FILE">%2$s</xliff:g>"</string>
     <string name="notification_receiving" msgid="4674648179652543984">"Через Bluetooth: отримання <xliff:g id="FILE">%1$s</xliff:g>"</string>
     <string name="notification_received" msgid="3324588019186687985">"Через Bluetooth: отримано <xliff:g id="FILE">%1$s</xliff:g>"</string>
     <string name="notification_received_fail" msgid="3619350997285714746">"Через Bluetooth: файл <xliff:g id="FILE">%1$s</xliff:g> не отримано"</string>
@@ -83,8 +83,8 @@
     <string name="bt_toast_4" msgid="4678812947604395649">"Надсил-ня файлу до \"<xliff:g id="RECIPIENT">%1$s</xliff:g>\""</string>
     <string name="bt_toast_5" msgid="2846870992823019494">"Надсил-ня файлів (<xliff:g id="NUMBER">%1$s</xliff:g>) до \"<xliff:g id="RECIPIENT">%2$s</xliff:g>\""</string>
     <string name="bt_toast_6" msgid="1855266596936622458">"Зупинено надсил. файлу до \"<xliff:g id="RECIPIENT">%1$s</xliff:g>\""</string>
-    <string name="bt_sm_2_1_nosdcard" msgid="1791835163301501637">"На носії USB замало місця, щоб зберегти файл, який надсилає <xliff:g id="SENDER">%1$s</xliff:g>"</string>
-    <string name="bt_sm_2_1_default" msgid="9115512207909504071">"На карті SD замало місця, щоб зберегти файл, який надсилає <xliff:g id="SENDER">%1$s</xliff:g>"</string>
+    <string name="bt_sm_2_1_nosdcard" msgid="5354343190503952837">"На носії USB недостатньо місця, щоб зберегти файл."</string>
+    <string name="bt_sm_2_1_default" msgid="2497541206648973852">"На карті SD недостатньо місця, щоб зберегти файл."</string>
     <string name="bt_sm_2_2" msgid="2965243265852680543">"Потрібно місця: <xliff:g id="SIZE">%1$s</xliff:g>"</string>
     <string name="ErrorTooManyRequests" msgid="8578277541472944529">"Обробляється забагато запитів. Спробуйте пізніше."</string>
     <string name="status_pending" msgid="2503691772030877944">"Передавання файлів ще не почалося."</string>
diff --git a/res/values-ur/strings.xml b/res/values-ur/strings.xml
index cb3b61b..c2e8534 100644
--- a/res/values-ur/strings.xml
+++ b/res/values-ur/strings.xml
@@ -18,8 +18,8 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="permlab_bluetoothShareManager" msgid="311492132450338925">"ڈاؤن لوڈ مینیجر تک رسائی حاصل کریں۔"</string>
     <string name="permdesc_bluetoothShareManager" msgid="8930572979123190223">"‏ایپ کو فائلیں منتقل کرنے کیلئے BluetoothShare مینیجر تک رسائی اور اسے استعمال کرنے کی اجازت دیتا ہے۔"</string>
-    <string name="permlab_bluetoothWhitelist" msgid="7091552898592306386">"بلوٹوتھ آلہ کی رسائی کو وہائٹ لسٹ کریں۔"</string>
-    <string name="permdesc_bluetoothWhitelist" msgid="5494513855192170109">"آلہ کو عارضی طور پر کسی بلوٹوتھ آلہ کو وہائٹ لسٹ کرنے کی اجازت دیتا ہے، اور اس آلہ کو صارف کی توثیق کے بغیر اس آلہ پر فائلیں بھیجنے دیتا ہے۔"</string>
+    <string name="permlab_bluetoothAcceptlist" msgid="2647575807648622053">"بلوٹوتھ آلہ تک رسائی کو فہرست قبول کریں۔"</string>
+    <string name="permdesc_bluetoothAcceptlist" msgid="2406423665674766442">"آلے کو عارضی طور پر ایک بلوٹوتھ آلہ کو فہرست قبول کرنے کی اجازت دیتا ہے اور اس آلہ کو صارف کی توثیق کے بغیر اس آلہ پر فائلز بھیجنے دیتا ہے۔"</string>
     <string name="bt_share_picker_label" msgid="6268100924487046932">"بلوٹوتھ"</string>
     <string name="unknown_device" msgid="9221903979877041009">"نامعلوم آلہ"</string>
     <string name="unknownNumber" msgid="4994750948072751566">"نامعلوم"</string>
@@ -37,7 +37,7 @@
     <string name="incoming_file_confirm_timeout_ok" msgid="1414676773249857278">"ٹھیک ہے"</string>
     <string name="incoming_file_confirm_timeout_content" msgid="172779756093975981">"\"<xliff:g id="SENDER">%1$s</xliff:g>\" کی جانب سے ایک موصول ہونے والی فائل کو قبول کرتے وقت ایک ٹائم آؤٹ پیش آگیا"</string>
     <string name="incoming_file_confirm_Notification_title" msgid="5573329005298936903">"آنے والی فائل"</string>
-    <string name="incoming_file_confirm_Notification_content" msgid="3359694069319644738">"<xliff:g id="SENDER">%1$s</xliff:g> <xliff:g id="FILE">%2$s</xliff:g> بھیجنے کے لئے تیار ہے"</string>
+    <string name="incoming_file_confirm_Notification_content" msgid="6237487486781004992">"<xliff:g id="SENDER">%1$s</xliff:g> فائل بھیجنے کیلئے تیار ہے: <xliff:g id="FILE">%2$s</xliff:g>"</string>
     <string name="notification_receiving" msgid="4674648179652543984">"بلوٹوتھ اشتراک: <xliff:g id="FILE">%1$s</xliff:g> موصول ہو رہی ہے"</string>
     <string name="notification_received" msgid="3324588019186687985">"بلوٹوتھ اشتراک: <xliff:g id="FILE">%1$s</xliff:g> موصول ہوئی"</string>
     <string name="notification_received_fail" msgid="3619350997285714746">"بلوٹوتھ اشتراک: فائل <xliff:g id="FILE">%1$s</xliff:g> موصول نہیں ہوئی"</string>
@@ -83,8 +83,8 @@
     <string name="bt_toast_4" msgid="4678812947604395649">"\"<xliff:g id="RECIPIENT">%1$s</xliff:g>\" کو فائل بھیجی جا رہی ہے"</string>
     <string name="bt_toast_5" msgid="2846870992823019494">"<xliff:g id="NUMBER">%1$s</xliff:g> فائلیں \"<xliff:g id="RECIPIENT">%2$s</xliff:g>\" کو بھیجی جا رہی ہیں"</string>
     <string name="bt_toast_6" msgid="1855266596936622458">"\"<xliff:g id="RECIPIENT">%1$s</xliff:g>\" کو فائل بھیجنا بند ہو گیا"</string>
-    <string name="bt_sm_2_1_nosdcard" msgid="1791835163301501637">"‏\"<xliff:g id="SENDER">%1$s</xliff:g>\" کی جانب سے موصول ہونے والی فائل کو محفوظ کرنے کے لیے USB اسٹوریج میں کافی جگہ نہیں ہے"</string>
-    <string name="bt_sm_2_1_default" msgid="9115512207909504071">"‏\"<xliff:g id="SENDER">%1$s</xliff:g>\" کی جانب سے موصول ہونے والی فائل کو محفوظ کرنے کے لیے SD کارڈ میں کافی جگہ نہیں ہے"</string>
+    <string name="bt_sm_2_1_nosdcard" msgid="5354343190503952837">"‏فائل محفوظ کرنے کیلئے USB اسٹوریج میں کافی جگہ نہیں ہے۔"</string>
+    <string name="bt_sm_2_1_default" msgid="2497541206648973852">"‏فائل محفوظ کرنے کیلئے SD کارڈ میں کافی جگہ نہیں ہے۔"</string>
     <string name="bt_sm_2_2" msgid="2965243265852680543">"جگہ درکار ہے: <xliff:g id="SIZE">%1$s</xliff:g>"</string>
     <string name="ErrorTooManyRequests" msgid="8578277541472944529">"کافی زیادہ درخواستوں پر کارروائی کی جا رہی ہے۔ بعد میں دوبارہ کوشش کریں۔"</string>
     <string name="status_pending" msgid="2503691772030877944">"فائل کی منتقلی ابھی شروع نہیں ہوئی۔"</string>
diff --git a/res/values-uz/strings.xml b/res/values-uz/strings.xml
index 5005bfb..eb2d310 100644
--- a/res/values-uz/strings.xml
+++ b/res/values-uz/strings.xml
@@ -18,8 +18,8 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="permlab_bluetoothShareManager" msgid="311492132450338925">"Yuklab olish menejeriga ruxsat."</string>
     <string name="permdesc_bluetoothShareManager" msgid="8930572979123190223">"Ilovaga BluetoothShare menejeriga kirishga va undan fayllar uzatishda foydalanishga ruxsat beradi."</string>
-    <string name="permlab_bluetoothWhitelist" msgid="7091552898592306386">"Ishonchli Bluetooth qurilmalari ro‘yxatiga kirish."</string>
-    <string name="permdesc_bluetoothWhitelist" msgid="5494513855192170109">"Ilovaga Bluetooth qurilmasini vaqtinchalik ishonchli qurilmalar ro‘yxatiga kiritishga va u qurilmaga foydalanuvchining tasdig‘isiz bu qurilmaga fayllar jo‘natish uchun ruxsat beradi."</string>
+    <string name="permlab_bluetoothAcceptlist" msgid="2647575807648622053">"Ishonchli Bluetooth qurilmalari roʻyxatiga kirish."</string>
+    <string name="permdesc_bluetoothAcceptlist" msgid="2406423665674766442">"Ilovaga Bluetooth qurilmasini vaqtinchalik ishonchli qurilmalar roʻyxatiga kiritishga va u qurilmaga foydalanuvchining tasdigʻisiz bu qurilmaga fayllar yuborish uchun ruxsat beradi."</string>
     <string name="bt_share_picker_label" msgid="6268100924487046932">"Bluetooth"</string>
     <string name="unknown_device" msgid="9221903979877041009">"Noma’lum qurilma"</string>
     <string name="unknownNumber" msgid="4994750948072751566">"Noma’lum"</string>
@@ -37,7 +37,7 @@
     <string name="incoming_file_confirm_timeout_ok" msgid="1414676773249857278">"OK"</string>
     <string name="incoming_file_confirm_timeout_content" msgid="172779756093975981">"\"<xliff:g id="SENDER">%1$s</xliff:g>\"dan kiruvchi xabarni olishga rozilik bildirilayotganda, kutish vaqti o‘tib ketdi."</string>
     <string name="incoming_file_confirm_Notification_title" msgid="5573329005298936903">"Kiruvchi fayl"</string>
-    <string name="incoming_file_confirm_Notification_content" msgid="3359694069319644738">"“<xliff:g id="SENDER">%1$s</xliff:g>” qurilmasi “<xliff:g id="FILE">%2$s</xliff:g>” faylini yuborishga tayyor"</string>
+    <string name="incoming_file_confirm_Notification_content" msgid="6237487486781004992">"<xliff:g id="SENDER">%1$s</xliff:g> fayl yuborishga tayyor: <xliff:g id="FILE">%2$s</xliff:g>"</string>
     <string name="notification_receiving" msgid="4674648179652543984">"Bluetooth orqali yuborildi: <xliff:g id="FILE">%1$s</xliff:g>"</string>
     <string name="notification_received" msgid="3324588019186687985">"Bluetooth orqali olindi: <xliff:g id="FILE">%1$s</xliff:g>"</string>
     <string name="notification_received_fail" msgid="3619350997285714746">"Fayl qabul qilinmadi: <xliff:g id="FILE">%1$s</xliff:g>"</string>
@@ -83,8 +83,8 @@
     <string name="bt_toast_4" msgid="4678812947604395649">"\"<xliff:g id="RECIPIENT">%1$s</xliff:g>\"ga fayl jo‘natilmoqda"</string>
     <string name="bt_toast_5" msgid="2846870992823019494">"<xliff:g id="NUMBER">%1$s</xliff:g> ta fayl \"<xliff:g id="RECIPIENT">%2$s</xliff:g>\"ga jo‘natimoqda"</string>
     <string name="bt_toast_6" msgid="1855266596936622458">"\"<xliff:g id="RECIPIENT">%1$s</xliff:g>\"ga fayl jo‘natish to‘xtatildi"</string>
-    <string name="bt_sm_2_1_nosdcard" msgid="1791835163301501637">"“<xliff:g id="SENDER">%1$s</xliff:g>” yuborgan faylni saqlash uchun USB xotirada joy yetarli emas"</string>
-    <string name="bt_sm_2_1_default" msgid="9115512207909504071">"“<xliff:g id="SENDER">%1$s</xliff:g>” yuborgan faylni saqlash uchun SD kartada joy yetarli emas"</string>
+    <string name="bt_sm_2_1_nosdcard" msgid="5354343190503952837">"Faylni saqlash uchun USB xotirada joy yetarli emas."</string>
+    <string name="bt_sm_2_1_default" msgid="2497541206648973852">"Faylni saqlash uchun SD kartada joy yetarli emas."</string>
     <string name="bt_sm_2_2" msgid="2965243265852680543">"Kerakli bo‘sh joy: <xliff:g id="SIZE">%1$s</xliff:g>"</string>
     <string name="ErrorTooManyRequests" msgid="8578277541472944529">"Juda ko‘p so‘rovlarga ishlov berilmoqda. Keyinroq urinib ko‘ring."</string>
     <string name="status_pending" msgid="2503691772030877944">"Fayl o‘tkazmasi hali boshlanmadi."</string>
diff --git a/res/values-vi/strings.xml b/res/values-vi/strings.xml
index 2309af8..a35cb61 100644
--- a/res/values-vi/strings.xml
+++ b/res/values-vi/strings.xml
@@ -18,8 +18,8 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="permlab_bluetoothShareManager" msgid="311492132450338925">"Truy cập trình quản lý tải xuống."</string>
     <string name="permdesc_bluetoothShareManager" msgid="8930572979123190223">"Cho phép ứng dụng truy cập trình quản lý BluetoothShare và sử dụng trình quản lý đó để chuyển tệp."</string>
-    <string name="permlab_bluetoothWhitelist" msgid="7091552898592306386">"Truy cập thiết bị bluetooth nằm trong danh sách trắng."</string>
-    <string name="permdesc_bluetoothWhitelist" msgid="5494513855192170109">"Cho phép ứng dụng tạm thời đưa thiết bị Bluetooth vào danh sách trắng, cho phép thiết bị đó gửi các tệp tới thiết bị này mà không cần xác nhận của người dùng."</string>
+    <string name="permlab_bluetoothAcceptlist" msgid="2647575807648622053">"Quyền sử dụng thiết bị Bluetooth trong danh sách chấp nhận."</string>
+    <string name="permdesc_bluetoothAcceptlist" msgid="2406423665674766442">"Cho phép ứng dụng tạm thời đưa một thiết bị Bluetooth vào danh sách chấp nhận và để thiết bị đó gửi tệp tới thiết bị này mà không cần người dùng xác nhận."</string>
     <string name="bt_share_picker_label" msgid="6268100924487046932">"Bluetooth"</string>
     <string name="unknown_device" msgid="9221903979877041009">"Thiết bị không xác định"</string>
     <string name="unknownNumber" msgid="4994750948072751566">"Không xác định"</string>
@@ -37,7 +37,7 @@
     <string name="incoming_file_confirm_timeout_ok" msgid="1414676773249857278">"OK"</string>
     <string name="incoming_file_confirm_timeout_content" msgid="172779756093975981">"Có thời gian chờ trong khi chấp nhận tệp tới từ \"<xliff:g id="SENDER">%1$s</xliff:g>\""</string>
     <string name="incoming_file_confirm_Notification_title" msgid="5573329005298936903">"Tệp đến"</string>
-    <string name="incoming_file_confirm_Notification_content" msgid="3359694069319644738">"<xliff:g id="SENDER">%1$s</xliff:g> đã sẵn sàng gửi <xliff:g id="FILE">%2$s</xliff:g>"</string>
+    <string name="incoming_file_confirm_Notification_content" msgid="6237487486781004992">"<xliff:g id="SENDER">%1$s</xliff:g> đã sẵn sàng gửi một tệp: <xliff:g id="FILE">%2$s</xliff:g>"</string>
     <string name="notification_receiving" msgid="4674648179652543984">"Chia sẻ qua Bluetooth: Đang nhận <xliff:g id="FILE">%1$s</xliff:g>"</string>
     <string name="notification_received" msgid="3324588019186687985">"Chia sẻ qua Bluetooth: Đã nhận <xliff:g id="FILE">%1$s</xliff:g>"</string>
     <string name="notification_received_fail" msgid="3619350997285714746">"Chia sẻ qua Bluetooth: Chưa nhận được tệp <xliff:g id="FILE">%1$s</xliff:g>"</string>
@@ -83,8 +83,8 @@
     <string name="bt_toast_4" msgid="4678812947604395649">"Đang gửi tệp tới \"<xliff:g id="RECIPIENT">%1$s</xliff:g>\""</string>
     <string name="bt_toast_5" msgid="2846870992823019494">"Đang gửi <xliff:g id="NUMBER">%1$s</xliff:g> tệp tới \"<xliff:g id="RECIPIENT">%2$s</xliff:g>\""</string>
     <string name="bt_toast_6" msgid="1855266596936622458">"Đã dừng gửi tệp tới \"<xliff:g id="RECIPIENT">%1$s</xliff:g>\""</string>
-    <string name="bt_sm_2_1_nosdcard" msgid="1791835163301501637">"Không đủ dung lượng trong bộ nhớ USB để lưu tệp từ \"<xliff:g id="SENDER">%1$s</xliff:g>\""</string>
-    <string name="bt_sm_2_1_default" msgid="9115512207909504071">"Không đủ dung lượng trên thẻ SD để lưu tệp từ \"<xliff:g id="SENDER">%1$s</xliff:g>\""</string>
+    <string name="bt_sm_2_1_nosdcard" msgid="5354343190503952837">"Không đủ dung lượng trong bộ lưu trữ USB để lưu tệp."</string>
+    <string name="bt_sm_2_1_default" msgid="2497541206648973852">"Không đủ dung lượng trên thẻ SD để lưu tệp."</string>
     <string name="bt_sm_2_2" msgid="2965243265852680543">"Dung lượng cần: <xliff:g id="SIZE">%1$s</xliff:g>"</string>
     <string name="ErrorTooManyRequests" msgid="8578277541472944529">"Quá nhiều yêu cầu đang được xử lý. Hãy thử lại sau."</string>
     <string name="status_pending" msgid="2503691772030877944">"Chuyển tệp chưa bắt đầu."</string>
diff --git a/res/values-zh-rCN/strings.xml b/res/values-zh-rCN/strings.xml
index ebf3db3..c6bc3a4 100644
--- a/res/values-zh-rCN/strings.xml
+++ b/res/values-zh-rCN/strings.xml
@@ -18,8 +18,8 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="permlab_bluetoothShareManager" msgid="311492132450338925">"使用下载管理器。"</string>
     <string name="permdesc_bluetoothShareManager" msgid="8930572979123190223">"允许应用访问蓝牙共享 (BluetoothShare) 管理器并将其用于传输文件。"</string>
-    <string name="permlab_bluetoothWhitelist" msgid="7091552898592306386">"将蓝牙设备列入访问权限白名单。"</string>
-    <string name="permdesc_bluetoothWhitelist" msgid="5494513855192170109">"允许该应用暂时将某个蓝牙设备列入白名单,从而允许该设备在不经过用户确认的情况下,将文件发送到本设备。"</string>
+    <string name="permlab_bluetoothAcceptlist" msgid="2647575807648622053">"将蓝牙设备加入许可名单。"</string>
+    <string name="permdesc_bluetoothAcceptlist" msgid="2406423665674766442">"允许该应用暂时将某个蓝牙设备加入许可名单,从而允许该设备在不经过用户确认的情况下,将文件发送到本设备。"</string>
     <string name="bt_share_picker_label" msgid="6268100924487046932">"蓝牙"</string>
     <string name="unknown_device" msgid="9221903979877041009">"未知设备"</string>
     <string name="unknownNumber" msgid="4994750948072751566">"未知号码"</string>
@@ -37,7 +37,7 @@
     <string name="incoming_file_confirm_timeout_ok" msgid="1414676773249857278">"确定"</string>
     <string name="incoming_file_confirm_timeout_content" msgid="172779756093975981">"接受来自“<xliff:g id="SENDER">%1$s</xliff:g>”的文件时发生超时"</string>
     <string name="incoming_file_confirm_Notification_title" msgid="5573329005298936903">"有人发送文件给您"</string>
-    <string name="incoming_file_confirm_Notification_content" msgid="3359694069319644738">"<xliff:g id="SENDER">%1$s</xliff:g>已准备好发送<xliff:g id="FILE">%2$s</xliff:g>"</string>
+    <string name="incoming_file_confirm_Notification_content" msgid="6237487486781004992">"“<xliff:g id="SENDER">%1$s</xliff:g>”已做好发送以下文件的准备:<xliff:g id="FILE">%2$s</xliff:g>"</string>
     <string name="notification_receiving" msgid="4674648179652543984">"蓝牙共享:正在接收“<xliff:g id="FILE">%1$s</xliff:g>”"</string>
     <string name="notification_received" msgid="3324588019186687985">"蓝牙共享:已接收“<xliff:g id="FILE">%1$s</xliff:g>”"</string>
     <string name="notification_received_fail" msgid="3619350997285714746">"蓝牙共享:未收到文件“<xliff:g id="FILE">%1$s</xliff:g>”"</string>
@@ -83,8 +83,8 @@
     <string name="bt_toast_4" msgid="4678812947604395649">"正在向“<xliff:g id="RECIPIENT">%1$s</xliff:g>”发送文件"</string>
     <string name="bt_toast_5" msgid="2846870992823019494">"正在向“<xliff:g id="RECIPIENT">%2$s</xliff:g>”发送<xliff:g id="NUMBER">%1$s</xliff:g>个文件"</string>
     <string name="bt_toast_6" msgid="1855266596936622458">"已停止向“<xliff:g id="RECIPIENT">%1$s</xliff:g>”发送文件"</string>
-    <string name="bt_sm_2_1_nosdcard" msgid="1791835163301501637">"USB 存储设备空间不足,无法保存来自“<xliff:g id="SENDER">%1$s</xliff:g>”的文件"</string>
-    <string name="bt_sm_2_1_default" msgid="9115512207909504071">"SD 卡存储空间不足,无法保存来自“<xliff:g id="SENDER">%1$s</xliff:g>”的文件"</string>
+    <string name="bt_sm_2_1_nosdcard" msgid="5354343190503952837">"USB 存储设备中的空间不足,无法保存该文件。"</string>
+    <string name="bt_sm_2_1_default" msgid="2497541206648973852">"SD 卡上的空间不足,无法保存该文件。"</string>
     <string name="bt_sm_2_2" msgid="2965243265852680543">"所需空间:<xliff:g id="SIZE">%1$s</xliff:g>"</string>
     <string name="ErrorTooManyRequests" msgid="8578277541472944529">"正在处理的请求太多。请稍后重试。"</string>
     <string name="status_pending" msgid="2503691772030877944">"尚未开始传输文件。"</string>
diff --git a/res/values-zh-rHK/strings.xml b/res/values-zh-rHK/strings.xml
index 8a234e7..fe42d8b 100644
--- a/res/values-zh-rHK/strings.xml
+++ b/res/values-zh-rHK/strings.xml
@@ -18,8 +18,8 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="permlab_bluetoothShareManager" msgid="311492132450338925">"存取下載管理員。"</string>
     <string name="permdesc_bluetoothShareManager" msgid="8930572979123190223">"允許應用程式存取 BluetoothShare 管理員並使用 BluetoothShare 管理員傳輸檔案。"</string>
-    <string name="permlab_bluetoothWhitelist" msgid="7091552898592306386">"將藍牙裝置列入許可名單。"</string>
-    <string name="permdesc_bluetoothWhitelist" msgid="5494513855192170109">"允許應用程式暫時將藍牙裝置加入許可名單,使其不需經使用者確認便可把檔案傳送到裝置上。"</string>
+    <string name="permlab_bluetoothAcceptlist" msgid="2647575807648622053">"將藍牙裝置加入允許名單。"</string>
+    <string name="permdesc_bluetoothAcceptlist" msgid="2406423665674766442">"允許應用程式暫時將藍牙裝置加入允許名單,使其不需經用戶確認便可把檔案傳送到裝置上。"</string>
     <string name="bt_share_picker_label" msgid="6268100924487046932">"藍牙"</string>
     <string name="unknown_device" msgid="9221903979877041009">"不明裝置"</string>
     <string name="unknownNumber" msgid="4994750948072751566">"未知"</string>
@@ -37,7 +37,7 @@
     <string name="incoming_file_confirm_timeout_ok" msgid="1414676773249857278">"確定"</string>
     <string name="incoming_file_confirm_timeout_content" msgid="172779756093975981">"接收來自「<xliff:g id="SENDER">%1$s</xliff:g>」的檔案時發生作業逾時"</string>
     <string name="incoming_file_confirm_Notification_title" msgid="5573329005298936903">"收到的檔案"</string>
-    <string name="incoming_file_confirm_Notification_content" msgid="3359694069319644738">"<xliff:g id="SENDER">%1$s</xliff:g>準備傳送<xliff:g id="FILE">%2$s</xliff:g>"</string>
+    <string name="incoming_file_confirm_Notification_content" msgid="6237487486781004992">"<xliff:g id="SENDER">%1$s</xliff:g> 準備傳送檔案:<xliff:g id="FILE">%2$s</xliff:g>"</string>
     <string name="notification_receiving" msgid="4674648179652543984">"藍牙分享:正在接收 <xliff:g id="FILE">%1$s</xliff:g>"</string>
     <string name="notification_received" msgid="3324588019186687985">"藍牙分享:已接收 <xliff:g id="FILE">%1$s</xliff:g>"</string>
     <string name="notification_received_fail" msgid="3619350997285714746">"藍牙分享:未收到檔案 <xliff:g id="FILE">%1$s</xliff:g>"</string>
@@ -83,8 +83,8 @@
     <string name="bt_toast_4" msgid="4678812947604395649">"正在將檔案傳送給「<xliff:g id="RECIPIENT">%1$s</xliff:g>」"</string>
     <string name="bt_toast_5" msgid="2846870992823019494">"正在將 <xliff:g id="NUMBER">%1$s</xliff:g> 個檔案傳送給「<xliff:g id="RECIPIENT">%2$s</xliff:g>」"</string>
     <string name="bt_toast_6" msgid="1855266596936622458">"已停止將檔案傳送給「<xliff:g id="RECIPIENT">%1$s</xliff:g>」"</string>
-    <string name="bt_sm_2_1_nosdcard" msgid="1791835163301501637">"USB 儲存空間上的儲存空間不足,無法儲存「<xliff:g id="SENDER">%1$s</xliff:g>」傳來的檔案"</string>
-    <string name="bt_sm_2_1_default" msgid="9115512207909504071">"SD 卡上的儲存空間不足,無法儲存「<xliff:g id="SENDER">%1$s</xliff:g>」傳來的檔案"</string>
+    <string name="bt_sm_2_1_nosdcard" msgid="5354343190503952837">"USB 儲存空間不足,無法儲存檔案。"</string>
+    <string name="bt_sm_2_1_default" msgid="2497541206648973852">"SD 卡空間不足,無法儲存檔案。"</string>
     <string name="bt_sm_2_2" msgid="2965243265852680543">"所需儲存空間:<xliff:g id="SIZE">%1$s</xliff:g>"</string>
     <string name="ErrorTooManyRequests" msgid="8578277541472944529">"同時處理過多要求,請稍後再試。"</string>
     <string name="status_pending" msgid="2503691772030877944">"尚未開始傳輸檔案。"</string>
diff --git a/res/values-zh-rTW/strings.xml b/res/values-zh-rTW/strings.xml
index ab9e5b6..25516c1 100644
--- a/res/values-zh-rTW/strings.xml
+++ b/res/values-zh-rTW/strings.xml
@@ -18,8 +18,8 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="permlab_bluetoothShareManager" msgid="311492132450338925">"存取下載管理員。"</string>
     <string name="permdesc_bluetoothShareManager" msgid="8930572979123190223">"允許應用程式存取 BluetoothShare 管理員,並可使用 BluetoothShare 管理員傳輸檔案。"</string>
-    <string name="permlab_bluetoothWhitelist" msgid="7091552898592306386">"將藍牙裝置列入許可清單。"</string>
-    <string name="permdesc_bluetoothWhitelist" msgid="5494513855192170109">"允許應用程式暫時將藍牙裝置加入許可名單,使其不需經過使用者確認即可將檔案傳送到裝置。"</string>
+    <string name="permlab_bluetoothAcceptlist" msgid="2647575807648622053">"將藍牙裝置存取權限加入許可清單。"</string>
+    <string name="permdesc_bluetoothAcceptlist" msgid="2406423665674766442">"允許應用程式暫時將藍牙裝置加入許可清單,讓該裝置可以直接傳送檔案到這部裝置,不用經過使用者確認。"</string>
     <string name="bt_share_picker_label" msgid="6268100924487046932">"藍牙"</string>
     <string name="unknown_device" msgid="9221903979877041009">"未知的裝置"</string>
     <string name="unknownNumber" msgid="4994750948072751566">"不明"</string>
@@ -37,7 +37,7 @@
     <string name="incoming_file_confirm_timeout_ok" msgid="1414676773249857278">"確定"</string>
     <string name="incoming_file_confirm_timeout_content" msgid="172779756093975981">"接收來自「<xliff:g id="SENDER">%1$s</xliff:g>」的檔案時發生作業逾時"</string>
     <string name="incoming_file_confirm_Notification_title" msgid="5573329005298936903">"有人傳送檔案給你"</string>
-    <string name="incoming_file_confirm_Notification_content" msgid="3359694069319644738">"<xliff:g id="SENDER">%1$s</xliff:g>已準備好傳送<xliff:g id="FILE">%2$s</xliff:g>"</string>
+    <string name="incoming_file_confirm_Notification_content" msgid="6237487486781004992">"「<xliff:g id="SENDER">%1$s</xliff:g>」準備要傳送檔案:<xliff:g id="FILE">%2$s</xliff:g>"</string>
     <string name="notification_receiving" msgid="4674648179652543984">"藍牙分享:正在接收 <xliff:g id="FILE">%1$s</xliff:g>"</string>
     <string name="notification_received" msgid="3324588019186687985">"藍牙分享:已接收 <xliff:g id="FILE">%1$s</xliff:g>"</string>
     <string name="notification_received_fail" msgid="3619350997285714746">"藍牙分享:未收到檔案 <xliff:g id="FILE">%1$s</xliff:g>"</string>
@@ -83,8 +83,8 @@
     <string name="bt_toast_4" msgid="4678812947604395649">"正在將檔案傳送給「<xliff:g id="RECIPIENT">%1$s</xliff:g>」"</string>
     <string name="bt_toast_5" msgid="2846870992823019494">"正在將 <xliff:g id="NUMBER">%1$s</xliff:g> 個檔案傳送給「<xliff:g id="RECIPIENT">%2$s</xliff:g>」"</string>
     <string name="bt_toast_6" msgid="1855266596936622458">"已停止將檔案傳送給「<xliff:g id="RECIPIENT">%1$s</xliff:g>」"</string>
-    <string name="bt_sm_2_1_nosdcard" msgid="1791835163301501637">"USB 儲存空間上沒有足夠的空間可以儲存「<xliff:g id="SENDER">%1$s</xliff:g>」傳來的檔案"</string>
-    <string name="bt_sm_2_1_default" msgid="9115512207909504071">"SD 卡上沒有足夠的空間可以儲存「<xliff:g id="SENDER">%1$s</xliff:g>」傳來的檔案"</string>
+    <string name="bt_sm_2_1_nosdcard" msgid="5354343190503952837">"USB 儲存空間不足,無法儲存這個檔案。"</string>
+    <string name="bt_sm_2_1_default" msgid="2497541206648973852">"SD 卡的空間不足,無法儲存這個檔案。"</string>
     <string name="bt_sm_2_2" msgid="2965243265852680543">"所需儲存空間:<xliff:g id="SIZE">%1$s</xliff:g>"</string>
     <string name="ErrorTooManyRequests" msgid="8578277541472944529">"系統正在處理多個要求,請稍後再試。"</string>
     <string name="status_pending" msgid="2503691772030877944">"尚未開始傳輸檔案。"</string>
diff --git a/res/values-zu/strings.xml b/res/values-zu/strings.xml
index de59ac7..3d641d2 100644
--- a/res/values-zu/strings.xml
+++ b/res/values-zu/strings.xml
@@ -18,8 +18,8 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="permlab_bluetoothShareManager" msgid="311492132450338925">"Finyelela kumphathi wokulayisha."</string>
     <string name="permdesc_bluetoothShareManager" msgid="8930572979123190223">"Ivumela uhlelo lokusebenza ukufinyelela umphathi we-BluetoothShare ngisho nokuyisebenzisela ukudlulisa amafayela."</string>
-    <string name="permlab_bluetoothWhitelist" msgid="7091552898592306386">"Ukufinyelela idivaysi ye-bluetooth yohlu lwabathintwayo"</string>
-    <string name="permdesc_bluetoothWhitelist" msgid="5494513855192170109">"Ivumela uhlelo lokusebenza ukwenza uhlu lwabathintayo kwidivaysi ye-Bluetooth, ngokuvumela leyo divaysi ukuthumela amafayela kule divaysi ngaphandle kokuqinisekisa komsebenzisi."</string>
+    <string name="permlab_bluetoothAcceptlist" msgid="2647575807648622053">"Uhlu lokwamukela lokufinyelela kudivayisi ye-Bluetooth."</string>
+    <string name="permdesc_bluetoothAcceptlist" msgid="2406423665674766442">"Ivumela uhlelo lokusebenza ukuba lamukele okwesikhashana uhlu lwedivayisi ye-Bluetooth, ngokuvumela leyo divayisi ukuthumela amafayela kule divayisi ngaphandle kokuqinisekisa komsebenzisi."</string>
     <string name="bt_share_picker_label" msgid="6268100924487046932">"Bluetooth"</string>
     <string name="unknown_device" msgid="9221903979877041009">"Idivayisi engaziwa"</string>
     <string name="unknownNumber" msgid="4994750948072751566">"Akwaziwa"</string>
@@ -37,7 +37,7 @@
     <string name="incoming_file_confirm_timeout_ok" msgid="1414676773249857278">"Kulungile"</string>
     <string name="incoming_file_confirm_timeout_content" msgid="172779756093975981">"Isikhathi siphelile ngenkathi yamukela ifayela engenayo esuka ku- \"<xliff:g id="SENDER">%1$s</xliff:g>\""</string>
     <string name="incoming_file_confirm_Notification_title" msgid="5573329005298936903">"Ifayela elingenayo"</string>
-    <string name="incoming_file_confirm_Notification_content" msgid="3359694069319644738">"<xliff:g id="SENDER">%1$s</xliff:g> ulungele ukuthumela i-<xliff:g id="FILE">%2$s</xliff:g>"</string>
+    <string name="incoming_file_confirm_Notification_content" msgid="6237487486781004992">"U-<xliff:g id="SENDER">%1$s</xliff:g> uselungele ukuthumela ifayela: <xliff:g id="FILE">%2$s</xliff:g>"</string>
     <string name="notification_receiving" msgid="4674648179652543984">"Abelana ne-Bluetooth: Ithola<xliff:g id="FILE">%1$s</xliff:g>"</string>
     <string name="notification_received" msgid="3324588019186687985">"Abelana ne-Bluetooth: Itholakele <xliff:g id="FILE">%1$s</xliff:g>"</string>
     <string name="notification_received_fail" msgid="3619350997285714746">"Abelana ne-Bluetooth: Ifayela <xliff:g id="FILE">%1$s</xliff:g> ayitholakalanga"</string>
@@ -83,8 +83,8 @@
     <string name="bt_toast_4" msgid="4678812947604395649">"Ithumela ifayela ku- \"<xliff:g id="RECIPIENT">%1$s</xliff:g>\""</string>
     <string name="bt_toast_5" msgid="2846870992823019494">"Ithumela <xliff:g id="NUMBER">%1$s</xliff:g> amafayela ku- \"<xliff:g id="RECIPIENT">%2$s</xliff:g>\""</string>
     <string name="bt_toast_6" msgid="1855266596936622458">"Imise ukuthumela ifayela ku- \"<xliff:g id="RECIPIENT">%1$s</xliff:g>\""</string>
-    <string name="bt_sm_2_1_nosdcard" msgid="1791835163301501637">"Asikho isikhala esanele kwindawo yokugcina ye-USB ukulondoloza ifayela kusuka ku- \"<xliff:g id="SENDER">%1$s</xliff:g>\""</string>
-    <string name="bt_sm_2_1_default" msgid="9115512207909504071">"Asikho isikhala esanele ekhadini le-SD ukulongcina ifayela esuka ku- \"<xliff:g id="SENDER">%1$s</xliff:g>\""</string>
+    <string name="bt_sm_2_1_nosdcard" msgid="5354343190503952837">"Asikho isikhala esanele kwisitoreji se-USB sokulondoloza ifayela."</string>
+    <string name="bt_sm_2_1_default" msgid="2497541206648973852">"Asikho isikhala esanele ekhadini le-SD sokulondoloza ifayela."</string>
     <string name="bt_sm_2_2" msgid="2965243265852680543">"Isikhala esidingekayo: <xliff:g id="SIZE">%1$s</xliff:g>"</string>
     <string name="ErrorTooManyRequests" msgid="8578277541472944529">"Kunezicelo eziningi ezenziwayo. Zama futhi emva kwesikhathi."</string>
     <string name="status_pending" msgid="2503691772030877944">"Ukudlulisa ifayela akuqalisiwe okwamanje"</string>
diff --git a/res/values/config.xml b/res/values/config.xml
index 6ceacf7..0fc3fdc 100644
--- a/res/values/config.xml
+++ b/res/values/config.xml
@@ -17,6 +17,7 @@
     <bool name="profile_supported_a2dp_sink">false</bool>
     <bool name="profile_supported_hs_hfp">true</bool>
     <bool name="profile_supported_hfpclient">false</bool>
+    <bool name="profile_supported_hfp_incallservice">true</bool>
     <bool name="profile_supported_hid_host">true</bool>
     <bool name="profile_supported_opp">true</bool>
     <bool name="profile_supported_pan">true</bool>
@@ -51,7 +52,7 @@
 
     <!-- Specifies latency parameters for high priority, balanced and low power
          GATT configurations. These values represents the number of packets a
-         slave device is allowed to skip. -->
+         peripheral device is allowed to skip. -->
     <integer name="gatt_high_priority_latency">0</integer>
     <integer name="gatt_balanced_priority_latency">0</integer>
     <integer name="gatt_low_power_latency">2</integer>
@@ -73,7 +74,7 @@
     <bool name="a2dp_sink_automatically_request_audio_focus">false</bool>
 
     <!-- For enabling the AVRCP Controller Cover Artwork feature -->
-    <bool name="avrcp_controller_enable_cover_art">false</bool>
+    <bool name="avrcp_controller_enable_cover_art">true</bool>
 
     <!-- For enabling browsed cover art with the AVRCP Controller Cover Artwork feature -->
     <bool name="avrcp_controller_cover_art_browsed_images">false</bool>
@@ -101,6 +102,12 @@
     <integer name="a2dp_source_codec_priority_aptx_hd">4001</integer>
     <integer name="a2dp_source_codec_priority_ldac">5001</integer>
 
+    <!-- For enabling the AVRCP Target Cover Artowrk feature-->
+    <bool name="avrcp_target_enable_cover_art">true</bool>
+
+    <!-- Enable support for URI based images. Off by default due to increased memory usage -->
+    <bool name="avrcp_target_cover_art_uri_images">false</bool>
+
     <!-- Package that is responsible for user interaction on pairing request,
          success or cancel.
          Receives:
@@ -117,4 +124,15 @@
     <!-- Package that is providing the exposure notification service -->
     <string name="exposure_notification_package">com.google.android.gms</string>
 
+    <!-- Enabling Gabeldorsche up to the scanning layer, including
+            - BLE scanning
+            - BLE advertising
+            - ACL connection management
+            - Controller information management
+            - HCI layer
+            - HAL interface layer
+            - Other required GD components like config storage
+     -->
+    <bool name="enable_gd_up_to_scanning_layer">false</bool>
+
 </resources>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index f59e783..8e65894 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -32,9 +32,9 @@
     <string name="permdesc_bluetoothShareManager">Allows the app to access the
         BluetoothShare manager and use it to transfer files. </string>
 
-    <string name="permlab_bluetoothWhitelist">Whitelist bluetooth device access.</string>
+    <string name="permlab_bluetoothAcceptlist">Acceptlist bluetooth device access.</string>
 
-    <string name="permdesc_bluetoothWhitelist">Allows the app to temporarily whitelist
+    <string name="permdesc_bluetoothAcceptlist">Allows the app to temporarily acceptlist
         a Bluetooth device, allowing that device to send files to this device without user
         confirmation.</string>
 
@@ -80,7 +80,7 @@
 
     <!-- Bluetooth File Transfer Acceptance Notification item -->
     <string name="incoming_file_confirm_Notification_title">Incoming file</string>
-    <string name="incoming_file_confirm_Notification_content"><xliff:g id="sender">%1$s</xliff:g> is ready to send <xliff:g id="file">%2$s</xliff:g></string>
+    <string name="incoming_file_confirm_Notification_content"><xliff:g id="sender">%1$s</xliff:g> is ready to send a file: <xliff:g id="file">%2$s</xliff:g></string>
 
     <!-- Inbound File Transfer Progress Notification item -->
     <!-- label for the notification item of receiving file -->
@@ -176,9 +176,9 @@
     <string name="bt_toast_6">Stopped sending file to \u0022<xliff:g id="recipient">%1$s</xliff:g>\u0022</string>
 
     <!-- Bluetooth System Messages [CHAR LIMIT=NONE] -->
-    <string name="bt_sm_2_1_nosdcard">There isn\'t enough space in USB storage to save the file from \u0022<xliff:g id="sender">%1$s</xliff:g>\u0022</string>
+    <string name="bt_sm_2_1_nosdcard">There isn\'t enough space in USB storage to save the file.</string>
     <!-- Bluetooth System Messages -->
-    <string name="bt_sm_2_1_default">There isn\'t enough space on the SD card to save the file from \u0022<xliff:g id="sender">%1$s</xliff:g>\u0022</string>
+    <string name="bt_sm_2_1_default">There isn\'t enough space on the SD card to save the file.</string>
     <string name="bt_sm_2_2">Space needed: <xliff:g id="size">%1$s</xliff:g></string>
 
     <string name="ErrorTooManyRequests">Too many requests are being processed. Try again later.</string>
diff --git a/res/values/styles.xml b/res/values/styles.xml
index 8e85faf..91f402d 100644
--- a/res/values/styles.xml
+++ b/res/values/styles.xml
@@ -45,4 +45,6 @@
         <item name="android:textColor">@*android:color/primary_text_default_material_light</item>
     </style>
 
+    <style name="dialog" parent="android:style/Theme.Material.Light.Dialog.Alert" />
+
 </resources>
diff --git a/src/com/android/bluetooth/AlertActivity.java b/src/com/android/bluetooth/AlertActivity.java
index e45c770..6445f0b 100644
--- a/src/com/android/bluetooth/AlertActivity.java
+++ b/src/com/android/bluetooth/AlertActivity.java
@@ -18,6 +18,7 @@
 
 import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
 
+import android.annotation.SuppressLint;
 import android.app.Activity;
 import android.app.AlertDialog;
 import android.app.Dialog;
@@ -33,6 +34,7 @@
  * @see #mAlert
  * @see #setupAlert()
  */
+@SuppressLint("AndroidFrameworkBluetoothPermission")
 public abstract class AlertActivity extends Activity implements DialogInterface.OnDismissListener,
         DialogInterface.OnCancelListener {
 
@@ -116,4 +118,13 @@
         if (mAlert == null) return;
         mAlert.getButton(identifier).setEnabled(enable);
     }
+
+    @Override
+    protected void onDestroy() {
+        if (mAlert != null) {
+            mAlert.dismiss();
+        }
+        super.onDestroy();
+    }
+
 }
diff --git a/src/com/android/bluetooth/BluetoothObexTransport.java b/src/com/android/bluetooth/BluetoothObexTransport.java
index e5683e9..b1b5709 100644
--- a/src/com/android/bluetooth/BluetoothObexTransport.java
+++ b/src/com/android/bluetooth/BluetoothObexTransport.java
@@ -32,10 +32,24 @@
 public class BluetoothObexTransport implements ObexTransport {
     private BluetoothSocket mSocket = null;
 
+    /**
+     * Will default at the maximum packet length.
+     */
+    public static final int PACKET_SIZE_UNSPECIFIED = -1;
+
+    private int mMaxTransmitPacketSize = PACKET_SIZE_UNSPECIFIED;
+    private int mMaxReceivePacketSize = PACKET_SIZE_UNSPECIFIED;
+
     public BluetoothObexTransport(BluetoothSocket socket) {
         this.mSocket = socket;
     }
 
+    public BluetoothObexTransport(BluetoothSocket socket, int transmitSize, int receiveSize) {
+        this.mSocket = socket;
+        this.mMaxTransmitPacketSize = transmitSize;
+        this.mMaxReceivePacketSize = receiveSize;
+    }
+
     @Override
     public void close() throws IOException {
         mSocket.close();
@@ -84,7 +98,7 @@
     @Override
     public int getMaxTransmitPacketSize() {
         if (mSocket.getConnectionType() != BluetoothSocket.TYPE_L2CAP) {
-            return -1;
+            return mMaxTransmitPacketSize;
         }
         return mSocket.getMaxTransmitPacketSize();
     }
@@ -92,7 +106,7 @@
     @Override
     public int getMaxReceivePacketSize() {
         if (mSocket.getConnectionType() != BluetoothSocket.TYPE_L2CAP) {
-            return -1;
+            return mMaxReceivePacketSize;
         }
         return mSocket.getMaxReceivePacketSize();
     }
diff --git a/src/com/android/bluetooth/ObexServerSockets.java b/src/com/android/bluetooth/ObexServerSockets.java
index 789f12f..7bfd3fe 100644
--- a/src/com/android/bluetooth/ObexServerSockets.java
+++ b/src/com/android/bluetooth/ObexServerSockets.java
@@ -143,6 +143,10 @@
             } catch (IOException e) {
                 Log.e(STAG, "Error create ServerSockets ", e);
                 initSocketOK = false;
+            } catch (SecurityException e) {
+                Log.e(STAG, "Error create ServerSockets ", e);
+                initSocketOK = false;
+                break;
             }
             if (!initSocketOK) {
                 // Need to break out of this loop if BT is being turned off.
diff --git a/src/com/android/bluetooth/Utils.java b/src/com/android/bluetooth/Utils.java
index 9695568..f1e8e0f 100644
--- a/src/com/android/bluetooth/Utils.java
+++ b/src/com/android/bluetooth/Utils.java
@@ -16,28 +16,56 @@
 
 package com.android.bluetooth;
 
+import static android.Manifest.permission.ACCESS_COARSE_LOCATION;
+import static android.Manifest.permission.ACCESS_FINE_LOCATION;
+import static android.Manifest.permission.BLUETOOTH_ADVERTISE;
+import static android.Manifest.permission.BLUETOOTH_CONNECT;
+import static android.Manifest.permission.BLUETOOTH_SCAN;
+import static android.Manifest.permission.RENOUNCE_PERMISSIONS;
+import static android.content.PermissionChecker.PERMISSION_HARD_DENIED;
+import static android.content.PermissionChecker.PID_UNKNOWN;
+import static android.content.pm.PackageManager.GET_PERMISSIONS;
+import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+import static android.os.PowerExemptionManager.TEMPORARY_ALLOW_LIST_TYPE_FOREGROUND_SERVICE_ALLOWED;
+
+import android.Manifest;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
+import android.annotation.SuppressLint;
+import android.app.AppGlobals;
 import android.app.AppOpsManager;
+import android.app.BroadcastOptions;
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothDevice;
+import android.companion.Association;
+import android.companion.CompanionDeviceManager;
+import android.content.AttributionSource;
 import android.content.ContentValues;
 import android.content.Context;
-import android.content.ContextWrapper;
+import android.content.PermissionChecker;
+import android.content.pm.PackageInfo;
 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.Bundle;
 import android.os.ParcelUuid;
+import android.os.PowerExemptionManager;
 import android.os.Process;
+import android.os.RemoteException;
 import android.os.SystemProperties;
 import android.os.UserHandle;
 import android.os.UserManager;
+import android.provider.DeviceConfig;
 import android.provider.Telephony;
 import android.util.Log;
 
+import com.android.bluetooth.btservice.AdapterService;
+import com.android.bluetooth.btservice.ProfileService;
+
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
 
@@ -62,12 +90,14 @@
     private static final String TAG = "BluetoothUtils";
     private static final int MICROS_PER_UNIT = 625;
     private static final String PTS_TEST_MODE_PROPERTY = "persist.bluetooth.pts";
+    private static final String KEY_TEMP_ALLOW_LIST_DURATION_MS = "temp_allow_list_duration_ms";
+    private static final long DEFAULT_TEMP_ALLOW_LIST_DURATION_MS = 20_000;
 
     static final int BD_ADDR_LEN = 6; // bytes
     static final int BD_UUID_LEN = 16; // bytes
 
     /*
-     * Special characters
+     * Special character
      *
      * (See "What is a phone number?" doc)
      * 'p' --- GSM pause character, same as comma
@@ -85,6 +115,15 @@
         return c == 'w' || c == 'W';
     }
 
+    public static @Nullable String getName(@Nullable BluetoothDevice device) {
+        final AdapterService service = AdapterService.getAdapterService();
+        if (service != null && device != null) {
+            return service.getRemoteName(device);
+        } else {
+            return null;
+        }
+    }
+
     public static String getAddressStringFromByte(byte[] address) {
         if (address == null || address.length != BD_ADDR_LEN) {
             return null;
@@ -285,53 +324,279 @@
         Utils.sForegroundUserId = uid;
     }
 
-    public static void enforceBluetoothPermission(Context context) {
-        context.enforceCallingOrSelfPermission(
-                android.Manifest.permission.BLUETOOTH,
-                "Need BLUETOOTH permission");
+    /**
+     * Enforces that a Companion Device Manager (CDM) association exists between the calling
+     * application and the Bluetooth Device.
+     *
+     * @param cdm the CompanionDeviceManager object
+     * @param context the Bluetooth AdapterService context
+     * @param callingPackage the calling package
+     * @param callingUid the calling app uid
+     * @param device the remote BluetoothDevice
+     * @return {@code true} if there is a CDM association
+     * @throws SecurityException if the package name does not match the uid or the association
+     *                           doesn't exist
+     */
+    public static boolean enforceCdmAssociation(CompanionDeviceManager cdm, Context context,
+            String callingPackage, int callingUid, BluetoothDevice device) {
+        if (!isPackageNameAccurate(context, callingPackage, callingUid)) {
+            throw new SecurityException("hasCdmAssociation: Package name " + callingPackage
+                    + " is inaccurate for calling uid " + callingUid);
+        }
+
+        for (Association association : cdm.getAllAssociations()) {
+            if (association.getPackageName().equals(callingPackage)
+                    && association.getDeviceMacAddress().equals(device.getAddress())) {
+                return true;
+            }
+        }
+        throw new SecurityException("The application with package name " + callingPackage
+                + " does not have a CDM association with the Bluetooth Device");
     }
 
-    public static void enforceBluetoothAdminPermission(Context context) {
-        context.enforceCallingOrSelfPermission(
-                android.Manifest.permission.BLUETOOTH_ADMIN,
-                "Need BLUETOOTH ADMIN permission");
+    /**
+     * Verifies whether the calling package name matches the calling app uid
+     * @param context the Bluetooth AdapterService context
+     * @param callingPackage the calling application package name
+     * @param callingUid the calling application uid
+     * @return {@code true} if the package name matches the calling app uid, {@code false} otherwise
+     */
+    public static boolean isPackageNameAccurate(Context context, String callingPackage,
+            int callingUid) {
+        // Verifies the integrity of the calling package name
+        try {
+            int packageUid = context.getPackageManager().getPackageUid(callingPackage, 0);
+            if (packageUid != callingUid) {
+                Log.e(TAG, "isPackageNameAccurate: App with package name " + callingPackage
+                        + " is UID " + packageUid + " but caller is " + callingUid);
+                return false;
+            }
+        } catch (PackageManager.NameNotFoundException e) {
+            Log.e(TAG, "isPackageNameAccurate: App with package name " + callingPackage
+                    + " does not exist");
+            return false;
+        }
+        return true;
     }
 
+    /**
+     * Checks whether the caller has the BLUETOOTH_PRIVILEGED permission
+     *
+     * @param context the Bluetooth AdapterService context
+     * @return {@code true} if the caller has the BLUETOOTH_PRIVILEGED permission, {@code false}
+     *         otherwise
+     */
+    // Suppressed since we're not actually enforcing here
+    @SuppressLint("AndroidFrameworkRequiresPermission")
+    public static boolean hasBluetoothPrivilegedPermission(Context context) {
+        return context.checkCallingOrSelfPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+                == PackageManager.PERMISSION_GRANTED;
+    }
+
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED)
     public static void enforceBluetoothPrivilegedPermission(Context context) {
         context.enforceCallingOrSelfPermission(
                 android.Manifest.permission.BLUETOOTH_PRIVILEGED,
                 "Need BLUETOOTH PRIVILEGED permission");
     }
 
+    @RequiresPermission(android.Manifest.permission.LOCAL_MAC_ADDRESS)
     public static void enforceLocalMacAddressPermission(Context context) {
         context.enforceCallingOrSelfPermission(
                 android.Manifest.permission.LOCAL_MAC_ADDRESS,
                 "Need LOCAL_MAC_ADDRESS permission");
     }
 
+    @RequiresPermission(android.Manifest.permission.DUMP)
     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;
+    public static AttributionSource getCallingAttributionSource() {
+        int callingUid = Binder.getCallingUid();
+        if (callingUid == android.os.Process.ROOT_UID) {
+            callingUid = android.os.Process.SYSTEM_UID;
         }
-        return true;
+        try {
+            return new AttributionSource(callingUid,
+                    AppGlobals.getPackageManager().getPackagesForUid(callingUid)[0], null);
+        } catch (RemoteException e) {
+            throw new IllegalStateException("Failed to resolve AttributionSource", e);
+        }
     }
 
-    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;
+    @SuppressLint("AndroidFrameworkRequiresPermission")
+    private static boolean checkPermissionForPreflight(Context context, String permission) {
+        final int result = PermissionChecker.checkCallingOrSelfPermissionForPreflight(
+                context, permission);
+        if (result == PERMISSION_GRANTED) {
+            return true;
         }
-        return true;
+
+        final String msg = "Need " + permission + " permission";
+        if (result == PERMISSION_HARD_DENIED) {
+            throw new SecurityException(msg);
+        } else {
+            Log.w(TAG, msg);
+            return false;
+        }
     }
 
-    public static boolean checkCaller() {
+    @SuppressLint("AndroidFrameworkRequiresPermission")
+    private static boolean checkPermissionForDataDelivery(Context context, String permission,
+            AttributionSource attributionSource, String message) {
+        // STOPSHIP(b/188391719): enable this security enforcement
+        // attributionSource.enforceCallingUid();
+        final int result = PermissionChecker.checkPermissionForDataDeliveryFromDataSource(
+                context, permission, PID_UNKNOWN,
+                new AttributionSource(context.getAttributionSource(), attributionSource), message);
+        if (result == PERMISSION_GRANTED) {
+            return true;
+        }
+
+        final String msg = "Need " + permission + " permission for " + attributionSource + ": "
+                + message;
+        if (result == PERMISSION_HARD_DENIED) {
+            throw new SecurityException(msg);
+        } else {
+            Log.w(TAG, msg);
+            return false;
+        }
+    }
+
+    /**
+     * Returns true if the BLUETOOTH_CONNECT permission is granted for the calling app. Returns
+     * false if the result is a soft denial. Throws SecurityException if the result is a hard
+     * denial.
+     *
+     * <p>Should be used in situations where the app op should not be noted.
+     */
+    @SuppressLint("AndroidFrameworkRequiresPermission")
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
+    public static boolean checkConnectPermissionForPreflight(Context context) {
+        return checkPermissionForPreflight(context, BLUETOOTH_CONNECT);
+    }
+
+    /**
+     * Returns true if the BLUETOOTH_CONNECT permission is granted for the calling app. Returns
+     * false if the result is a soft denial. Throws SecurityException if the result is a hard
+     * denial.
+     *
+     * <p>Should be used in situations where data will be delivered and hence the app op should
+     * be noted.
+     */
+    @SuppressLint("AndroidFrameworkRequiresPermission")
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
+    public static boolean checkConnectPermissionForDataDelivery(
+            Context context, AttributionSource attributionSource, String message) {
+        return checkPermissionForDataDelivery(context, BLUETOOTH_CONNECT,
+                attributionSource, message);
+    }
+
+    /**
+     * Returns true if the BLUETOOTH_SCAN permission is granted for the calling app. Returns false
+     * if the result is a soft denial. Throws SecurityException if the result is a hard denial.
+     *
+     * <p>Should be used in situations where the app op should not be noted.
+     */
+    @SuppressLint("AndroidFrameworkRequiresPermission")
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN)
+    public static boolean checkScanPermissionForPreflight(Context context) {
+        return checkPermissionForPreflight(context, BLUETOOTH_SCAN);
+    }
+
+    /**
+     * Returns true if the BLUETOOTH_SCAN permission is granted for the calling app. Returns false
+     * if the result is a soft denial. Throws SecurityException if the result is a hard denial.
+     *
+     * <p>Should be used in situations where data will be delivered and hence the app op should
+     * be noted.
+     */
+    @SuppressLint("AndroidFrameworkRequiresPermission")
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN)
+    public static boolean checkScanPermissionForDataDelivery(
+            Context context, AttributionSource attributionSource, String message) {
+        return checkPermissionForDataDelivery(context, BLUETOOTH_SCAN,
+                attributionSource, message);
+    }
+
+    /**
+     * Returns true if the BLUETOOTH_ADVERTISE permission is granted for the
+     * calling app. Returns false if the result is a soft denial. Throws
+     * SecurityException if the result is a hard denial.
+     * <p>
+     * Should be used in situations where the app op should not be noted.
+     */
+    @SuppressLint("AndroidFrameworkRequiresPermission")
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADVERTISE)
+    public static boolean checkAdvertisePermissionForPreflight(Context context) {
+        return checkPermissionForPreflight(context, BLUETOOTH_ADVERTISE);
+    }
+
+    /**
+     * Returns true if the BLUETOOTH_ADVERTISE permission is granted for the
+     * calling app. Returns false if the result is a soft denial. Throws
+     * SecurityException if the result is a hard denial.
+     * <p>
+     * Should be used in situations where data will be delivered and hence the
+     * app op should be noted.
+     */
+    @SuppressLint("AndroidFrameworkRequiresPermission")
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADVERTISE)
+    public static boolean checkAdvertisePermissionForDataDelivery(
+            Context context, AttributionSource attributionSource, String message) {
+        return checkPermissionForDataDelivery(context, BLUETOOTH_ADVERTISE,
+                attributionSource, message);
+    }
+
+    /**
+     * Returns true if the specified package has disavowed the use of bluetooth scans for location,
+     * that is, if they have specified the {@code neverForLocation} flag on the BLUETOOTH_SCAN
+     * permission.
+     */
+    // Suppressed since we're not actually enforcing here
+    @SuppressLint("AndroidFrameworkRequiresPermission")
+    public static boolean hasDisavowedLocationForScan(
+            Context context, AttributionSource attributionSource, boolean inTestMode) {
+
+        // Check every step along the attribution chain for a renouncement.
+        // If location has been renounced anywhere in the chain we treat it as a disavowal.
+        AttributionSource currentAttrib = attributionSource;
+        while (true) {
+            if (currentAttrib.getRenouncedPermissions().contains(ACCESS_FINE_LOCATION)
+                    && (inTestMode || context.checkPermission(RENOUNCE_PERMISSIONS, -1,
+                    currentAttrib.getUid())
+                    == PackageManager.PERMISSION_GRANTED)) {
+                return true;
+            }
+            AttributionSource nextAttrib = currentAttrib.getNext();
+            if (nextAttrib == null) {
+                break;
+            }
+            currentAttrib = nextAttrib;
+        }
+
+        // Check the last attribution in the chain for a neverForLocation disavowal.
+        String packageName = currentAttrib.getPackageName();
+        PackageManager pm = context.getPackageManager();
+        try {
+            // TODO(b/183478032): Cache PackageInfo for use here.
+            PackageInfo pkgInfo = pm.getPackageInfo(packageName, GET_PERMISSIONS);
+            for (int i = 0; i < pkgInfo.requestedPermissions.length; i++) {
+                if (pkgInfo.requestedPermissions[i].equals(BLUETOOTH_SCAN)) {
+                    return (pkgInfo.requestedPermissionsFlags[i]
+                            & PackageInfo.REQUESTED_PERMISSION_NEVER_FOR_LOCATION) != 0;
+                }
+            }
+        } catch (PackageManager.NameNotFoundException e) {
+            Log.w(TAG, "Could not find package for disavowal check: " + packageName);
+        }
+        return false;
+    }
+
+    public static boolean checkCallerIsSystemOrActiveUser() {
         int callingUser = UserHandle.getCallingUserId();
         int callingUid = Binder.getCallingUid();
         return (sForegroundUserId == callingUser)
@@ -339,9 +604,21 @@
                 || (UserHandle.getAppId(Process.SYSTEM_UID) == UserHandle.getAppId(callingUid));
     }
 
-    public static boolean checkCallerAllowManagedProfiles(Context mContext) {
-        if (mContext == null) {
-            return checkCaller();
+    public static boolean checkCallerIsSystemOrActiveUser(String tag) {
+        final boolean res = checkCallerIsSystemOrActiveUser();
+        if (!res) {
+            Log.w(TAG, tag + " - Not allowed for non-active user and non-system user");
+        }
+        return res;
+    }
+
+    public static boolean callerIsSystemOrActiveUser(String tag, String method) {
+        return checkCallerIsSystemOrActiveUser(tag + "." + method + "()");
+    }
+
+    public static boolean checkCallerIsSystemOrActiveOrManagedUser(Context context) {
+        if (context == null) {
+            return checkCallerIsSystemOrActiveUser();
         }
         int callingUser = UserHandle.getCallingUserId();
         int callingUid = Binder.getCallingUid();
@@ -349,7 +626,7 @@
         // Use the Bluetooth process identity when making call to get parent user
         long ident = Binder.clearCallingIdentity();
         try {
-            UserManager um = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
+            UserManager um = (UserManager) context.getSystemService(Context.USER_SERVICE);
             UserInfo ui = um.getProfileParent(callingUser);
             int parentUser = (ui != null) ? ui.id : UserHandle.USER_NULL;
 
@@ -365,16 +642,30 @@
         }
     }
 
-    /**
-     * Enforce the context has android.Manifest.permission.BLUETOOTH_ADMIN permission. A
-     * {@link SecurityException} would be thrown if neither the calling process or the application
-     * does not have BLUETOOTH_ADMIN permission.
-     *
-     * @param context Context for the permission check.
-     */
-    public static void enforceAdminPermission(ContextWrapper context) {
-        context.enforceCallingOrSelfPermission(android.Manifest.permission.BLUETOOTH_ADMIN,
-                "Need BLUETOOTH_ADMIN permission");
+    public static boolean checkCallerIsSystemOrActiveOrManagedUser(Context context, String tag) {
+        final boolean res = checkCallerIsSystemOrActiveOrManagedUser(context);
+        if (!res) {
+            Log.w(TAG, tag + " - Not allowed for"
+                    + " non-active user and non-system and non-managed user");
+        }
+        return res;
+    }
+
+    public static boolean callerIsSystemOrActiveOrManagedUser(Context context, String tag,
+            String method) {
+        return checkCallerIsSystemOrActiveOrManagedUser(context, tag + "." + method + "()");
+    }
+
+    public static boolean checkServiceAvailable(ProfileService service, String tag) {
+        if (service == null) {
+            Log.w(TAG, tag + " - Not present");
+            return false;
+        }
+        if (!service.isAvailable()) {
+            Log.w(TAG, tag + " - Not available");
+            return false;
+        }
+        return true;
     }
 
     /**
@@ -389,19 +680,21 @@
      * Checks that calling process has android.Manifest.permission.ACCESS_COARSE_LOCATION and
      * OP_COARSE_LOCATION is allowed
      */
-    public static boolean checkCallerHasCoarseLocation(Context context, AppOpsManager appOps,
-            String callingPackage, @Nullable String callingFeatureId, UserHandle userHandle) {
+    // Suppressed since we're not actually enforcing here
+    @SuppressLint("AndroidFrameworkRequiresPermission")
+    public static boolean checkCallerHasCoarseLocation(
+            Context context, AttributionSource attributionSource, UserHandle userHandle) {
         if (blockedByLocationOff(context, userHandle)) {
             Log.e(TAG, "Permission denial: Location is off.");
             return false;
         }
 
-        // Check coarse, but note fine
-        if (context.checkCallingOrSelfPermission(
-                android.Manifest.permission.ACCESS_COARSE_LOCATION)
-                        == PackageManager.PERMISSION_GRANTED
-                && isAppOppAllowed(appOps, AppOpsManager.OPSTR_FINE_LOCATION, callingPackage,
-                callingFeatureId)) {
+        // STOPSHIP(b/188391719): enable this security enforcement
+        // attributionSource.enforceCallingUid();
+        if (PermissionChecker.checkPermissionForDataDeliveryFromDataSource(
+                context, ACCESS_COARSE_LOCATION, PID_UNKNOWN,
+                new AttributionSource(context.getAttributionSource(), attributionSource),
+                "Bluetooth location check") == PERMISSION_GRANTED) {
             return true;
         }
 
@@ -415,27 +708,28 @@
      * OP_COARSE_LOCATION is allowed or android.Manifest.permission.ACCESS_FINE_LOCATION and
      * OP_FINE_LOCATION is allowed
      */
-    public static boolean checkCallerHasCoarseOrFineLocation(Context context, AppOpsManager appOps,
-            String callingPackage, @Nullable String callingFeatureId, UserHandle userHandle) {
+    // Suppressed since we're not actually enforcing here
+    @SuppressLint("AndroidFrameworkRequiresPermission")
+    public static boolean checkCallerHasCoarseOrFineLocation(
+            Context context, AttributionSource attributionSource, UserHandle userHandle) {
         if (blockedByLocationOff(context, userHandle)) {
             Log.e(TAG, "Permission denial: Location is off.");
             return false;
         }
 
-        if (context.checkCallingOrSelfPermission(
-                android.Manifest.permission.ACCESS_FINE_LOCATION)
-                        == PackageManager.PERMISSION_GRANTED
-                && isAppOppAllowed(appOps, AppOpsManager.OPSTR_FINE_LOCATION, callingPackage,
-                callingFeatureId)) {
+        // STOPSHIP(b/188391719): enable this security enforcement
+        // attributionSource.enforceCallingUid();
+        if (PermissionChecker.checkPermissionForDataDeliveryFromDataSource(
+                context, ACCESS_FINE_LOCATION, PID_UNKNOWN,
+                new AttributionSource(context.getAttributionSource(), attributionSource),
+                "Bluetooth location check") == PERMISSION_GRANTED) {
             return true;
         }
 
-        // Check coarse, but note fine
-        if (context.checkCallingOrSelfPermission(
-                android.Manifest.permission.ACCESS_COARSE_LOCATION)
-                        == PackageManager.PERMISSION_GRANTED
-                && isAppOppAllowed(appOps, AppOpsManager.OPSTR_FINE_LOCATION, callingPackage,
-                callingFeatureId)) {
+        if (PermissionChecker.checkPermissionForDataDeliveryFromDataSource(
+                context, ACCESS_COARSE_LOCATION, PID_UNKNOWN,
+                new AttributionSource(context.getAttributionSource(), attributionSource),
+                "Bluetooth location check") == PERMISSION_GRANTED) {
             return true;
         }
 
@@ -448,18 +742,21 @@
      * Checks that calling process has android.Manifest.permission.ACCESS_FINE_LOCATION and
      * OP_FINE_LOCATION is allowed
      */
-    public static boolean checkCallerHasFineLocation(Context context, AppOpsManager appOps,
-            String callingPackage, @Nullable String callingFeatureId, UserHandle userHandle) {
+    // Suppressed since we're not actually enforcing here
+    @SuppressLint("AndroidFrameworkRequiresPermission")
+    public static boolean checkCallerHasFineLocation(
+            Context context, AttributionSource attributionSource, UserHandle userHandle) {
         if (blockedByLocationOff(context, userHandle)) {
             Log.e(TAG, "Permission denial: Location is off.");
             return false;
         }
 
-        if (context.checkCallingOrSelfPermission(
-                android.Manifest.permission.ACCESS_FINE_LOCATION)
-                        == PackageManager.PERMISSION_GRANTED
-                && isAppOppAllowed(appOps, AppOpsManager.OPSTR_FINE_LOCATION, callingPackage,
-                callingFeatureId)) {
+        // STOPSHIP(b/188391719): enable this security enforcement
+        // attributionSource.enforceCallingUid();
+        if (PermissionChecker.checkPermissionForDataDeliveryFromDataSource(
+                context, ACCESS_FINE_LOCATION, PID_UNKNOWN,
+                new AttributionSource(context.getAttributionSource(), attributionSource),
+                "Bluetooth location check") == PERMISSION_GRANTED) {
             return true;
         }
 
@@ -471,6 +768,8 @@
     /**
      * Returns true if the caller holds NETWORK_SETTINGS
      */
+    // Suppressed since we're not actually enforcing here
+    @SuppressLint("AndroidFrameworkRequiresPermission")
     public static boolean checkCallerHasNetworkSettingsPermission(Context context) {
         return context.checkCallingOrSelfPermission(android.Manifest.permission.NETWORK_SETTINGS)
                 == PackageManager.PERMISSION_GRANTED;
@@ -479,6 +778,8 @@
     /**
      * Returns true if the caller holds NETWORK_SETUP_WIZARD
      */
+    // Suppressed since we're not actually enforcing here
+    @SuppressLint("AndroidFrameworkRequiresPermission")
     public static boolean checkCallerHasNetworkSetupWizardPermission(Context context) {
         return context.checkCallingOrSelfPermission(
                 android.Manifest.permission.NETWORK_SETUP_WIZARD)
@@ -488,12 +789,29 @@
     /**
      * Returns true if the caller holds RADIO_SCAN_WITHOUT_LOCATION
      */
+    // Suppressed since we're not actually enforcing here
+    @SuppressLint("AndroidFrameworkRequiresPermission")
     public static boolean checkCallerHasScanWithoutLocationPermission(Context context) {
         return context.checkCallingOrSelfPermission(
                 android.Manifest.permission.RADIO_SCAN_WITHOUT_LOCATION)
                 == PackageManager.PERMISSION_GRANTED;
     }
 
+    // Suppressed since we're not actually enforcing here
+    @SuppressLint("AndroidFrameworkRequiresPermission")
+    public static boolean checkCallerHasPrivilegedPermission(Context context) {
+        return context.checkCallingOrSelfPermission(
+                android.Manifest.permission.BLUETOOTH_PRIVILEGED)
+                == PackageManager.PERMISSION_GRANTED;
+    }
+
+    // Suppressed since we're not actually enforcing here
+    @SuppressLint("AndroidFrameworkRequiresPermission")
+    public static boolean checkCallerHasWriteSmsPermission(Context context) {
+        return context.checkCallingOrSelfPermission(
+                android.Manifest.permission.WRITE_SMS) == PackageManager.PERMISSION_GRANTED;
+    }
+
     public static boolean isQApp(Context context, String pkgName) {
         try {
             return context.getPackageManager().getApplicationInfo(pkgName, 0).targetSdkVersion
@@ -633,4 +951,20 @@
 
         return 1 == context.getContentResolver().update(uri, values, null, null);
     }
+
+    public static @NonNull Bundle getTempAllowlistBroadcastOptions() {
+        // Use the Bluetooth process identity to pass permission check when reading DeviceConfig
+        final long ident = Binder.clearCallingIdentity();
+        final BroadcastOptions bOptions = BroadcastOptions.makeBasic();
+        try {
+            final long durationMs = DeviceConfig.getLong(DeviceConfig.NAMESPACE_BLUETOOTH,
+                    KEY_TEMP_ALLOW_LIST_DURATION_MS, DEFAULT_TEMP_ALLOW_LIST_DURATION_MS);
+            bOptions.setTemporaryAppAllowlist(durationMs,
+                    TEMPORARY_ALLOW_LIST_TYPE_FOREGROUND_SERVICE_ALLOWED,
+                    PowerExemptionManager.REASON_BLUETOOTH_BROADCAST, "");
+        } finally {
+            Binder.restoreCallingIdentity(ident);
+        }
+        return bOptions.toBundle();
+    }
 }
diff --git a/src/com/android/bluetooth/a2dp/A2dpNativeInterface.java b/src/com/android/bluetooth/a2dp/A2dpNativeInterface.java
index 3203513..2530f82 100644
--- a/src/com/android/bluetooth/a2dp/A2dpNativeInterface.java
+++ b/src/com/android/bluetooth/a2dp/A2dpNativeInterface.java
@@ -21,6 +21,7 @@
  */
 package com.android.bluetooth.a2dp;
 
+import android.annotation.RequiresPermission;
 import android.bluetooth.BluetoothA2dp;
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothCodecConfig;
diff --git a/src/com/android/bluetooth/a2dp/A2dpService.java b/src/com/android/bluetooth/a2dp/A2dpService.java
index 45a066a..c6b1759 100644
--- a/src/com/android/bluetooth/a2dp/A2dpService.java
+++ b/src/com/android/bluetooth/a2dp/A2dpService.java
@@ -16,9 +16,11 @@
 
 package com.android.bluetooth.a2dp;
 
-import static com.android.bluetooth.Utils.enforceBluetoothPermission;
+import static android.Manifest.permission.BLUETOOTH_CONNECT;
+
 import static com.android.bluetooth.Utils.enforceBluetoothPrivilegedPermission;
 
+import android.annotation.RequiresPermission;
 import android.bluetooth.BluetoothA2dp;
 import android.bluetooth.BluetoothA2dp.OptionalCodecsPreferenceStatus;
 import android.bluetooth.BluetoothA2dp.OptionalCodecsSupportStatus;
@@ -27,7 +29,10 @@
 import android.bluetooth.BluetoothDevice;
 import android.bluetooth.BluetoothProfile;
 import android.bluetooth.BluetoothUuid;
+import android.bluetooth.BufferConstraints;
 import android.bluetooth.IBluetoothA2dp;
+import android.content.Attributable;
+import android.content.AttributionSource;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
@@ -43,6 +48,7 @@
 import com.android.bluetooth.btservice.MetricsLogger;
 import com.android.bluetooth.btservice.ProfileService;
 import com.android.bluetooth.btservice.ServiceFactory;
+import com.android.bluetooth.btservice.storage.DatabaseManager;
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.ArrayUtils;
@@ -64,6 +70,7 @@
     private static A2dpService sA2dpService;
 
     private AdapterService mAdapterService;
+    private DatabaseManager mDatabaseManager;
     private HandlerThread mStateMachinesThread;
 
     @VisibleForTesting
@@ -108,12 +115,14 @@
             throw new IllegalStateException("start() called twice");
         }
 
-        // Step 1: Get AdapterService, A2dpNativeInterface, AudioManager.
+        // Step 1: Get AdapterService, A2dpNativeInterface, DatabaseManager, AudioManager.
         // None of them can be null.
         mAdapterService = Objects.requireNonNull(AdapterService.getAdapterService(),
                 "AdapterService cannot be null when A2dpService starts");
         mA2dpNativeInterface = Objects.requireNonNull(A2dpNativeInterface.getInstance(),
                 "A2dpNativeInterface cannot be null when A2dpService starts");
+        mDatabaseManager = Objects.requireNonNull(mAdapterService.getDatabase(),
+                "DatabaseManager cannot be null when A2dpService starts");
         mAudioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
         Objects.requireNonNull(mAudioManager,
                                "AudioManager cannot be null when A2dpService starts");
@@ -234,7 +243,6 @@
     }
 
     public boolean connect(BluetoothDevice device) {
-        enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH ADMIN permission");
         if (DBG) {
             Log.d(TAG, "connect(): " + device);
         }
@@ -286,7 +294,6 @@
      * @return true if profile disconnected, false if device not connected over a2dp
      */
     public boolean disconnect(BluetoothDevice device) {
-        enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH ADMIN permission");
         if (DBG) {
             Log.d(TAG, "disconnect(): " + device);
         }
@@ -303,7 +310,6 @@
     }
 
     public List<BluetoothDevice> getConnectedDevices() {
-        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
         synchronized (mStateMachines) {
             List<BluetoothDevice> devices = new ArrayList<>();
             for (A2dpStateMachine sm : mStateMachines.values()) {
@@ -384,7 +390,6 @@
     }
 
     List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
-        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
         List<BluetoothDevice> devices = new ArrayList<>();
         if (states == null) {
             return devices;
@@ -432,7 +437,6 @@
     }
 
     public int getConnectionState(BluetoothDevice device) {
-        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
         synchronized (mStateMachines) {
             A2dpStateMachine sm = mStateMachines.get(device);
             if (sm == null) {
@@ -508,7 +512,6 @@
      * @return true on success, otherwise false
      */
     public boolean setActiveDevice(BluetoothDevice device) {
-        enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH ADMIN permission");
         synchronized (mActiveSwitchingGuard) {
             if (device == null) {
                 // Remove active device and continue playing audio only if necessary.
@@ -597,7 +600,6 @@
      * @return the active device or null if no device is active
      */
     public BluetoothDevice getActiveDevice() {
-        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
         synchronized (mStateMachines) {
             return mActiveDevice;
         }
@@ -624,22 +626,24 @@
      * @param connectionPolicy is the connection policy to set to for this profile
      * @return true if connectionPolicy is set, false on error
      */
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED)
     public boolean setConnectionPolicy(BluetoothDevice device, int connectionPolicy) {
         enforceCallingOrSelfPermission(BLUETOOTH_PRIVILEGED,
                 "Need BLUETOOTH_PRIVILEGED permission");
         if (DBG) {
             Log.d(TAG, "Saved connectionPolicy " + device + " = " + connectionPolicy);
         }
-        boolean setSuccessfully;
-        setSuccessfully = mAdapterService.getDatabase()
-                .setProfileConnectionPolicy(device, BluetoothProfile.A2DP, connectionPolicy);
-        if (setSuccessfully && connectionPolicy == BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
+
+        if (!mDatabaseManager.setProfileConnectionPolicy(device, BluetoothProfile.A2DP,
+                  connectionPolicy)) {
+            return false;
+        }
+        if (connectionPolicy == BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
             connect(device);
-        } else if (setSuccessfully
-                && connectionPolicy == BluetoothProfile.CONNECTION_POLICY_FORBIDDEN) {
+        } else if (connectionPolicy == BluetoothProfile.CONNECTION_POLICY_FORBIDDEN) {
             disconnect(device);
         }
-        return setSuccessfully;
+        return true;
     }
 
     /**
@@ -655,7 +659,7 @@
      * @hide
      */
     public int getConnectionPolicy(BluetoothDevice device) {
-        return mAdapterService.getDatabase()
+        return mDatabaseManager
                 .getProfileConnectionPolicy(device, BluetoothProfile.A2DP);
     }
 
@@ -675,7 +679,6 @@
     }
 
     boolean isA2dpPlaying(BluetoothDevice device) {
-        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
         if (DBG) {
             Log.d(TAG, "isA2dpPlaying(" + device + ")");
         }
@@ -697,7 +700,6 @@
      * @hide
      */
     public BluetoothCodecStatus getCodecStatus(BluetoothDevice device) {
-        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
         if (DBG) {
             Log.d(TAG, "getCodecStatus(" + device + ")");
         }
@@ -726,7 +728,6 @@
      */
     public void setCodecConfigPreference(BluetoothDevice device,
                                          BluetoothCodecConfig codecConfig) {
-        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
         if (DBG) {
             Log.d(TAG, "setCodecConfigPreference(" + device + "): "
                     + Objects.toString(codecConfig));
@@ -758,7 +759,6 @@
      * @hide
      */
     public void enableOptionalCodecs(BluetoothDevice device) {
-        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
         if (DBG) {
             Log.d(TAG, "enableOptionalCodecs(" + device + ")");
         }
@@ -789,7 +789,6 @@
      * @hide
      */
     public void disableOptionalCodecs(BluetoothDevice device) {
-        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
         if (DBG) {
             Log.d(TAG, "disableOptionalCodecs(" + device + ")");
         }
@@ -822,15 +821,13 @@
      * {@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);
+        return mDatabaseManager.getA2dpSupportsOptionalCodecs(device);
     }
 
     public void setSupportsOptionalCodecs(BluetoothDevice device, boolean doesSupport) {
-        enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH ADMIN permission");
         int value = doesSupport ? BluetoothA2dp.OPTIONAL_CODECS_SUPPORTED
                 : BluetoothA2dp.OPTIONAL_CODECS_NOT_SUPPORTED;
-        mAdapterService.getDatabase().setA2dpSupportsOptionalCodecs(device, value);
+        mDatabaseManager.setA2dpSupportsOptionalCodecs(device, value);
     }
 
     /**
@@ -843,8 +840,7 @@
      * {@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);
+        return mDatabaseManager.getA2dpOptionalCodecsEnabled(device);
     }
 
     /**
@@ -858,14 +854,54 @@
      */
     public void setOptionalCodecsEnabled(BluetoothDevice device,
             @OptionalCodecsPreferenceStatus int value) {
-        enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission");
         if (value != BluetoothA2dp.OPTIONAL_CODECS_PREF_UNKNOWN
                 && value != BluetoothA2dp.OPTIONAL_CODECS_PREF_DISABLED
                 && value != BluetoothA2dp.OPTIONAL_CODECS_PREF_ENABLED) {
             Log.w(TAG, "Unexpected value passed to setOptionalCodecsEnabled:" + value);
             return;
         }
-        mAdapterService.getDatabase().setA2dpOptionalCodecsEnabled(device, value);
+        mDatabaseManager.setA2dpOptionalCodecsEnabled(device, value);
+    }
+
+    /**
+     * Get dynamic audio buffer size supported type
+     *
+     * @return support <p>Possible values are
+     * {@link BluetoothA2dp#DYNAMIC_BUFFER_SUPPORT_NONE},
+     * {@link BluetoothA2dp#DYNAMIC_BUFFER_SUPPORT_A2DP_OFFLOAD},
+     * {@link BluetoothA2dp#DYNAMIC_BUFFER_SUPPORT_A2DP_SOFTWARE_ENCODING}.
+     */
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED)
+    public int getDynamicBufferSupport() {
+        enforceCallingOrSelfPermission(BLUETOOTH_PRIVILEGED,
+                "Need BLUETOOTH_PRIVILEGED permission");
+        return mAdapterService.getDynamicBufferSupport();
+    }
+
+    /**
+     * Get dynamic audio buffer size
+     *
+     * @return BufferConstraints
+     */
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED)
+    public BufferConstraints getBufferConstraints() {
+        enforceCallingOrSelfPermission(BLUETOOTH_PRIVILEGED,
+                "Need BLUETOOTH_PRIVILEGED permission");
+        return mAdapterService.getBufferConstraints();
+    }
+
+    /**
+     * Set dynamic audio buffer size
+     *
+     * @param codec Audio codec
+     * @param value buffer millis
+     * @return true if the settings is successful, false otherwise
+     */
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED)
+    public boolean setBufferLengthMillis(int codec, int value) {
+        enforceCallingOrSelfPermission(BLUETOOTH_PRIVILEGED,
+                "Need BLUETOOTH_PRIVILEGED permission");
+        return mAdapterService.setBufferLengthMillis(codec, value);
     }
 
     // Handle messages from native (JNI) to Java
@@ -990,7 +1026,7 @@
         intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
         intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT
                         | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
-        sendBroadcast(intent, ProfileService.BLUETOOTH_PERM);
+        sendBroadcast(intent, BLUETOOTH_CONNECT, Utils.getTempAllowlistBroadcastOptions());
     }
 
     private void broadcastCodecConfig(BluetoothDevice device, BluetoothCodecStatus codecStatus) {
@@ -1002,7 +1038,7 @@
         intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
         intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT
                         | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
-        sendBroadcast(intent, A2dpService.BLUETOOTH_PERM);
+        sendBroadcast(intent, BLUETOOTH_CONNECT, Utils.getTempAllowlistBroadcastOptions());
     }
 
     private class BondStateChangedReceiver extends BroadcastReceiver {
@@ -1180,16 +1216,14 @@
             implements IProfileServiceBinder {
         private A2dpService mService;
 
-        private A2dpService getService() {
-            if (!Utils.checkCaller()) {
-                Log.w(TAG, "A2DP call not allowed for non-active user");
+        @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
+        private A2dpService getService(AttributionSource source) {
+            if (!Utils.checkCallerIsSystemOrActiveUser(TAG)
+                    || !Utils.checkServiceAvailable(mService, TAG)
+                    || !Utils.checkConnectPermissionForDataDelivery(mService, source, TAG)) {
                 return null;
             }
-
-            if (mService != null && mService.isAvailable()) {
-                return mService;
-            }
-            return null;
+            return mService;
         }
 
         BluetoothA2dpBinder(A2dpService svc) {
@@ -1203,7 +1237,13 @@
 
         @Override
         public boolean connect(BluetoothDevice device) {
-            A2dpService service = getService();
+            return connectWithAttribution(device, Utils.getCallingAttributionSource());
+        }
+
+        @Override
+        public boolean connectWithAttribution(BluetoothDevice device, AttributionSource source) {
+            Attributable.setAttributionSource(device, source);
+            A2dpService service = getService(source);
             if (service == null) {
                 return false;
             }
@@ -1212,7 +1252,13 @@
 
         @Override
         public boolean disconnect(BluetoothDevice device) {
-            A2dpService service = getService();
+            return disconnectWithAttribution(device, Utils.getCallingAttributionSource());
+        }
+
+        @Override
+        public boolean disconnectWithAttribution(BluetoothDevice device, AttributionSource source) {
+            Attributable.setAttributionSource(device, source);
+            A2dpService service = getService(source);
             if (service == null) {
                 return false;
             }
@@ -1221,7 +1267,12 @@
 
         @Override
         public List<BluetoothDevice> getConnectedDevices() {
-            A2dpService service = getService();
+            return getConnectedDevicesWithAttribution(Utils.getCallingAttributionSource());
+        }
+
+        @Override
+        public List<BluetoothDevice> getConnectedDevicesWithAttribution(AttributionSource source) {
+            A2dpService service = getService(source);
             if (service == null) {
                 return new ArrayList<>(0);
             }
@@ -1230,7 +1281,14 @@
 
         @Override
         public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
-            A2dpService service = getService();
+            return getDevicesMatchingConnectionStatesWithAttribution(states,
+                    Utils.getCallingAttributionSource());
+        }
+
+        @Override
+        public List<BluetoothDevice> getDevicesMatchingConnectionStatesWithAttribution(int[] states,
+                AttributionSource source) {
+            A2dpService service = getService(source);
             if (service == null) {
                 return new ArrayList<>(0);
             }
@@ -1239,7 +1297,14 @@
 
         @Override
         public int getConnectionState(BluetoothDevice device) {
-            A2dpService service = getService();
+            return getConnectionStateWithAttribution(device, Utils.getCallingAttributionSource());
+        }
+
+        @Override
+        public int getConnectionStateWithAttribution(BluetoothDevice device,
+                AttributionSource source) {
+            Attributable.setAttributionSource(device, source);
+            A2dpService service = getService(source);
             if (service == null) {
                 return BluetoothProfile.STATE_DISCONNECTED;
             }
@@ -1247,8 +1312,9 @@
         }
 
         @Override
-        public boolean setActiveDevice(BluetoothDevice device) {
-            A2dpService service = getService();
+        public boolean setActiveDevice(BluetoothDevice device, AttributionSource source) {
+            Attributable.setAttributionSource(device, source);
+            A2dpService service = getService(source);
             if (service == null) {
                 return false;
             }
@@ -1256,8 +1322,8 @@
         }
 
         @Override
-        public BluetoothDevice getActiveDevice() {
-            A2dpService service = getService();
+        public BluetoothDevice getActiveDevice(AttributionSource source) {
+            A2dpService service = getService(source);
             if (service == null) {
                 return null;
             }
@@ -1265,8 +1331,10 @@
         }
 
         @Override
-        public boolean setConnectionPolicy(BluetoothDevice device, int connectionPolicy) {
-            A2dpService service = getService();
+        public boolean setConnectionPolicy(BluetoothDevice device, int connectionPolicy,
+                AttributionSource source) {
+            Attributable.setAttributionSource(device, source);
+            A2dpService service = getService(source);
             if (service == null) {
                 return false;
             }
@@ -1274,18 +1342,19 @@
         }
 
         @Override
-        public int getPriority(BluetoothDevice device) {
-            A2dpService service = getService();
+        public int getPriority(BluetoothDevice device, AttributionSource source) {
+            Attributable.setAttributionSource(device, source);
+            A2dpService service = getService(source);
             if (service == null) {
                 return BluetoothProfile.CONNECTION_POLICY_UNKNOWN;
             }
-            enforceBluetoothPermission(service);
             return service.getConnectionPolicy(device);
         }
 
         @Override
-        public int getConnectionPolicy(BluetoothDevice device) {
-            A2dpService service = getService();
+        public int getConnectionPolicy(BluetoothDevice device, AttributionSource source) {
+            Attributable.setAttributionSource(device, source);
+            A2dpService service = getService(source);
             if (service == null) {
                 return BluetoothProfile.CONNECTION_POLICY_UNKNOWN;
             }
@@ -1295,16 +1364,13 @@
 
         @Override
         public boolean isAvrcpAbsoluteVolumeSupported() {
-            A2dpService service = getService();
-            if (service == null) {
-                return false;
-            }
-            return service.isAvrcpAbsoluteVolumeSupported();
+            // TODO (apanicke): Add a hook here for the AvrcpTargetService.
+            return false;
         }
 
         @Override
-        public void setAvrcpAbsoluteVolume(int volume) {
-            A2dpService service = getService();
+        public void setAvrcpAbsoluteVolume(int volume, AttributionSource source) {
+            A2dpService service = getService(source);
             if (service == null) {
                 return;
             }
@@ -1312,8 +1378,9 @@
         }
 
         @Override
-        public boolean isA2dpPlaying(BluetoothDevice device) {
-            A2dpService service = getService();
+        public boolean isA2dpPlaying(BluetoothDevice device, AttributionSource source) {
+            Attributable.setAttributionSource(device, source);
+            A2dpService service = getService(source);
             if (service == null) {
                 return false;
             }
@@ -1321,8 +1388,10 @@
         }
 
         @Override
-        public BluetoothCodecStatus getCodecStatus(BluetoothDevice device) {
-            A2dpService service = getService();
+        public BluetoothCodecStatus getCodecStatus(BluetoothDevice device,
+                AttributionSource source) {
+            Attributable.setAttributionSource(device, source);
+            A2dpService service = getService(source);
             if (service == null) {
                 return null;
             }
@@ -1331,8 +1400,9 @@
 
         @Override
         public void setCodecConfigPreference(BluetoothDevice device,
-                                             BluetoothCodecConfig codecConfig) {
-            A2dpService service = getService();
+                BluetoothCodecConfig codecConfig, AttributionSource source) {
+            Attributable.setAttributionSource(device, source);
+            A2dpService service = getService(source);
             if (service == null) {
                 return;
             }
@@ -1340,8 +1410,9 @@
         }
 
         @Override
-        public void enableOptionalCodecs(BluetoothDevice device) {
-            A2dpService service = getService();
+        public void enableOptionalCodecs(BluetoothDevice device, AttributionSource source) {
+            Attributable.setAttributionSource(device, source);
+            A2dpService service = getService(source);
             if (service == null) {
                 return;
             }
@@ -1349,37 +1420,72 @@
         }
 
         @Override
-        public void disableOptionalCodecs(BluetoothDevice device) {
-            A2dpService service = getService();
+        public void disableOptionalCodecs(BluetoothDevice device, AttributionSource source) {
+            Attributable.setAttributionSource(device, source);
+            A2dpService service = getService(source);
             if (service == null) {
                 return;
             }
             service.disableOptionalCodecs(device);
         }
 
-        public int supportsOptionalCodecs(BluetoothDevice device) {
-            A2dpService service = getService();
+        @Override
+        public int supportsOptionalCodecs(BluetoothDevice device, AttributionSource source) {
+            Attributable.setAttributionSource(device, source);
+            A2dpService service = getService(source);
             if (service == null) {
                 return BluetoothA2dp.OPTIONAL_CODECS_SUPPORT_UNKNOWN;
             }
             return service.getSupportsOptionalCodecs(device);
         }
 
-        public int getOptionalCodecsEnabled(BluetoothDevice device) {
-            A2dpService service = getService();
+        @Override
+        public int getOptionalCodecsEnabled(BluetoothDevice device, AttributionSource source) {
+            Attributable.setAttributionSource(device, source);
+            A2dpService service = getService(source);
             if (service == null) {
                 return BluetoothA2dp.OPTIONAL_CODECS_PREF_UNKNOWN;
             }
             return service.getOptionalCodecsEnabled(device);
         }
 
-        public void setOptionalCodecsEnabled(BluetoothDevice device, int value) {
-            A2dpService service = getService();
+        @Override
+        public void setOptionalCodecsEnabled(BluetoothDevice device, int value,
+                AttributionSource source) {
+            Attributable.setAttributionSource(device, source);
+            A2dpService service = getService(source);
             if (service == null) {
                 return;
             }
             service.setOptionalCodecsEnabled(device, value);
         }
+
+        @Override
+        public int getDynamicBufferSupport(AttributionSource source) {
+            A2dpService service = getService(source);
+            if (service == null) {
+                return BluetoothA2dp.DYNAMIC_BUFFER_SUPPORT_NONE;
+            }
+            return service.getDynamicBufferSupport();
+        }
+
+        @Override
+        public BufferConstraints getBufferConstraints(AttributionSource source) {
+            A2dpService service = getService(source);
+            if (service == null) {
+                return null;
+            }
+            return service.getBufferConstraints();
+        }
+
+        @Override
+        public boolean setBufferLengthMillis(int codec, int value, AttributionSource source) {
+            A2dpService service = getService(source);
+            if (service == null) {
+                return false;
+            }
+            return service.setBufferLengthMillis(codec, value);
+        }
     }
 
     @Override
diff --git a/src/com/android/bluetooth/a2dp/A2dpStateMachine.java b/src/com/android/bluetooth/a2dp/A2dpStateMachine.java
index 946b9b4..a313bad 100644
--- a/src/com/android/bluetooth/a2dp/A2dpStateMachine.java
+++ b/src/com/android/bluetooth/a2dp/A2dpStateMachine.java
@@ -45,6 +45,9 @@
 
 package com.android.bluetooth.a2dp;
 
+import static android.Manifest.permission.BLUETOOTH_CONNECT;
+
+import android.annotation.RequiresPermission;
 import android.bluetooth.BluetoothA2dp;
 import android.bluetooth.BluetoothCodecConfig;
 import android.bluetooth.BluetoothCodecStatus;
@@ -55,7 +58,7 @@
 import android.os.Message;
 import android.util.Log;
 
-import com.android.bluetooth.BluetoothStatsLog;
+import com.android.bluetooth.Utils;
 import com.android.bluetooth.btservice.ProfileService;
 import com.android.bluetooth.statemachine.State;
 import com.android.bluetooth.statemachine.StateMachine;
@@ -682,19 +685,20 @@
         intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mDevice);
         intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT
                         | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
-        mA2dpService.sendBroadcast(intent, ProfileService.BLUETOOTH_PERM);
+        mA2dpService.sendBroadcast(intent, BLUETOOTH_CONNECT,
+                Utils.getTempAllowlistBroadcastOptions());
     }
 
     private void broadcastAudioState(int newState, int prevState) {
         log("A2DP Playing state : device: " + mDevice + " State:" + audioStateToString(prevState)
                 + "->" + audioStateToString(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);
         intent.putExtra(BluetoothProfile.EXTRA_STATE, newState);
         intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
-        mA2dpService.sendBroadcast(intent, A2dpService.BLUETOOTH_PERM);
+        mA2dpService.sendBroadcast(intent, BLUETOOTH_CONNECT,
+                Utils.getTempAllowlistBroadcastOptions());
     }
 
     @Override
diff --git a/src/com/android/bluetooth/a2dpsink/A2dpSinkService.java b/src/com/android/bluetooth/a2dpsink/A2dpSinkService.java
index 95b6cdd..c70ed46 100644
--- a/src/com/android/bluetooth/a2dpsink/A2dpSinkService.java
+++ b/src/com/android/bluetooth/a2dpsink/A2dpSinkService.java
@@ -15,24 +15,28 @@
  */
 package com.android.bluetooth.a2dpsink;
 
+import android.annotation.RequiresPermission;
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothAudioConfig;
 import android.bluetooth.BluetoothDevice;
 import android.bluetooth.BluetoothProfile;
 import android.bluetooth.IBluetoothA2dpSink;
+import android.content.Attributable;
+import android.content.AttributionSource;
 import android.media.AudioManager;
 import android.util.Log;
 
 import com.android.bluetooth.Utils;
 import com.android.bluetooth.btservice.AdapterService;
 import com.android.bluetooth.btservice.ProfileService;
+import com.android.bluetooth.btservice.storage.DatabaseManager;
 import com.android.internal.annotations.VisibleForTesting;
 
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
 import java.util.Map;
-import java.util.Set;
+import java.util.Objects;
 import java.util.concurrent.ConcurrentHashMap;
 
 /**
@@ -42,14 +46,18 @@
 public class A2dpSinkService extends ProfileService {
     private static final String TAG = "A2dpSinkService";
     private static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG);
-    static final int MAXIMUM_CONNECTED_DEVICES = 1;
+    private int mMaxConnectedAudioDevices;
 
-    private final BluetoothAdapter mAdapter;
+    private AdapterService mAdapterService;
+    private DatabaseManager mDatabaseManager;
     protected Map<BluetoothDevice, A2dpSinkStateMachine> mDeviceStateMap =
             new ConcurrentHashMap<>(1);
 
     private final Object mStreamHandlerLock = new Object();
 
+    private final Object mActiveDeviceLock = new Object();
+    private BluetoothDevice mActiveDevice = null;
+
     private A2dpSinkStreamHandler mA2dpSinkStreamHandler;
     private static A2dpSinkService sService;
 
@@ -59,10 +67,16 @@
 
     @Override
     protected boolean start() {
+        mAdapterService = Objects.requireNonNull(AdapterService.getAdapterService(),
+                "AdapterService cannot be null when A2dpSinkService starts");
+        mDatabaseManager = Objects.requireNonNull(AdapterService.getAdapterService().getDatabase(),
+                "DatabaseManager cannot be null when A2dpSinkService starts");
+
         synchronized (mStreamHandlerLock) {
             mA2dpSinkStreamHandler = new A2dpSinkStreamHandler(this, this);
         }
-        initNative();
+        mMaxConnectedAudioDevices = mAdapterService.getMaxConnectedAudioDevices();
+        initNative(mMaxConnectedAudioDevices);
         setA2dpSinkService(this);
         return true;
     }
@@ -98,8 +112,36 @@
     }
 
 
-    public A2dpSinkService() {
-        mAdapter = BluetoothAdapter.getDefaultAdapter();
+    public A2dpSinkService() {}
+
+    /**
+     * Set the device that should be allowed to actively stream
+     */
+    public boolean setActiveDevice(BluetoothDevice device) {
+        // Translate to byte address for JNI. Use an all 0 MAC for no active device
+        byte[] address = null;
+        if (device != null) {
+            address = Utils.getByteAddress(device);
+        } else {
+            address = Utils.getBytesFromAddress("00:00:00:00:00:00");
+        }
+
+        synchronized (mActiveDeviceLock) {
+            if (setActiveDeviceNative(address)) {
+                mActiveDevice = device;
+                return true;
+            }
+            return false;
+        }
+    }
+
+    /**
+     * Get the device that is allowed to be actively streaming
+     */
+    public BluetoothDevice getActiveDevice() {
+        synchronized (mActiveDeviceLock) {
+            return mActiveDevice;
+        }
     }
 
     /**
@@ -124,6 +166,7 @@
         }
     }
 
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED)
     boolean isA2dpPlaying(BluetoothDevice device) {
         enforceCallingOrSelfPermission(
                 BLUETOOTH_PRIVILEGED, "Need BLUETOOTH_PRIVILEGED permission");
@@ -143,16 +186,14 @@
             implements IProfileServiceBinder {
         private A2dpSinkService mService;
 
-        private A2dpSinkService getService() {
-            if (!Utils.checkCaller()) {
-                Log.w(TAG, "A2dp call not allowed for non-active user");
+        @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
+        private A2dpSinkService getService(AttributionSource source) {
+            if (!Utils.checkCallerIsSystemOrActiveUser(TAG)
+                    || !Utils.checkServiceAvailable(mService, TAG)
+                    || !Utils.checkConnectPermissionForDataDelivery(mService, source, TAG)) {
                 return null;
             }
-
-            if (mService != null) {
-                return mService;
-            }
-            return null;
+            return mService;
         }
 
         A2dpSinkServiceBinder(A2dpSinkService svc) {
@@ -165,8 +206,9 @@
         }
 
         @Override
-        public boolean connect(BluetoothDevice device) {
-            A2dpSinkService service = getService();
+        public boolean connect(BluetoothDevice device, AttributionSource source) {
+            Attributable.setAttributionSource(device, source);
+            A2dpSinkService service = getService(source);
             if (service == null) {
                 return false;
             }
@@ -174,8 +216,9 @@
         }
 
         @Override
-        public boolean disconnect(BluetoothDevice device) {
-            A2dpSinkService service = getService();
+        public boolean disconnect(BluetoothDevice device, AttributionSource source) {
+            Attributable.setAttributionSource(device, source);
+            A2dpSinkService service = getService(source);
             if (service == null) {
                 return false;
             }
@@ -183,8 +226,8 @@
         }
 
         @Override
-        public List<BluetoothDevice> getConnectedDevices() {
-            A2dpSinkService service = getService();
+        public List<BluetoothDevice> getConnectedDevices(AttributionSource source) {
+            A2dpSinkService service = getService(source);
             if (service == null) {
                 return new ArrayList<BluetoothDevice>(0);
             }
@@ -192,8 +235,9 @@
         }
 
         @Override
-        public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
-            A2dpSinkService service = getService();
+        public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states,
+                AttributionSource source) {
+            A2dpSinkService service = getService(source);
             if (service == null) {
                 return new ArrayList<BluetoothDevice>(0);
             }
@@ -201,8 +245,9 @@
         }
 
         @Override
-        public int getConnectionState(BluetoothDevice device) {
-            A2dpSinkService service = getService();
+        public int getConnectionState(BluetoothDevice device, AttributionSource source) {
+            Attributable.setAttributionSource(device, source);
+            A2dpSinkService service = getService(source);
             if (service == null) {
                 return BluetoothProfile.STATE_DISCONNECTED;
             }
@@ -210,8 +255,10 @@
         }
 
         @Override
-        public boolean setConnectionPolicy(BluetoothDevice device, int connectionPolicy) {
-            A2dpSinkService service = getService();
+        public boolean setConnectionPolicy(BluetoothDevice device, int connectionPolicy,
+                AttributionSource source) {
+            Attributable.setAttributionSource(device, source);
+            A2dpSinkService service = getService(source);
             if (service == null) {
                 return false;
             }
@@ -219,8 +266,9 @@
         }
 
         @Override
-        public int getConnectionPolicy(BluetoothDevice device) {
-            A2dpSinkService service = getService();
+        public int getConnectionPolicy(BluetoothDevice device, AttributionSource source) {
+            Attributable.setAttributionSource(device, source);
+            A2dpSinkService service = getService(source);
             if (service == null) {
                 return BluetoothProfile.CONNECTION_POLICY_UNKNOWN;
             }
@@ -228,8 +276,9 @@
         }
 
         @Override
-        public boolean isA2dpPlaying(BluetoothDevice device) {
-            A2dpSinkService service = getService();
+        public boolean isA2dpPlaying(BluetoothDevice device, AttributionSource source) {
+            Attributable.setAttributionSource(device, source);
+            A2dpSinkService service = getService(source);
             if (service == null) {
                 return false;
             }
@@ -237,8 +286,10 @@
         }
 
         @Override
-        public BluetoothAudioConfig getAudioConfig(BluetoothDevice device) {
-            A2dpSinkService service = getService();
+        public BluetoothAudioConfig getAudioConfig(BluetoothDevice device,
+                AttributionSource source) {
+            Attributable.setAttributionSource(device, source);
+            A2dpSinkService service = getService(source);
             if (service == null) {
                 return null;
             }
@@ -253,6 +304,7 @@
      *
      * @return true if connection is successful, false otherwise.
      */
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED)
     public boolean connect(BluetoothDevice device) {
         enforceCallingOrSelfPermission(BLUETOOTH_PRIVILEGED,
                 "Need BLUETOOTH_PRIVILEGED permission");
@@ -289,7 +341,6 @@
      * @return true if disconnect is successful, false otherwise.
      */
     public boolean disconnect(BluetoothDevice device) {
-        enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH ADMIN permission");
         if (DBG) {
             StringBuilder sb = new StringBuilder();
             dump(sb);
@@ -337,7 +388,7 @@
     List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
         if (DBG) Log.d(TAG, "getDevicesMatchingConnectionStates" + Arrays.toString(states));
         List<BluetoothDevice> deviceList = new ArrayList<>();
-        Set<BluetoothDevice> bondedDevices = mAdapter.getBondedDevices();
+        BluetoothDevice[] bondedDevices = mAdapterService.getBondedDevices();
         int connectionState;
         for (BluetoothDevice device : bondedDevices) {
             connectionState = getConnectionState(device);
@@ -383,14 +434,18 @@
      * @param connectionPolicy is the connection policy to set to for this profile
      * @return true if connectionPolicy is set, false on error
      */
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED)
     public boolean setConnectionPolicy(BluetoothDevice device, int connectionPolicy) {
         enforceCallingOrSelfPermission(
                 BLUETOOTH_PRIVILEGED, "Need BLUETOOTH_PRIVILEGED permission");
         if (DBG) {
             Log.d(TAG, "Saved connectionPolicy " + device + " = " + connectionPolicy);
         }
-        AdapterService.getAdapterService().getDatabase()
-                .setProfileConnectionPolicy(device, BluetoothProfile.A2DP_SINK, connectionPolicy);
+
+        if (!mDatabaseManager.setProfileConnectionPolicy(device, BluetoothProfile.A2DP_SINK,
+                  connectionPolicy)) {
+            return false;
+        }
         if (connectionPolicy == BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
             connect(device);
         } else if (connectionPolicy == BluetoothProfile.CONNECTION_POLICY_FORBIDDEN) {
@@ -405,10 +460,11 @@
      * @param device the remote device
      * @return connection policy of the specified device
      */
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED)
     public int getConnectionPolicy(BluetoothDevice device) {
         enforceCallingOrSelfPermission(
                 BLUETOOTH_PRIVILEGED, "Need BLUETOOTH_PRIVILEGED permission");
-        return AdapterService.getAdapterService().getDatabase()
+        return mDatabaseManager
                 .getProfileConnectionPolicy(device, BluetoothProfile.A2DP_SINK);
     }
 
@@ -416,6 +472,8 @@
     @Override
     public void dump(StringBuilder sb) {
         super.dump(sb);
+        ProfileService.println(sb, "Active Device = " + getActiveDevice());
+        ProfileService.println(sb, "Max Connected Devices = " + mMaxConnectedAudioDevices);
         ProfileService.println(sb, "Devices Tracked = " + mDeviceStateMap.size());
         for (A2dpSinkStateMachine stateMachine : mDeviceStateMap.values()) {
             ProfileService.println(sb,
@@ -437,7 +495,7 @@
 
     private static native void classInitNative();
 
-    private native void initNative();
+    private native void initNative(int maxConnectedAudioDevices);
 
     private native void cleanupNative();
 
@@ -473,7 +531,7 @@
     public native void informAudioTrackGainNative(float gain);
 
     private void onConnectionStateChanged(byte[] address, int state) {
-        StackEvent event = StackEvent.connectionStateChanged(getDevice(address), state);
+        StackEvent event = StackEvent.connectionStateChanged(getAnonymousDevice(address), state);
         A2dpSinkStateMachine stateMachine = getOrCreateStateMachine(event.mDevice);
         stateMachine.sendMessage(A2dpSinkStateMachine.STACK_EVENT, event);
     }
@@ -495,7 +553,7 @@
     }
 
     private void onAudioConfigChanged(byte[] address, int sampleRate, int channelCount) {
-        StackEvent event = StackEvent.audioConfigChanged(getDevice(address), sampleRate,
+        StackEvent event = StackEvent.audioConfigChanged(getAnonymousDevice(address), sampleRate,
                 channelCount);
         A2dpSinkStateMachine stateMachine = getOrCreateStateMachine(event.mDevice);
         stateMachine.sendMessage(A2dpSinkStateMachine.STACK_EVENT, event);
diff --git a/src/com/android/bluetooth/a2dpsink/A2dpSinkStateMachine.java b/src/com/android/bluetooth/a2dpsink/A2dpSinkStateMachine.java
index 618546e..cfc4bfc 100644
--- a/src/com/android/bluetooth/a2dpsink/A2dpSinkStateMachine.java
+++ b/src/com/android/bluetooth/a2dpsink/A2dpSinkStateMachine.java
@@ -15,6 +15,9 @@
  */
 package com.android.bluetooth.a2dpsink;
 
+import static android.Manifest.permission.BLUETOOTH_CONNECT;
+
+import android.annotation.RequiresPermission;
 import android.bluetooth.BluetoothA2dpSink;
 import android.bluetooth.BluetoothAudioConfig;
 import android.bluetooth.BluetoothDevice;
@@ -129,7 +132,7 @@
      */
     public void dump(StringBuilder sb) {
         ProfileService.println(sb, "mDevice: " + mDevice.getAddress() + "("
-                + mDevice.getName() + ") " + this.toString());
+                + Utils.getName(mDevice) + ") " + this.toString());
     }
 
     @Override
@@ -164,6 +167,7 @@
             return false;
         }
 
+        @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED)
         void processStackEvent(StackEvent event) {
             switch (event.mType) {
                 case StackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED:
@@ -307,6 +311,6 @@
         intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mDevice);
         intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
         mMostRecentState = currentState;
-        mService.sendBroadcast(intent, ProfileService.BLUETOOTH_PERM);
+        mService.sendBroadcast(intent, BLUETOOTH_CONNECT, Utils.getTempAllowlistBroadcastOptions());
     }
 }
diff --git a/src/com/android/bluetooth/a2dpsink/A2dpSinkStreamHandler.java b/src/com/android/bluetooth/a2dpsink/A2dpSinkStreamHandler.java
index 658feff..a6ce1f3 100644
--- a/src/com/android/bluetooth/a2dpsink/A2dpSinkStreamHandler.java
+++ b/src/com/android/bluetooth/a2dpsink/A2dpSinkStreamHandler.java
@@ -16,6 +16,7 @@
 
 package com.android.bluetooth.a2dpsink;
 
+import android.annotation.RequiresPermission;
 import android.bluetooth.BluetoothDevice;
 import android.bluetooth.BluetoothHeadsetClientCall;
 import android.content.Context;
diff --git a/src/com/android/bluetooth/avrcp/AvrcpEventLogger.java b/src/com/android/bluetooth/audio_util/BTAudioEventLogger.java
similarity index 75%
rename from src/com/android/bluetooth/avrcp/AvrcpEventLogger.java
rename to src/com/android/bluetooth/audio_util/BTAudioEventLogger.java
index b2cf805..8bb64bc 100644
--- a/src/com/android/bluetooth/avrcp/AvrcpEventLogger.java
+++ b/src/com/android/bluetooth/audio_util/BTAudioEventLogger.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.bluetooth.avrcp;
+package com.android.bluetooth.audio_util;
 
 import android.util.Log;
 
@@ -23,8 +23,8 @@
 import com.google.common.collect.EvictingQueue;
 
 
-// This class is to store logs for Avrcp for given size.
-public class AvrcpEventLogger {
+// This class is to store logs for Audio for given size.
+public class BTAudioEventLogger {
     private final String mTitle;
     private final EvictingQueue<Event> mEvents;
 
@@ -44,34 +44,34 @@
         }
     }
 
-    AvrcpEventLogger(int size, String title) {
+    public BTAudioEventLogger(int size, String title) {
         mEvents = EvictingQueue.create(size);
         mTitle = title;
     }
 
-    synchronized void add(String msg) {
+    public synchronized void add(String msg) {
         Event event = new Event(msg);
         mEvents.add(event);
     }
 
-    synchronized void logv(String tag, String msg) {
+    public synchronized void logv(String tag, String msg) {
         add(msg);
         Log.v(tag, msg);
     }
 
-    synchronized void logd(String tag, String msg) {
+    public synchronized void logd(String tag, String msg) {
         logd(true, tag, msg);
     }
 
-    synchronized void logd(boolean debug, String tag, String msg) {
+    public synchronized void logd(boolean debug, String tag, String msg) {
         add(msg);
         if (debug) {
             Log.d(tag, msg);
         }
     }
 
-    synchronized void dump(StringBuilder sb) {
-        sb.append("Avrcp ").append(mTitle).append(":\n");
+    public synchronized void dump(StringBuilder sb) {
+        sb.append("BTAudio ").append(mTitle).append(":\n");
         for (Event event : mEvents) {
             sb.append("  ").append(event.toString()).append("\n");
         }
diff --git a/src/com/android/bluetooth/avrcp/BrowsablePlayerConnector.java b/src/com/android/bluetooth/audio_util/BrowsablePlayerConnector.java
similarity index 99%
rename from src/com/android/bluetooth/avrcp/BrowsablePlayerConnector.java
rename to src/com/android/bluetooth/audio_util/BrowsablePlayerConnector.java
index e009f28..08f6230 100644
--- a/src/com/android/bluetooth/avrcp/BrowsablePlayerConnector.java
+++ b/src/com/android/bluetooth/audio_util/BrowsablePlayerConnector.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.bluetooth.avrcp;
+package com.android.bluetooth.audio_util;
 
 import android.content.Context;
 import android.content.pm.ResolveInfo;
diff --git a/src/com/android/bluetooth/avrcp/BrowsedPlayerWrapper.java b/src/com/android/bluetooth/audio_util/BrowsedPlayerWrapper.java
similarity index 99%
rename from src/com/android/bluetooth/avrcp/BrowsedPlayerWrapper.java
rename to src/com/android/bluetooth/audio_util/BrowsedPlayerWrapper.java
index 6ffdac5..57e87f9 100644
--- a/src/com/android/bluetooth/avrcp/BrowsedPlayerWrapper.java
+++ b/src/com/android/bluetooth/audio_util/BrowsedPlayerWrapper.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.bluetooth.avrcp;
+package com.android.bluetooth.audio_util;
 
 import android.annotation.Nullable;
 import android.content.ComponentName;
@@ -429,7 +429,7 @@
                     Folder f = new Folder(item.getMediaId(), false, title);
                     return_list.add(new ListItem(f));
                 } else {
-                    return_list.add(new ListItem(Util.toMetadata(item)));
+                    return_list.add(new ListItem(Util.toMetadata(mContext, item)));
                 }
             }
 
diff --git a/src/com/android/bluetooth/avrcp/GPMWrapper.java b/src/com/android/bluetooth/audio_util/GPMWrapper.java
similarity index 86%
rename from src/com/android/bluetooth/avrcp/GPMWrapper.java
rename to src/com/android/bluetooth/audio_util/GPMWrapper.java
index ea9875d..102c62f 100644
--- a/src/com/android/bluetooth/avrcp/GPMWrapper.java
+++ b/src/com/android/bluetooth/audio_util/GPMWrapper.java
@@ -14,8 +14,9 @@
  * limitations under the License.
  */
 
-package com.android.bluetooth.avrcp;
+package com.android.bluetooth.audio_util;
 
+import android.content.Context;
 import android.media.session.MediaSession;
 import android.os.Looper;
 import android.util.Log;
@@ -29,8 +30,8 @@
     private static final String TAG = "AvrcpGPMWrapper";
     private static final boolean DEBUG = true;
 
-    GPMWrapper(MediaController controller, Looper looper) {
-        super(controller, looper);
+    GPMWrapper(Context context, MediaController controller, Looper looper) {
+        super(context, controller, looper);
     }
 
     @Override
@@ -50,8 +51,8 @@
         }
 
         // Check if current playing song in Queue matches current Metadata
-        Metadata qitem = Util.toMetadata(currItem);
-        Metadata mdata = Util.toMetadata(getMetadata());
+        Metadata qitem = Util.toMetadata(mContext, currItem);
+        Metadata mdata = Util.toMetadata(mContext, getMetadata());
         if (currItem == null || !qitem.equals(mdata)) {
             if (DEBUG) {
                 Log.d(TAG, "Metadata currently out of sync for Google Play Music");
diff --git a/src/com/android/bluetooth/avrcp/MediaPlayerList.java b/src/com/android/bluetooth/audio_util/MediaPlayerList.java
similarity index 91%
rename from src/com/android/bluetooth/avrcp/MediaPlayerList.java
rename to src/com/android/bluetooth/audio_util/MediaPlayerList.java
index 4d9b413..cdb85d2 100644
--- a/src/com/android/bluetooth/avrcp/MediaPlayerList.java
+++ b/src/com/android/bluetooth/audio_util/MediaPlayerList.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.bluetooth.avrcp;
+package com.android.bluetooth.audio_util;
 
 import android.annotation.NonNull;
 import android.content.BroadcastReceiver;
@@ -61,7 +61,7 @@
  * player would effectively cause player switch by sending a play command to that player.
  */
 public class MediaPlayerList {
-    private static final String TAG = "AvrcpMediaPlayerList";
+    private static final String TAG = "MediaPlayerList";
     private static final boolean DEBUG = true;
     static boolean sTesting = false;
 
@@ -89,10 +89,11 @@
     private MediaSessionManager mMediaSessionManager;
     private MediaData mCurrMediaData = null;
     private final AudioManager mAudioManager;
-    private final AvrcpEventLogger mActivePlayerLogger = new AvrcpEventLogger(
-            ACTIVE_PLAYER_LOGGER_SIZE, ACTIVE_PLAYER_LOGGER_TITLE);
-    private final AvrcpEventLogger mAudioPlaybackStateLogger = new AvrcpEventLogger(
-            AUDIO_PLAYBACK_STATE_LOGGER_SIZE, AUDIO_PLAYBACK_STATE_LOGGER_TITLE);
+
+    private final BTAudioEventLogger mActivePlayerLogger = new BTAudioEventLogger(
+        ACTIVE_PLAYER_LOGGER_SIZE, ACTIVE_PLAYER_LOGGER_TITLE);
+    private final BTAudioEventLogger mAudioPlaybackStateLogger = new BTAudioEventLogger(
+        AUDIO_PLAYBACK_STATE_LOGGER_SIZE, AUDIO_PLAYBACK_STATE_LOGGER_TITLE);
 
     private Map<Integer, MediaPlayerWrapper> mMediaPlayers =
             Collections.synchronizedMap(new HashMap<Integer, MediaPlayerWrapper>());
@@ -102,29 +103,25 @@
             Collections.synchronizedMap(new HashMap<Integer, BrowsedPlayerWrapper>());
     private int mActivePlayerId = NO_ACTIVE_PLAYER;
 
-    @VisibleForTesting
+    private MediaUpdateCallback mCallback;
     private boolean mAudioPlaybackIsActive = false;
 
-    private AvrcpTargetService.ListCallback mCallback;
     private BrowsablePlayerConnector mBrowsablePlayerConnector;
 
-    interface MediaUpdateCallback {
+    public interface MediaUpdateCallback {
         void run(MediaData data);
-    }
-
-    interface GetPlayerRootCallback {
-        void run(int playerId, boolean success, String rootId, int numItems);
-    }
-
-    interface GetFolderItemsCallback {
-        void run(String parentId, List<ListItem> items);
-    }
-
-    interface FolderUpdateCallback {
         void run(boolean availablePlayers, boolean addressedPlayers, boolean uids);
     }
 
-    MediaPlayerList(Looper looper, Context context) {
+    public interface GetPlayerRootCallback {
+        void run(int playerId, boolean success, String rootId, int numItems);
+    }
+
+    public interface GetFolderItemsCallback {
+        void run(String parentId, List<ListItem> items);
+    }
+
+    public MediaPlayerList(Looper looper, Context context) {
         Log.v(TAG, "Creating MediaPlayerList");
 
         mLooper = looper;
@@ -150,7 +147,7 @@
                 mContext.getMainExecutor(), mMediaKeyEventSessionChangedListener);
     }
 
-    void init(AvrcpTargetService.ListCallback callback) {
+    public void init(MediaUpdateCallback callback) {
         Log.v(TAG, "Initializing MediaPlayerList");
         mCallback = callback;
 
@@ -204,7 +201,7 @@
             });
     }
 
-    void cleanup() {
+    public void cleanup() {
         mContext.unregisterReceiver(mPackageChangedBroadcastReceiver);
 
         mActivePlayerId = NO_ACTIVE_PLAYER;
@@ -232,7 +229,7 @@
         mBrowsablePlayers.clear();
     }
 
-    int getCurrentPlayerId() {
+    public int getCurrentPlayerId() {
         return BLUETOOTH_PLAYER_ID;
     }
 
@@ -244,18 +241,34 @@
         return id;
     }
 
-    MediaPlayerWrapper getActivePlayer() {
+    public MediaPlayerWrapper getActivePlayer() {
         return mMediaPlayers.get(mActivePlayerId);
     }
 
     // In this case the displayed player is the Bluetooth Player, the number of items is equal
     // to the number of players. The root ID will always be empty string in this case as well.
-    void getPlayerRoot(int playerId, GetPlayerRootCallback cb) {
+    public void getPlayerRoot(int playerId, GetPlayerRootCallback cb) {
+        /** M: Fix PTS AVRCP/TG/MCN/CB/BI-02-C fail @{ */
+        if (Utils.isPtsTestMode()) {
+            d("PTS test mode: getPlayerRoot");
+            BrowsedPlayerWrapper wrapper = mBrowsablePlayers.get(BLUETOOTH_PLAYER_ID + 1);
+            String itemId = wrapper.getRootId();
+
+            wrapper.getFolderItems(itemId, (status, id, results) -> {
+                if (status != BrowsedPlayerWrapper.STATUS_SUCCESS) {
+                    cb.run(playerId, playerId == BLUETOOTH_PLAYER_ID, "", 0);
+                    return;
+                }
+                cb.run(playerId, playerId == BLUETOOTH_PLAYER_ID, "", results.size());
+            });
+            return;
+        }
+        /** @} */
         cb.run(playerId, playerId == BLUETOOTH_PLAYER_ID, "", mBrowsablePlayers.size());
     }
 
     // Return the "Bluetooth Player" as the only player always
-    List<PlayerInfo> getMediaPlayerList() {
+    public List<PlayerInfo> getMediaPlayerList() {
         PlayerInfo info = new PlayerInfo();
         info.id = BLUETOOTH_PLAYER_ID;
         info.name = BLUETOOTH_PLAYER_NAME;
@@ -271,7 +284,7 @@
     }
 
     @NonNull
-    String getCurrentMediaId() {
+    public String getCurrentMediaId() {
         final MediaPlayerWrapper player = getActivePlayer();
         if (player == null) return "";
 
@@ -292,14 +305,14 @@
     }
 
     @NonNull
-    Metadata getCurrentSongInfo() {
+    public Metadata getCurrentSongInfo() {
         final MediaPlayerWrapper player = getActivePlayer();
         if (player == null) return Util.empty_data();
 
         return player.getCurrentMetadata();
     }
 
-    PlaybackState getCurrentPlayStatus() {
+    public PlaybackState getCurrentPlayStatus() {
         final MediaPlayerWrapper player = getActivePlayer();
         if (player == null) return null;
 
@@ -316,7 +329,7 @@
     }
 
     @NonNull
-    List<Metadata> getNowPlayingList() {
+    public List<Metadata> getNowPlayingList() {
         // Only send the current song for the now playing if there is no active song. See
         // |getCurrentMediaId()| for reasons why there might be no active song.
         if (getCurrentMediaId().equals("")) {
@@ -330,7 +343,7 @@
         return getActivePlayer().getCurrentQueue();
     }
 
-    void playItem(int playerId, boolean nowPlaying, String mediaId) {
+    public void playItem(int playerId, boolean nowPlaying, String mediaId) {
         if (nowPlaying) {
             playNowPlayingItem(mediaId);
         } else {
@@ -391,10 +404,29 @@
         return;
     }
 
-    void getFolderItems(int playerId, String mediaId, GetFolderItemsCallback cb) {
+    public void getFolderItems(int playerId, String mediaId, GetFolderItemsCallback cb) {
         // The playerId is unused since we always assume the remote device is using the
         // Bluetooth Player.
         d("getFolderItems(): playerId=" + playerId + ", mediaId=" + mediaId);
+        /** M: Fix PTS AVRCP/TG/MCN/CB/BI-02-C fail @{ */
+        if (Utils.isPtsTestMode()) {
+            d("PTS test mode: getFolderItems");
+            BrowsedPlayerWrapper wrapper = mBrowsablePlayers.get(BLUETOOTH_PLAYER_ID + 1);
+            String itemId = mediaId;
+            if (mediaId.equals("")) {
+                itemId = wrapper.getRootId();
+            }
+
+            wrapper.getFolderItems(itemId, (status, id, results) -> {
+                if (status != BrowsedPlayerWrapper.STATUS_SUCCESS) {
+                    cb.run(mediaId, new ArrayList<ListItem>());
+                    return;
+                }
+                cb.run(mediaId, results);
+            });
+            return;
+        }
+        /** @} */
 
         // The device is requesting the content of the root folder. This folder contains a list of
         // Browsable Media Players displayed as folders with their contents contained within.
@@ -471,6 +503,7 @@
         }
 
         MediaPlayerWrapper newPlayer = MediaPlayerWrapperFactory.wrap(
+                mContext,
                 controller,
                 mLooper);
 
@@ -581,11 +614,11 @@
     }
 
     // TODO (apanicke): Add logging for media key events in dumpsys
-    void sendMediaKeyEvent(int key, boolean pushed) {
+    public void sendMediaKeyEvent(int key, boolean pushed) {
         d("sendMediaKeyEvent: key=" + key + " pushed=" + pushed);
         int action = pushed ? KeyEvent.ACTION_DOWN : KeyEvent.ACTION_UP;
         KeyEvent event = new KeyEvent(action, AvrcpPassthrough.toKeyCode(key));
-        mMediaSessionManager.dispatchMediaKeyEvent(event);
+        mAudioManager.dispatchMediaKeyEvent(event);
     }
 
     private void sendFolderUpdate(boolean availablePlayers, boolean addressedPlayers,
@@ -823,7 +856,7 @@
             };
 
 
-    void dump(StringBuilder sb) {
+    public void dump(StringBuilder sb) {
         sb.append("List of MediaControllers: size=" + mMediaPlayers.size() + "\n");
         for (int id : mMediaPlayers.keySet()) {
             if (id == mActivePlayerId) {
diff --git a/src/com/android/bluetooth/avrcp/MediaPlayerWrapper.java b/src/com/android/bluetooth/audio_util/MediaPlayerWrapper.java
similarity index 84%
rename from src/com/android/bluetooth/avrcp/MediaPlayerWrapper.java
rename to src/com/android/bluetooth/audio_util/MediaPlayerWrapper.java
index c9a2b11..463c5fc 100644
--- a/src/com/android/bluetooth/avrcp/MediaPlayerWrapper.java
+++ b/src/com/android/bluetooth/audio_util/MediaPlayerWrapper.java
@@ -14,9 +14,10 @@
  * limitations under the License.
  */
 
-package com.android.bluetooth.avrcp;
+package com.android.bluetooth.audio_util;
 
 import android.annotation.Nullable;
+import android.content.Context;
 import android.media.MediaMetadata;
 import android.media.session.MediaSession;
 import android.media.session.PlaybackState;
@@ -38,18 +39,19 @@
  * TODO (apanicke): Once MediaPlayer2 is supported better, replace this class
  * with that.
  */
-class MediaPlayerWrapper {
-    private static final String TAG = "AvrcpMediaPlayerWrapper";
+public class MediaPlayerWrapper {
+    private static final String TAG = "AudioMediaPlayerWrapper";
     private static final boolean DEBUG = false;
     static boolean sTesting = false;
     private static final int PLAYBACK_STATE_CHANGE_EVENT_LOGGER_SIZE = 5;
     private static final String PLAYBACK_STATE_CHANGE_LOGGER_EVENT_TITLE =
             "Playback State change Event";
 
+    final Context mContext;
     private MediaController mMediaController;
     private String mPackageName;
     private Looper mLooper;
-    private final AvrcpEventLogger mPlaybackStateChangeEventLogger;
+    private final BTAudioEventLogger mPlaybackStateChangeEventLogger;
 
     private MediaData mCurrentData;
 
@@ -81,16 +83,17 @@
         return true;
     }
 
-    MediaPlayerWrapper(MediaController controller, Looper looper) {
+    MediaPlayerWrapper(Context context, MediaController controller, Looper looper) {
+        mContext = context;
         mMediaController = controller;
         mPackageName = controller.getPackageName();
         mLooper = looper;
-        mPlaybackStateChangeEventLogger = new AvrcpEventLogger(
+        mPlaybackStateChangeEventLogger = new BTAudioEventLogger(
                 PLAYBACK_STATE_CHANGE_EVENT_LOGGER_SIZE, PLAYBACK_STATE_CHANGE_LOGGER_EVENT_TITLE);
 
         mCurrentData = new MediaData(null, null, null);
-        mCurrentData.queue = Util.toMetadataList(getQueue());
-        mCurrentData.metadata = Util.toMetadata(getMetadata());
+        mCurrentData.queue = Util.toMetadataList(mContext, getQueue());
+        mCurrentData.metadata = Util.toMetadata(mContext, getMetadata());
         mCurrentData.state = getPlaybackState();
     }
 
@@ -101,7 +104,7 @@
         mLooper = null;
     }
 
-    String getPackageName() {
+    public String getPackageName() {
         return mPackageName;
     }
 
@@ -114,10 +117,10 @@
     }
 
     Metadata getCurrentMetadata() {
-        return Util.toMetadata(getMetadata());
+        return Util.toMetadata(mContext, getMetadata());
     }
 
-    PlaybackState getPlaybackState() {
+    public PlaybackState getPlaybackState() {
         return mMediaController.getPlaybackState();
     }
 
@@ -152,13 +155,51 @@
         controller.skipToQueueItem(qid);
     }
 
+    public void playCurrent() {
+        MediaController.TransportControls controller = mMediaController.getTransportControls();
+        controller.play();
+    }
+
+    public void stopCurrent() {
+        MediaController.TransportControls controller = mMediaController.getTransportControls();
+        controller.stop();
+    }
+
+    public void pauseCurrent() {
+        MediaController.TransportControls controller = mMediaController.getTransportControls();
+        controller.pause();
+    }
+
+    public void seekTo(long position) {
+        MediaController.TransportControls controller = mMediaController.getTransportControls();
+        controller.seekTo(position);
+    }
+
+    public void skipToPrevious() {
+        MediaController.TransportControls controller = mMediaController.getTransportControls();
+        controller.skipToPrevious();
+    }
+
+    public void skipToNext() {
+        MediaController.TransportControls controller = mMediaController.getTransportControls();
+        controller.skipToNext();
+    }
+
     // TODO (apanicke): Implement shuffle and repeat support. Right now these use custom actions
     // and it may only be possible to do this with Google Play Music
-    boolean isShuffleSupported() {
+    public boolean isShuffleSupported() {
         return false;
     }
 
-    boolean isRepeatSupported() {
+    public boolean isRepeatSupported() {
+        return false;
+    }
+
+    public boolean isShuffleSet() {
+        return false;
+    }
+
+    public boolean isRepeatSet() {
         return false;
     }
 
@@ -174,11 +215,12 @@
      * Return whether the queue, metadata, and queueID are all in sync.
      */
     boolean isMetadataSynced() {
-        if (getQueue() != null && getActiveQueueID() != -1) {
+        List<MediaSession.QueueItem> queue = getQueue();
+        if (queue != null && getActiveQueueID() != -1) {
             // Check if currentPlayingQueueId is in the current Queue
             MediaSession.QueueItem currItem = null;
 
-            for (MediaSession.QueueItem item : getQueue()) {
+            for (MediaSession.QueueItem item : queue) {
                 if (item.getQueueId()
                         == getActiveQueueID()) { // The item exists in the current queue
                     currItem = item;
@@ -187,8 +229,8 @@
             }
 
             // Check if current playing song in Queue matches current Metadata
-            Metadata qitem = Util.toMetadata(currItem);
-            Metadata mdata = Util.toMetadata(getMetadata());
+            Metadata qitem = Util.toMetadata(mContext, currItem);
+            Metadata mdata = Util.toMetadata(mContext, getMetadata());
             if (currItem == null || !qitem.equals(mdata)) {
                 if (DEBUG) {
                     Log.d(TAG, "Metadata currently out of sync for " + mPackageName);
@@ -219,9 +261,9 @@
         // Update the current data since it could have changed while we weren't registered for
         // updates
         mCurrentData = new MediaData(
-                Util.toMetadata(getMetadata()),
+                Util.toMetadata(mContext, getMetadata()),
                 getPlaybackState(),
-                Util.toMetadataList(getQueue()));
+                Util.toMetadataList(mContext, getQueue()));
 
         mControllerCallbacks = new MediaControllerListener(mMediaController, mLooper);
     }
@@ -256,9 +298,9 @@
 
         // Update the current data since it could be different on the new controller for the player
         mCurrentData = new MediaData(
-                Util.toMetadata(getMetadata()),
+                Util.toMetadata(mContext, getMetadata()),
                 getPlaybackState(),
-                Util.toMetadataList(getQueue()));
+                Util.toMetadataList(mContext, getQueue()));
 
         mControllerCallbacks = new MediaControllerListener(mMediaController, mLooper);
         d("Controller for " + mPackageName + " was updated.");
@@ -266,9 +308,9 @@
 
     private void sendMediaUpdate() {
         MediaData newData = new MediaData(
-                Util.toMetadata(getMetadata()),
+                Util.toMetadata(mContext, getMetadata()),
                 getPlaybackState(),
-                Util.toMetadataList(getQueue()));
+                Util.toMetadataList(mContext, getQueue()));
 
         if (newData.equals(mCurrentData)) {
             // This may happen if the controller is fully synced by the time the
@@ -307,9 +349,9 @@
             }
 
             Log.e(TAG, "Timeout while waiting for metadata to sync for " + mPackageName);
-            Log.e(TAG, "  └ Current Metadata: " +  Util.toMetadata(getMetadata()));
+            Log.e(TAG, "  └ Current Metadata: " +  Util.toMetadata(mContext, getMetadata()));
             Log.e(TAG, "  └ Current Playstate: " + getPlaybackState());
-            List<Metadata> current_queue = Util.toMetadataList(getQueue());
+            List<Metadata> current_queue = Util.toMetadataList(mContext, getQueue());
             for (int i = 0; i < current_queue.size(); i++) {
                 Log.e(TAG, "  └ QueueItem(" + i + "): " + current_queue.get(i));
             }
@@ -373,7 +415,7 @@
 
             if (DEBUG) {
                 Log.v(TAG, "onMetadataChanged(): " + mPackageName + " : "
-                    + Util.toMetadata(metadata));
+                        + Util.toMetadata(mContext, metadata));
             }
 
             if (!Objects.equals(metadata, getMetadata())) {
@@ -441,7 +483,7 @@
                 e("The callback queue isn't the current queue");
             }
 
-            List<Metadata> current_queue = Util.toMetadataList(queue);
+            List<Metadata> current_queue = Util.toMetadataList(mContext, queue);
             if (current_queue.equals(mCurrentData.queue)) {
                 Log.w(TAG, "onQueueChanged(): " + mPackageName
                         + " tried to update with no new data");
@@ -478,7 +520,7 @@
      * in milliseconds.
      */
     private static final long PLAYSTATE_BOUNCE_IGNORE_PERIOD = 500;
-    static boolean playstateEquals(PlaybackState a, PlaybackState b) {
+    public static boolean playstateEquals(PlaybackState a, PlaybackState b) {
         if (a == b) return true;
 
         if (a != null && b != null
diff --git a/src/com/android/bluetooth/avrcp/helpers/AvrcpPassthrough.java b/src/com/android/bluetooth/audio_util/helpers/AvrcpPassthrough.java
similarity index 99%
rename from src/com/android/bluetooth/avrcp/helpers/AvrcpPassthrough.java
rename to src/com/android/bluetooth/audio_util/helpers/AvrcpPassthrough.java
index 59ca419..afdae11 100644
--- a/src/com/android/bluetooth/avrcp/helpers/AvrcpPassthrough.java
+++ b/src/com/android/bluetooth/audio_util/helpers/AvrcpPassthrough.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.bluetooth.avrcp;
+package com.android.bluetooth.audio_util;
 
 import android.bluetooth.BluetoothAvrcp;
 import android.view.KeyEvent;
diff --git a/src/com/android/bluetooth/avrcp/helpers/Folder.java b/src/com/android/bluetooth/audio_util/helpers/Folder.java
similarity index 95%
rename from src/com/android/bluetooth/avrcp/helpers/Folder.java
rename to src/com/android/bluetooth/audio_util/helpers/Folder.java
index 7786cf3..ac8b33e 100644
--- a/src/com/android/bluetooth/avrcp/helpers/Folder.java
+++ b/src/com/android/bluetooth/audio_util/helpers/Folder.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.bluetooth.avrcp;
+package com.android.bluetooth.audio_util;
 
 class Folder implements Cloneable {
     public String mediaId;
diff --git a/src/com/android/bluetooth/audio_util/helpers/Image.java b/src/com/android/bluetooth/audio_util/helpers/Image.java
new file mode 100644
index 0000000..c0623c6
--- /dev/null
+++ b/src/com/android/bluetooth/audio_util/helpers/Image.java
@@ -0,0 +1,259 @@
+/*
+ * Copyright (C) 2020 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.audio_util;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.media.MediaDescription;
+import android.media.MediaMetadata;
+import android.net.Uri;
+import android.os.Bundle;
+import android.util.Log;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * An object to represent an image from the Media Framework
+ *
+ * This object abstracts away the method used to get the bitmap and provides a way for us to
+ * determine image equality in an application/folder/item agnostic way.
+ */
+public class Image {
+    private static final String TAG = "Image";
+    private static final boolean DEBUG = false;
+
+    public static int SOURCE_NONE = 0;
+    public static int SOURCE_URI = 1;
+    public static int SOURCE_BITMAP = 2;
+
+    private final Context mContext;
+
+    private int mSource = SOURCE_NONE;
+    private Bitmap mImage = null;
+
+    // For use with other applications so they can conveniently assign the handle their storage
+    // solution has picked for this image and pass this object on directly.
+    private String mImageHandle = null;
+
+    /**
+     * Create an Image object from a MediaMetadata object
+     */
+    public Image(Context context, MediaMetadata metadata) {
+        mContext = context;
+
+        String uri_art = metadata.getString(MediaMetadata.METADATA_KEY_ART_URI);
+        String uri_album_art = metadata.getString(MediaMetadata.METADATA_KEY_ALBUM_ART_URI);
+        String uri_icon = metadata.getString(MediaMetadata.METADATA_KEY_DISPLAY_ICON_URI);
+        Bitmap bmp_art = metadata.getBitmap(MediaMetadata.METADATA_KEY_ART);
+        Bitmap bmp_album_art = metadata.getBitmap(MediaMetadata.METADATA_KEY_ALBUM_ART);
+        Bitmap bmp_icon = metadata.getBitmap(MediaMetadata.METADATA_KEY_DISPLAY_ICON);
+
+        if (bmp_art != null) {
+            setImage(bmp_art);
+        } else if (bmp_album_art != null) {
+            setImage(bmp_album_art);
+        } else if (bmp_icon != null) {
+            setImage(bmp_icon);
+        } else if (uri_art != null) {
+            setImage(uri_art);
+        } else if (uri_album_art != null) {
+            setImage(uri_album_art);
+        } else if (uri_icon != null) {
+            setImage(uri_icon);
+        }
+    }
+
+    /**
+     * Create an image out of a bundle of MediaMetadata keyed values
+     */
+    public Image(Context context, Bundle bundle) {
+        mContext = context;
+
+        String uri_art = bundle.getString(MediaMetadata.METADATA_KEY_ART_URI);
+        String uri_album_art = bundle.getString(MediaMetadata.METADATA_KEY_ALBUM_ART_URI);
+        String uri_icon = bundle.getString(MediaMetadata.METADATA_KEY_DISPLAY_ICON_URI);
+        Bitmap bmp_art = bundle.getParcelable(MediaMetadata.METADATA_KEY_ART);
+        Bitmap bmp_album_art = bundle.getParcelable(MediaMetadata.METADATA_KEY_ALBUM_ART);
+        Bitmap bmp_icon = bundle.getParcelable(MediaMetadata.METADATA_KEY_DISPLAY_ICON);
+
+        if (bmp_art != null) {
+            setImage(bmp_art);
+        } else if (bmp_album_art != null) {
+            setImage(bmp_album_art);
+        } else if (bmp_icon != null) {
+            setImage(bmp_icon);
+        } else if (uri_art != null) {
+            setImage(uri_art);
+        } else if (uri_album_art != null) {
+            setImage(uri_album_art);
+        } else if (uri_icon != null) {
+            setImage(uri_icon);
+        }
+    }
+
+    /**
+     * Create an Image object from a MediaDescription object
+     */
+    public Image(Context context, MediaDescription desc) {
+        mContext = context;
+        Uri uri = desc.getIconUri();
+        Bitmap bitmap = desc.getIconBitmap();
+        if (bitmap != null) {
+            setImage(bitmap);
+        } else if (uri != null) {
+            setImage(uri);
+        }
+    }
+
+    /**
+     * Create an Image object from a raw image uri
+     */
+    public Image(Context context, Uri uri) {
+        mContext = context;
+        setImage(uri);
+    }
+
+    /**
+     * Create an Image object from a raw image
+     */
+    public Image(Context context, Bitmap image) {
+        mContext = context;
+        setImage(image);
+    }
+
+    /**
+     * Set the image by resolving a URI to a bitmap
+     */
+    private void setImage(String uriString) {
+        if (uriString == null) return;
+        Uri uri = Uri.parse(uriString);
+        setImage(uri);
+    }
+
+    /**
+     * Set the image by resolving a URI to a bitmap
+     */
+    private void setImage(Uri uri) {
+        if (uri == null) return;
+        Bitmap image = getImageFromUri(uri);
+        if (image == null) return;
+        setImage(image);
+        setSource(SOURCE_URI);
+    }
+
+    /**
+     * Set the image directly from a bitmap
+     */
+    private void setImage(Bitmap image) {
+        if (image == null) return;
+        mImage = image;
+        setSource(SOURCE_BITMAP);
+    }
+
+    /**
+     * Get the bitmap associated with this Image
+     */
+    public Bitmap getImage() {
+        return mImage;
+    }
+
+    /**
+     * Get an image Bitmap from a Uri
+     *
+     * Used to convert Uris into the images they represent
+     *
+     * @param uri A Uri pointing to an image
+     * @return A Bitmap object representing the image at the given Uri
+     */
+    private Bitmap getImageFromUri(Uri uri) {
+        if (uri == null) return null;
+        Bitmap art = null;
+        InputStream input = null;
+        try {
+            if (mContext == null) return null;
+            input = mContext.getContentResolver().openInputStream(uri);
+            art = BitmapFactory.decodeStream(input);
+        } catch (Exception e) {
+            Log.w("Failed to fetch image at uri=" + uri, e);
+            art = null;
+        }
+        try {
+            if (input != null) {
+                input.close();
+            }
+        } catch (IOException e) {
+            Log.e(TAG, "Failed to close image file stream, exception=" + e);
+        }
+        return art;
+    }
+
+    /**
+     * Get the source of the image, if known
+     *
+     * Images currently come from either raw bitmaps or a URI that points to a ContentProvider.
+     * This allows us to set where it came from, largely used for debug purposes.
+     */
+    public int getSource() {
+        return mSource;
+    }
+
+    /**
+     * Set the source of the image.
+     *
+     * Images currently come from either raw bitmaps or a URI that points to a ContentProvider.
+     * This allows us to set where it came from, largely used for debug purposes.
+     */
+    private void setSource(int source) {
+        mSource = source;
+    }
+
+    /**
+     * Assign a handle value from your storage solution to this image
+     */
+    public void setImageHandle(String handle) {
+        mImageHandle = handle;
+    }
+
+    /**
+     * Get the handle value associated with this image from your storage situation
+     */
+    public String getImageHandle() {
+        return mImageHandle;
+    }
+
+    /**
+     * Determine if two image objects are the same.
+     */
+    @Override
+    public boolean equals(Object o) {
+        if (o == null) return false;
+        if (!(o instanceof Image)) return false;
+        final Image image = (Image) o;
+        final Bitmap bmp = image.getImage();
+        if (bmp == null) return (mImage == null);
+        return bmp.sameAs(mImage);
+    }
+
+    /**
+     * Get a string representation of the image and its metadata
+     */
+    @Override
+    public String toString() {
+        return "<Image source=" + mSource + ">";
+    }
+}
diff --git a/src/com/android/bluetooth/avrcp/helpers/ListItem.java b/src/com/android/bluetooth/audio_util/helpers/ListItem.java
similarity index 92%
rename from src/com/android/bluetooth/avrcp/helpers/ListItem.java
rename to src/com/android/bluetooth/audio_util/helpers/ListItem.java
index 210906b..fa7182b 100644
--- a/src/com/android/bluetooth/avrcp/helpers/ListItem.java
+++ b/src/com/android/bluetooth/audio_util/helpers/ListItem.java
@@ -14,9 +14,9 @@
  * limitations under the License.
  */
 
-package com.android.bluetooth.avrcp;
+package com.android.bluetooth.audio_util;
 
-class ListItem implements Cloneable {
+public class ListItem implements Cloneable {
     public boolean isFolder = false;
     public Folder folder;
     public Metadata song;
diff --git a/src/com/android/bluetooth/avrcp/helpers/MediaData.java b/src/com/android/bluetooth/audio_util/helpers/MediaData.java
similarity index 91%
rename from src/com/android/bluetooth/avrcp/helpers/MediaData.java
rename to src/com/android/bluetooth/audio_util/helpers/MediaData.java
index 6646f90..6e5964e 100644
--- a/src/com/android/bluetooth/avrcp/helpers/MediaData.java
+++ b/src/com/android/bluetooth/audio_util/helpers/MediaData.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.bluetooth.avrcp;
+package com.android.bluetooth.audio_util;
 
 import android.media.session.PlaybackState;
 
@@ -24,12 +24,12 @@
 /*
  * Helper class to transport metadata around AVRCP
  */
-class MediaData {
+public class MediaData {
     public List<Metadata> queue;
     public PlaybackState state;
     public Metadata metadata;
 
-    MediaData(Metadata m, PlaybackState s, List<Metadata> q) {
+    public MediaData(Metadata m, PlaybackState s, List<Metadata> q) {
         metadata = m;
         state = s;
         queue = q;
diff --git a/src/com/android/bluetooth/audio_util/helpers/Metadata.java b/src/com/android/bluetooth/audio_util/helpers/Metadata.java
new file mode 100644
index 0000000..ad661a3
--- /dev/null
+++ b/src/com/android/bluetooth/audio_util/helpers/Metadata.java
@@ -0,0 +1,249 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.bluetooth.audio_util;
+
+import android.content.Context;
+import android.media.MediaDescription;
+import android.media.MediaMetadata;
+import android.media.browse.MediaBrowser.MediaItem;
+import android.media.session.MediaSession;
+import android.os.Bundle;
+
+import java.util.Objects;
+
+public class Metadata implements Cloneable {
+    public String mediaId;
+    public String title;
+    public String artist;
+    public String album;
+    public String trackNum;
+    public String numTracks;
+    public String genre;
+    public String duration;
+    public Image image;
+
+    @Override
+    public Metadata clone() {
+        Metadata data = new Metadata();
+        data.mediaId = mediaId;
+        data.title = title;
+        data.artist = artist;
+        data.album = album;
+        data.trackNum = trackNum;
+        data.numTracks = numTracks;
+        data.genre = genre;
+        data.duration = duration;
+        data.image = image;
+        return data;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (o == null) return false;
+        if (!(o instanceof Metadata)) return false;
+
+        final Metadata m = (Metadata) o;
+        if (!Objects.equals(title, m.title)) return false;
+        if (!Objects.equals(artist, m.artist)) return false;
+        if (!Objects.equals(album, m.album)) return false;
+        if (!Objects.equals(trackNum, m.trackNum)) return false;
+        if (!Objects.equals(numTracks, m.numTracks)) return false;
+        if (!Objects.equals(image, m.image)) return false;
+        return true;
+    }
+
+    @Override
+    public String toString() {
+        return "{ mediaId=\"" + mediaId + "\" title=\"" + title + "\" artist=\"" + artist
+                + "\" album=\"" + album + "\" duration=" + duration
+                + " trackPosition=" + trackNum + "/" + numTracks + " image=" + image + " }";
+    }
+
+    /**
+     * A Builder object to populate a Metadata from various different Media Framework objects
+     */
+    public static class Builder {
+        private Metadata mMetadata = new Metadata();
+        private Context mContext = null;
+
+        /**
+         * Set the Media ID fot the Metadata Object
+         */
+        public Builder setMediaId(String id) {
+            mMetadata.mediaId = id;
+            return this;
+        }
+
+        /**
+         * Set the context this builder should use when resolving images
+         */
+        public Builder useContext(Context context) {
+            mContext = context;
+            return this;
+        }
+
+        /**
+         * Extract the fields from a MediaMetadata object into a Metadata, if they exist
+         */
+        public Builder fromMediaMetadata(MediaMetadata data) {
+            if (data == null) return this;
+
+            // First, use the basic description available with the MediaMetadata
+            fromMediaDescription(data.getDescription());
+
+            // Then, replace with better data if available on the MediaMetadata
+            if (data.containsKey(MediaMetadata.METADATA_KEY_MEDIA_ID)) {
+                mMetadata.mediaId = data.getString(MediaMetadata.METADATA_KEY_MEDIA_ID);
+            }
+            if (data.containsKey(MediaMetadata.METADATA_KEY_TITLE)) {
+                mMetadata.title = data.getString(MediaMetadata.METADATA_KEY_TITLE);
+            }
+            if (data.containsKey(MediaMetadata.METADATA_KEY_ARTIST)) {
+                mMetadata.artist = data.getString(MediaMetadata.METADATA_KEY_ARTIST);
+            }
+            if (data.containsKey(MediaMetadata.METADATA_KEY_ALBUM)) {
+                mMetadata.album = data.getString(MediaMetadata.METADATA_KEY_ALBUM);
+            }
+            if (data.containsKey(MediaMetadata.METADATA_KEY_TRACK_NUMBER)) {
+                mMetadata.trackNum = "" + data.getLong(MediaMetadata.METADATA_KEY_TRACK_NUMBER);
+            }
+            if (data.containsKey(MediaMetadata.METADATA_KEY_NUM_TRACKS)) {
+                mMetadata.numTracks = "" + data.getLong(MediaMetadata.METADATA_KEY_NUM_TRACKS);
+            }
+            if (data.containsKey(MediaMetadata.METADATA_KEY_GENRE)) {
+                mMetadata.genre = data.getString(MediaMetadata.METADATA_KEY_GENRE);
+            }
+            if (data.containsKey(MediaMetadata.METADATA_KEY_DURATION)) {
+                mMetadata.duration = "" + data.getLong(MediaMetadata.METADATA_KEY_DURATION);
+            }
+            if ((mContext != null && Util.areUriImagesSupported(mContext)
+                    && (data.containsKey(MediaMetadata.METADATA_KEY_ART_URI)
+                    || data.containsKey(MediaMetadata.METADATA_KEY_ALBUM_ART_URI)
+                    || data.containsKey(MediaMetadata.METADATA_KEY_DISPLAY_ICON_URI)))
+                    || data.containsKey(MediaMetadata.METADATA_KEY_ART)
+                    || data.containsKey(MediaMetadata.METADATA_KEY_ALBUM_ART)
+                    || data.containsKey(MediaMetadata.METADATA_KEY_DISPLAY_ICON)) {
+                mMetadata.image = new Image(mContext, data);
+            }
+            return this;
+        }
+
+        /**
+         * Extract the fields from a MediaItem object into a Metadata, if they exist
+         */
+        public Builder fromMediaItem(MediaItem item) {
+            if (item == null) return this;
+            return fromMediaDescription(item.getDescription()).setMediaId(item.getMediaId());
+        }
+
+        /**
+         * Extract the fields from a MediaDescription object into a Metadata, if they exist
+         */
+        public Builder fromMediaDescription(MediaDescription desc) {
+            if (desc == null) return this;
+
+            // Default the following mapping if they exist
+            if (desc.getTitle() != null) mMetadata.title = desc.getTitle().toString();
+            if (desc.getSubtitle() != null) mMetadata.artist = desc.getSubtitle().toString();
+            if (desc.getDescription() != null) mMetadata.album = desc.getDescription().toString();
+
+            // Check for artwork
+            if (desc.getIconBitmap() != null) {
+                mMetadata.image = new Image(mContext, desc.getIconBitmap());
+            } else if (mContext != null && Util.areUriImagesSupported(mContext)
+                    && desc.getIconUri() != null) {
+                mMetadata.image = new Image(mContext, desc.getIconUri());
+            }
+
+            // Then, check the extras in the description for even better data
+            return fromBundle(desc.getExtras()).setMediaId(desc.getMediaId());
+        }
+
+        /**
+         * Extract the fields from a MediaSession.QueueItem object into a Metadata, if they exist
+         */
+        public Builder fromQueueItem(MediaSession.QueueItem item) {
+            if (item == null) return this;
+            return fromMediaDescription(item.getDescription());
+        }
+
+        /**
+         * Extract the fields from a Bundle of MediaMetadata constants into a Metadata, if they
+         * exist
+         */
+        public Builder fromBundle(Bundle bundle) {
+            if (bundle == null) return this;
+            if (bundle.containsKey(MediaMetadata.METADATA_KEY_MEDIA_ID)) {
+                mMetadata.mediaId = bundle.getString(MediaMetadata.METADATA_KEY_MEDIA_ID);
+            }
+            if (bundle.containsKey(MediaMetadata.METADATA_KEY_TITLE)) {
+                mMetadata.title = bundle.getString(MediaMetadata.METADATA_KEY_TITLE);
+            }
+            if (bundle.containsKey(MediaMetadata.METADATA_KEY_ARTIST)) {
+                mMetadata.artist = bundle.getString(MediaMetadata.METADATA_KEY_ARTIST);
+            }
+            if (bundle.containsKey(MediaMetadata.METADATA_KEY_ALBUM)) {
+                mMetadata.album = bundle.getString(MediaMetadata.METADATA_KEY_ALBUM);
+            }
+            if (bundle.containsKey(MediaMetadata.METADATA_KEY_TRACK_NUMBER)) {
+                mMetadata.trackNum = "" + bundle.getLong(MediaMetadata.METADATA_KEY_TRACK_NUMBER);
+            }
+            if (bundle.containsKey(MediaMetadata.METADATA_KEY_NUM_TRACKS)) {
+                mMetadata.numTracks = "" + bundle.getLong(MediaMetadata.METADATA_KEY_NUM_TRACKS);
+            }
+            if (bundle.containsKey(MediaMetadata.METADATA_KEY_GENRE)) {
+                mMetadata.genre = bundle.getString(MediaMetadata.METADATA_KEY_GENRE);
+            }
+            if (bundle.containsKey(MediaMetadata.METADATA_KEY_DURATION)) {
+                mMetadata.duration = "" + bundle.getLong(MediaMetadata.METADATA_KEY_DURATION);
+            }
+            if ((mContext != null && Util.areUriImagesSupported(mContext)
+                    && (bundle.containsKey(MediaMetadata.METADATA_KEY_ART_URI)
+                    || bundle.containsKey(MediaMetadata.METADATA_KEY_ALBUM_ART_URI)
+                    || bundle.containsKey(MediaMetadata.METADATA_KEY_DISPLAY_ICON_URI)))
+                    || bundle.containsKey(MediaMetadata.METADATA_KEY_ART)
+                    || bundle.containsKey(MediaMetadata.METADATA_KEY_ALBUM_ART)
+                    || bundle.containsKey(MediaMetadata.METADATA_KEY_DISPLAY_ICON)) {
+                mMetadata.image = new Image(mContext, bundle);
+            }
+            return this;
+        }
+
+        /**
+         * Elect to use default values in the Metadata in place of any missing values
+         */
+        public Builder useDefaults() {
+            if (mMetadata.mediaId == null) mMetadata.mediaId = "Not Provided";
+            if (mMetadata.title == null) mMetadata.title = "Not Provided";
+            if (mMetadata.artist == null) mMetadata.artist = "";
+            if (mMetadata.album == null) mMetadata.album = "";
+            if (mMetadata.trackNum == null) mMetadata.trackNum = "1";
+            if (mMetadata.numTracks == null) mMetadata.numTracks = "1";
+            if (mMetadata.genre == null) mMetadata.genre = "";
+            if (mMetadata.duration == null) mMetadata.duration = "0";
+            // The default value chosen for an image is null. Update here if we pick something else
+            return this;
+        }
+
+        /**
+         * Get the final Metadata objects you're building
+         */
+        public Metadata build() {
+            return mMetadata.clone();
+        }
+    }
+}
diff --git a/src/com/android/bluetooth/avrcp/helpers/PlayStatus.java b/src/com/android/bluetooth/audio_util/helpers/PlayStatus.java
similarity index 94%
rename from src/com/android/bluetooth/avrcp/helpers/PlayStatus.java
rename to src/com/android/bluetooth/audio_util/helpers/PlayStatus.java
index 2c69bbd..922b151 100644
--- a/src/com/android/bluetooth/avrcp/helpers/PlayStatus.java
+++ b/src/com/android/bluetooth/audio_util/helpers/PlayStatus.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.bluetooth.avrcp;
+package com.android.bluetooth.audio_util;
 
 import android.media.session.PlaybackState;
 
@@ -23,7 +23,7 @@
  */
 // TODO(apanicke): Send the current active song ID along with this object so that all information
 // is carried by our custom types.
-class PlayStatus {
+public class PlayStatus {
     static final byte STOPPED = 0;
     static final byte PLAYING = 1;
     static final byte PAUSED = 2;
@@ -36,7 +36,7 @@
     public byte state = STOPPED;
 
     // Duration info isn't contained in the PlaybackState so the service must supply it.
-    static PlayStatus fromPlaybackState(PlaybackState state, long duration) {
+    public static PlayStatus fromPlaybackState(PlaybackState state, long duration) {
         PlayStatus ret = new PlayStatus();
         if (state == null) return ret;
 
diff --git a/src/com/android/bluetooth/avrcp/helpers/PlayerInfo.java b/src/com/android/bluetooth/audio_util/helpers/PlayerInfo.java
similarity index 91%
rename from src/com/android/bluetooth/avrcp/helpers/PlayerInfo.java
rename to src/com/android/bluetooth/audio_util/helpers/PlayerInfo.java
index 96b152b..a653e96 100644
--- a/src/com/android/bluetooth/avrcp/helpers/PlayerInfo.java
+++ b/src/com/android/bluetooth/audio_util/helpers/PlayerInfo.java
@@ -14,12 +14,12 @@
  * limitations under the License.
  */
 
-package com.android.bluetooth.avrcp;
+package com.android.bluetooth.audio_util;
 
 /**
  * Carries Media Player Information
  */
-class PlayerInfo {
+public class PlayerInfo {
     public int id;
     public String name;
     public boolean browsable;
diff --git a/src/com/android/bluetooth/audio_util/helpers/Util.java b/src/com/android/bluetooth/audio_util/helpers/Util.java
new file mode 100644
index 0000000..499d08f
--- /dev/null
+++ b/src/com/android/bluetooth/audio_util/helpers/Util.java
@@ -0,0 +1,162 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.bluetooth.audio_util;
+
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.media.MediaDescription;
+import android.media.MediaMetadata;
+import android.media.browse.MediaBrowser.MediaItem;
+import android.media.session.MediaSession;
+import android.os.Bundle;
+import android.util.Log;
+
+import com.android.bluetooth.R;
+
+import java.util.ArrayList;
+import java.util.List;
+
+class Util {
+    public static String TAG = "audio_util.Util";
+    public static boolean DEBUG = false;
+
+    private static final String GPM_KEY = "com.google.android.music.mediasession.music_metadata";
+
+    // TODO (apanicke): Remove this prefix later, for now it makes debugging easier.
+    public static final String NOW_PLAYING_PREFIX = "NowPlayingId";
+
+    /**
+     * Get an empty set of Metadata
+     */
+    public static final Metadata empty_data() {
+        Metadata ret = new Metadata();
+        ret.mediaId = "Not Provided";
+        ret.title = "Not Provided";
+        ret.artist = "";
+        ret.album = "";
+        ret.genre = "";
+        ret.trackNum = "1";
+        ret.numTracks = "1";
+        ret.duration = "0";
+        ret.image = null;
+        return ret;
+    }
+
+    /**
+     * Get whether or not Bluetooth is configured to support URI images or not.
+     *
+     * Note that creating URI images will dramatically increase memory usage.
+     */
+    public static boolean areUriImagesSupported(Context context) {
+        if (context == null) return false;
+        return context.getResources().getBoolean(R.bool.avrcp_target_cover_art_uri_images);
+    }
+
+    /**
+     * Translate a bundle of MediaMetadata keys to audio_util's Metadata
+     */
+    public static Metadata toMetadata(Context context, Bundle bundle) {
+        Metadata.Builder builder = new Metadata.Builder();
+        return builder.useContext(context).useDefaults().fromBundle(bundle).build();
+    }
+
+    /**
+     * Translate a MediaDescription to audio_util's Metadata
+     */
+    public static Metadata toMetadata(Context context, MediaDescription desc) {
+        // Find GPM_KEY data if it exists
+        MediaMetadata data = null;
+        Bundle extras = (desc != null ? desc.getExtras() : null);
+        if (extras != null && extras.containsKey(GPM_KEY)) {
+            data = (MediaMetadata) extras.get(GPM_KEY);
+        }
+
+        Metadata.Builder builder = new Metadata.Builder();
+        return builder.useContext(context).useDefaults().fromMediaDescription(desc)
+                .fromMediaMetadata(data).build();
+    }
+
+    /**
+     * Translate a MediaItem to audio_util's Metadata
+     */
+    public static Metadata toMetadata(Context context, MediaItem item) {
+        Metadata.Builder builder = new Metadata.Builder();
+        return builder.useContext(context).useDefaults().fromMediaItem(item).build();
+    }
+
+    /**
+     * Translate a MediaSession.QueueItem to audio_util's Metadata
+     */
+    public static Metadata toMetadata(Context context, MediaSession.QueueItem item) {
+        Metadata.Builder builder = new Metadata.Builder().useDefaults().fromQueueItem(item);
+        // For Queue Items, the Media Id will always be just its Queue ID
+        // We don't need to use its actual ID since we don't promise UIDS being valid
+        // between a file system and it's now playing list.
+        if (item != null) builder.setMediaId(NOW_PLAYING_PREFIX + item.getQueueId());
+        return builder.build();
+    }
+
+    /**
+     * Translate a MediaMetadata to audio_util's Metadata
+     */
+    public static Metadata toMetadata(Context context, MediaMetadata data) {
+        Metadata.Builder builder = new Metadata.Builder();
+        // This will always be currsong. The AVRCP service will overwrite the mediaId if it needs to
+        // TODO (apanicke): Remove when the service is ready, right now it makes debugging much more
+        // convenient
+        return builder.useContext(context).useDefaults().fromMediaMetadata(data)
+                .setMediaId("currsong").build();
+    }
+
+    /**
+     * Translate a list of MediaSession.QueueItem to a list of audio_util's Metadata
+     */
+    public static List<Metadata> toMetadataList(Context context,
+            List<MediaSession.QueueItem> items) {
+        ArrayList<Metadata> list = new ArrayList<Metadata>();
+
+        if (items == null) return list;
+
+        for (int i = 0; i < items.size(); i++) {
+            Metadata data = toMetadata(context, items.get(i));
+            data.trackNum = "" + (i + 1);
+            data.numTracks = "" + items.size();
+            list.add(data);
+        }
+
+        return list;
+    }
+
+    // Helper method to close a list of ListItems so that if the callee wants
+    // to mutate the list they can do it without affecting any internally cached info
+    public static List<ListItem> cloneList(List<ListItem> list) {
+        List<ListItem> clone = new ArrayList<ListItem>(list.size());
+        for (ListItem item : list) clone.add(item.clone());
+        return clone;
+    }
+
+    public static String getDisplayName(Context context, String packageName) {
+        try {
+            PackageManager manager = context.getPackageManager();
+            return manager.getApplicationLabel(manager.getApplicationInfo(packageName, 0))
+                    .toString();
+        } catch (Exception e) {
+            Log.w(TAG, "Name Not Found using package name: " + packageName);
+            return packageName;
+        }
+    }
+}
diff --git a/src/com/android/bluetooth/avrcp/mockable/MediaBrowser.java b/src/com/android/bluetooth/audio_util/mockable/MediaBrowser.java
similarity index 98%
rename from src/com/android/bluetooth/avrcp/mockable/MediaBrowser.java
rename to src/com/android/bluetooth/audio_util/mockable/MediaBrowser.java
index 7bf38bd..7443e4e 100644
--- a/src/com/android/bluetooth/avrcp/mockable/MediaBrowser.java
+++ b/src/com/android/bluetooth/audio_util/mockable/MediaBrowser.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.bluetooth.avrcp;
+package com.android.bluetooth.audio_util;
 
 import android.content.ComponentName;
 import android.content.Context;
diff --git a/src/com/android/bluetooth/avrcp/mockable/MediaBrowserFactory.java b/src/com/android/bluetooth/audio_util/mockable/MediaBrowserFactory.java
similarity index 97%
rename from src/com/android/bluetooth/avrcp/mockable/MediaBrowserFactory.java
rename to src/com/android/bluetooth/audio_util/mockable/MediaBrowserFactory.java
index eb8c092..aa2f76c 100644
--- a/src/com/android/bluetooth/avrcp/mockable/MediaBrowserFactory.java
+++ b/src/com/android/bluetooth/audio_util/mockable/MediaBrowserFactory.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.bluetooth.avrcp;
+package com.android.bluetooth.audio_util;
 
 import android.content.ComponentName;
 import android.content.Context;
diff --git a/src/com/android/bluetooth/avrcp/mockable/MediaController.java b/src/com/android/bluetooth/audio_util/mockable/MediaController.java
similarity index 99%
rename from src/com/android/bluetooth/avrcp/mockable/MediaController.java
rename to src/com/android/bluetooth/audio_util/mockable/MediaController.java
index 0d160bc..956dfd5 100644
--- a/src/com/android/bluetooth/avrcp/mockable/MediaController.java
+++ b/src/com/android/bluetooth/audio_util/mockable/MediaController.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.bluetooth.avrcp;
+package com.android.bluetooth.audio_util;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
diff --git a/src/com/android/bluetooth/avrcp/mockable/MediaControllerFactory.java b/src/com/android/bluetooth/audio_util/mockable/MediaControllerFactory.java
similarity index 97%
rename from src/com/android/bluetooth/avrcp/mockable/MediaControllerFactory.java
rename to src/com/android/bluetooth/audio_util/mockable/MediaControllerFactory.java
index 8b2a80d..ff477ac 100644
--- a/src/com/android/bluetooth/avrcp/mockable/MediaControllerFactory.java
+++ b/src/com/android/bluetooth/audio_util/mockable/MediaControllerFactory.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.bluetooth.avrcp;
+package com.android.bluetooth.audio_util;
 
 import android.content.Context;
 import android.media.session.MediaSession;
diff --git a/src/com/android/bluetooth/avrcp/mockable/MediaPlayerWrapperFactory.java b/src/com/android/bluetooth/audio_util/mockable/MediaPlayerWrapperFactory.java
similarity index 83%
rename from src/com/android/bluetooth/avrcp/mockable/MediaPlayerWrapperFactory.java
rename to src/com/android/bluetooth/audio_util/mockable/MediaPlayerWrapperFactory.java
index d3ef751..32e0211 100644
--- a/src/com/android/bluetooth/avrcp/mockable/MediaPlayerWrapperFactory.java
+++ b/src/com/android/bluetooth/audio_util/mockable/MediaPlayerWrapperFactory.java
@@ -14,8 +14,9 @@
  * limitations under the License.
  */
 
-package com.android.bluetooth.avrcp;
+package com.android.bluetooth.audio_util;
 
+import android.content.Context;
 import android.os.Looper;
 
 import com.android.internal.annotations.VisibleForTesting;
@@ -29,7 +30,7 @@
 public final class MediaPlayerWrapperFactory {
     private static MediaPlayerWrapper sInjectedWrapper;
 
-    static MediaPlayerWrapper wrap(MediaController controller, Looper looper) {
+    static MediaPlayerWrapper wrap(Context context, MediaController controller, Looper looper) {
         if (sInjectedWrapper != null) return sInjectedWrapper;
         if (controller == null || looper == null) {
             return null;
@@ -37,9 +38,9 @@
 
         MediaPlayerWrapper newWrapper;
         if (controller.getPackageName().equals("com.google.android.music")) {
-            newWrapper = new GPMWrapper(controller, looper);
+            newWrapper = new GPMWrapper(context, controller, looper);
         } else {
-            newWrapper = new MediaPlayerWrapper(controller, looper);
+            newWrapper = new MediaPlayerWrapper(context, controller, looper);
         }
         return newWrapper;
     }
diff --git a/src/com/android/bluetooth/avrcp/AvrcpBipObexServer.java b/src/com/android/bluetooth/avrcp/AvrcpBipObexServer.java
new file mode 100644
index 0000000..498c470
--- /dev/null
+++ b/src/com/android/bluetooth/avrcp/AvrcpBipObexServer.java
@@ -0,0 +1,366 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.bluetooth.avrcp;
+
+import android.util.Log;
+
+import com.android.bluetooth.avrcpcontroller.BipImageDescriptor;
+import com.android.bluetooth.avrcpcontroller.BipImageProperties;
+import com.android.bluetooth.avrcpcontroller.ParseException;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.Arrays;
+
+import javax.obex.HeaderSet;
+import javax.obex.Operation;
+import javax.obex.ResponseCodes;
+import javax.obex.ServerRequestHandler;
+
+/**
+ * A class responsible for handling requests from a specific client connection
+ */
+public class AvrcpBipObexServer extends ServerRequestHandler {
+    private static final String TAG = "AvrcpBipObexServer";
+    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+
+    private final AvrcpCoverArtService mAvrcpCoverArtService;
+
+    // AVRCP Controller BIP Image Initiator/Cover Art UUID - AVRCP 1.6 Section 5.14.2.1
+    private static final byte[] BLUETOOTH_UUID_AVRCP_COVER_ART = new byte[] {
+        (byte) 0x71,
+        (byte) 0x63,
+        (byte) 0xDD,
+        (byte) 0x54,
+        (byte) 0x4A,
+        (byte) 0x7E,
+        (byte) 0x11,
+        (byte) 0xE2,
+        (byte) 0xB4,
+        (byte) 0x7C,
+        (byte) 0x00,
+        (byte) 0x50,
+        (byte) 0xC2,
+        (byte) 0x49,
+        (byte) 0x00,
+        (byte) 0x48
+    };
+
+    private static final String TYPE_GET_LINKED_THUMBNAIL = "x-bt/img-thm";
+    private static final String TYPE_GET_IMAGE_PROPERTIES = "x-bt/img-properties";
+    private static final String TYPE_GET_IMAGE = "x-bt/img-img";
+
+    private static final byte HEADER_ID_IMG_HANDLE = 0x30;
+    private static final byte HEADER_ID_IMG_DESCRIPTOR = 0x71;
+
+    private final Callback mCallback;
+
+    /**
+     * A set of callbacks to notify the creator of important AVRCP BIP events.
+     */
+    public interface Callback {
+        /**
+         * Receive a notification when this server session connects to a device
+         */
+        void onConnected();
+
+        /**
+         * Receive a notification when this server session disconnects with a device
+         */
+        void onDisconnected();
+         /**
+          * Receive a notification when this server session closes a connection with a device
+          */
+        void onClose();
+    }
+
+    public AvrcpBipObexServer(AvrcpCoverArtService service, Callback callback) {
+        super();
+        mAvrcpCoverArtService = service;
+        mCallback = callback;
+    }
+
+    @Override
+    public int onConnect(final HeaderSet request, HeaderSet reply) {
+        debug("onConnect");
+        try {
+            byte[] uuid = (byte[]) request.getHeader(HeaderSet.TARGET);
+            debug("onConnect - uuid=" + Arrays.toString(uuid));
+            if (!Arrays.equals(uuid, BLUETOOTH_UUID_AVRCP_COVER_ART)) {
+                warn("onConnect - uuid didn't match. Not Acceptable");
+                return ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE;
+            }
+            // ...
+        } catch (IOException e) {
+            warn("onConnect - Something bad happened");
+            return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
+        }
+
+        reply.setHeader(HeaderSet.WHO, BLUETOOTH_UUID_AVRCP_COVER_ART);
+        debug("onConnect - Successful");
+        if (mCallback != null) {
+            mCallback.onConnected();
+        }
+        return ResponseCodes.OBEX_HTTP_OK;
+    }
+
+    @Override
+    public void onDisconnect(final HeaderSet request, HeaderSet reply) {
+        debug("onDisconnect");
+        if (mCallback != null) {
+            mCallback.onDisconnected();
+        }
+    }
+
+    @Override
+    public int onGet(final Operation op) {
+        debug("onGet");
+        try {
+            HeaderSet request = op.getReceivedHeader();
+            if (request == null) return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
+
+            // Route the request to the proper response handler
+            String type = (String) request.getHeader(HeaderSet.TYPE);
+            if (TYPE_GET_LINKED_THUMBNAIL.equals(type)) {
+                return handleGetImageThumbnail(op);
+            } else if (TYPE_GET_IMAGE_PROPERTIES.equals(type)) {
+                return handleGetImageProperties(op);
+            } else if (TYPE_GET_IMAGE.equals(type)) {
+                return handleGetImage(op);
+            } else {
+                warn("Received unknown type '" + type + "'");
+                return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
+            }
+        } catch (IllegalArgumentException e) {
+            return ResponseCodes.OBEX_HTTP_PRECON_FAILED;
+        } catch (ParseException e) {
+            return ResponseCodes.OBEX_HTTP_PRECON_FAILED;
+        } catch (Exception e) {
+            return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
+        }
+    }
+
+    @Override
+    public int onPut(final Operation op) {
+        return ResponseCodes.OBEX_HTTP_NOT_IMPLEMENTED;
+    }
+
+    @Override
+    public int onAbort(final HeaderSet request, HeaderSet reply) {
+        return ResponseCodes.OBEX_HTTP_NOT_IMPLEMENTED;
+    }
+
+    @Override
+    public int onSetPath(final HeaderSet request, HeaderSet reply, final boolean backup,
+            final boolean create) {
+        return ResponseCodes.OBEX_HTTP_NOT_IMPLEMENTED;
+    }
+
+    @Override
+    public void onClose() {
+        debug("Connection closed");
+        if (mCallback != null) {
+            mCallback.onClose();
+        }
+    }
+
+    /**
+     * Determine if a given image handle is valid in format
+     *
+     * An image handle a 9 character string of numbers 0-9 only. Anything else is invalid. This is
+     * defined in section 4.4.4 (Image Handles) of the BIP specification, which is inherited by the
+     * AVRCP specification.
+     *
+     * @return True if the image handle is valid, false otherwise.
+     */
+    private boolean isImageHandleValid(String handle) {
+        if (handle == null || handle.length() != 7) return false;
+        for (int i = 0; i < 7; i++) {
+            char c = handle.charAt(i);
+            if (!Character.isDigit(c)) return false;
+        }
+        return true;
+    }
+
+    private int handleGetImageThumbnail(Operation op)throws IOException  {
+        HeaderSet request = op.getReceivedHeader();
+        String imageHandle = (String) request.getHeader(HEADER_ID_IMG_HANDLE);
+
+        debug("Received GetImageThumbnail(handle='" + imageHandle + "')");
+
+        if (imageHandle == null) {
+            warn("Received GetImageThumbnail without an image handle");
+            return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
+        }
+
+        if (!isImageHandleValid(imageHandle)) {
+            debug("Received GetImageThumbnail with an invalid image handle");
+            return ResponseCodes.OBEX_HTTP_PRECON_FAILED;
+        }
+
+        CoverArt image = mAvrcpCoverArtService.getImage(imageHandle);
+        if (image == null) {
+            warn("No image stored at handle '" + imageHandle + "'");
+            return ResponseCodes.OBEX_HTTP_NOT_FOUND;
+        }
+
+        byte[] thumbnail = image.getThumbnail();
+        if (thumbnail == null) {
+            warn("Failed to serialize image");
+            return ResponseCodes.OBEX_HTTP_NOT_FOUND;
+        }
+
+        HeaderSet replyHeaders = new HeaderSet();
+        return sendResponse(op, replyHeaders, thumbnail);
+    }
+
+    private int handleGetImageProperties(Operation op) throws IOException {
+        HeaderSet request = op.getReceivedHeader();
+        String imageHandle = (String) request.getHeader(HEADER_ID_IMG_HANDLE);
+
+        debug("Received GetImageProperties(handle='" + imageHandle + "')");
+
+        if (imageHandle == null) {
+            warn("Received GetImageProperties without an image handle");
+            return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
+        }
+
+        if (!isImageHandleValid(imageHandle)) {
+            debug("Received GetImageProperties with an invalid image handle");
+            return ResponseCodes.OBEX_HTTP_PRECON_FAILED;
+        }
+
+        CoverArt image = mAvrcpCoverArtService.getImage(imageHandle);
+        if (image == null) {
+            warn("No image stored at handle '" + imageHandle + "'");
+            return ResponseCodes.OBEX_HTTP_NOT_FOUND;
+        }
+        BipImageProperties properties = image.getImageProperties();
+        if (properties == null) {
+            warn("Failed to get properties for known image");
+            return ResponseCodes.OBEX_HTTP_NOT_FOUND;
+        }
+
+        byte[] propertiesBytes = properties.serialize();
+        if (propertiesBytes == null) {
+            debug("Failed to serialize properties for image");
+            return ResponseCodes.OBEX_HTTP_NOT_FOUND;
+        }
+
+        debug("Sending image properties: " + properties);
+        HeaderSet replyHeaders = new HeaderSet();
+        return sendResponse(op, replyHeaders, propertiesBytes);
+    }
+
+    private int handleGetImage(Operation op) throws IOException {
+        HeaderSet request = op.getReceivedHeader();
+        String imageHandle = (String) request.getHeader(HEADER_ID_IMG_HANDLE);
+        byte[] descriptorBytes = (byte[]) request.getHeader(HEADER_ID_IMG_DESCRIPTOR);
+        BipImageDescriptor descriptor = null;
+
+        if (descriptorBytes != null) {
+            descriptor = new BipImageDescriptor(new ByteArrayInputStream(descriptorBytes));
+        }
+
+        debug("Received GetImage(handle='" + imageHandle + "', descriptor='" + descriptor + "')");
+
+        if (imageHandle == null) {
+            warn("Received GetImage without an image handle");
+            return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
+        }
+
+        if (!isImageHandleValid(imageHandle)) {
+            debug("Received GetImage with an invalid image handle");
+            return ResponseCodes.OBEX_HTTP_PRECON_FAILED;
+        }
+
+        CoverArt image = mAvrcpCoverArtService.getImage(imageHandle);
+        if (image == null) {
+            warn("No image stored at handle '" + imageHandle + "'");
+            return ResponseCodes.OBEX_HTTP_NOT_FOUND;
+        }
+
+        byte[] imageBytes = null;
+        if (descriptor == null) {
+            debug("Received GetImage without an image descriptor. Returning native format");
+            imageBytes = image.getImage();
+        } else {
+            imageBytes = image.getImage(descriptor);
+        }
+
+        if (imageBytes == null) {
+            warn("Failed to serialize image with given format");
+            return ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE; // BIP Section 5.3 unsupported format
+        }
+
+        debug("Sending image");
+        HeaderSet replyHeaders = new HeaderSet();
+        replyHeaders.setHeader(HeaderSet.LENGTH, null); // Section 4.5.8, Required, null is fine
+        return sendResponse(op, replyHeaders, imageBytes);
+    }
+
+    /**
+     * Send a response to the given operation using the given headers and bytes.
+     */
+    private int sendResponse(Operation op, HeaderSet replyHeaders, byte[] bytes) {
+        if (op != null && bytes != null && replyHeaders != null) {
+            OutputStream outStream = null;
+            int maxChunkSize = 0;
+            int bytesToWrite = 0;
+            int bytesWritten = 0;
+            try {
+                op.sendHeaders(replyHeaders); // Do this before getting chunk size
+                maxChunkSize = op.getMaxPacketSize();
+                outStream = op.openOutputStream();
+                while (bytesWritten < bytes.length) {
+                    bytesToWrite = Math.min(maxChunkSize, bytes.length - bytesWritten);
+                    outStream.write(bytes, bytesWritten, bytesToWrite);
+                    bytesWritten += bytesToWrite;
+                }
+            } catch (IOException e) {
+                warn("An exception occurred while writing response, e=" + e);
+            } finally {
+                // Make sure we close
+                if (outStream != null) {
+                    try {
+                        outStream.close();
+                    } catch (IOException e) { }
+                }
+            }
+            // If we didn't write everything then send the error code
+            if (bytesWritten != bytes.length) {
+                warn("Failed to write entire response");
+                return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
+            }
+            // Otherwise, success!
+            return ResponseCodes.OBEX_HTTP_OK;
+        }
+        // If had no header or no body to send then assume we didn't find anything at all
+        return ResponseCodes.OBEX_HTTP_NOT_FOUND;
+    }
+
+    private void warn(String msg) {
+        Log.w(TAG, msg);
+    }
+
+    private void debug(String msg) {
+        if (DEBUG) {
+            Log.d(TAG, msg);
+        }
+    }
+}
diff --git a/src/com/android/bluetooth/avrcp/AvrcpCoverArtService.java b/src/com/android/bluetooth/avrcp/AvrcpCoverArtService.java
new file mode 100644
index 0000000..4397bbc
--- /dev/null
+++ b/src/com/android/bluetooth/avrcp/AvrcpCoverArtService.java
@@ -0,0 +1,295 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.bluetooth.avrcp;
+
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothSocket;
+import android.content.Context;
+import android.util.Log;
+
+import com.android.bluetooth.BluetoothObexTransport;
+import com.android.bluetooth.IObexConnectionHandler;
+import com.android.bluetooth.ObexServerSockets;
+import com.android.bluetooth.audio_util.Image;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.HashMap;
+
+import javax.obex.ServerSession;
+
+/**
+ * The AVRCP Cover Art Service
+ *
+ * This service handles allocation of image handles and storage of images. It also owns the BIP OBEX
+ * server that handles requests to get AVRCP cover artwork.
+ */
+public class AvrcpCoverArtService {
+    private static final String TAG = "AvrcpCoverArtService";
+    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+
+    private static final int COVER_ART_STORAGE_MAX_ITEMS = 32;
+
+    private final Context mContext;
+
+    // Cover Art and Image Handle objects
+    private final AvrcpCoverArtStorage mStorage;
+
+    // BIP Server Objects
+    private volatile boolean mShutdown = true;
+    private final SocketAcceptor mAcceptThread;
+    private ObexServerSockets mServerSockets = null;
+    private final HashMap<BluetoothDevice, ServerSession> mClients =
+            new HashMap<BluetoothDevice, ServerSession>();
+    private final Object mClientsLock = new Object();
+    private final Object mServerLock = new Object();
+
+    // Native interface
+    private AvrcpNativeInterface mNativeInterface;
+
+    public AvrcpCoverArtService(Context context) {
+        mContext = context;
+        mNativeInterface = AvrcpNativeInterface.getInterface();
+        mAcceptThread = new SocketAcceptor();
+        mStorage = new AvrcpCoverArtStorage(COVER_ART_STORAGE_MAX_ITEMS);
+    }
+
+    /**
+     * Start the AVRCP Cover Art Service.
+     *
+     * This will start up a BIP OBEX server and record the l2cap psm in the SDP record, and begin
+     * accepting connections.
+     */
+    public boolean start() {
+        debug("Starting service");
+        if (!isShutdown()) {
+            error("Service already started");
+            return true;
+        }
+        mStorage.clear();
+        return startBipServer();
+    }
+
+    /**
+     * Stop the AVRCP Cover Art Service.
+     *
+     * Tear down existing connections, remove ourselved from the SDP record.
+     */
+    public boolean stop() {
+        debug("Stopping service");
+        stopBipServer();
+        synchronized (mClientsLock) {
+            for (ServerSession session : mClients.values()) {
+                session.close();
+            }
+            mClients.clear();
+        }
+        mStorage.clear();
+        return true;
+    }
+
+    private boolean startBipServer() {
+        debug("Starting BIP OBEX server");
+        synchronized (mServerLock) {
+            mServerSockets = ObexServerSockets.create(mAcceptThread);
+            if (mServerSockets == null) {
+                error("Failed to get a server socket. Can't setup cover art service");
+                return false;
+            }
+            registerBipServer(mServerSockets.getL2capPsm());
+            mShutdown = false;
+            debug("Service started, psm=" + mServerSockets.getL2capPsm());
+        }
+        return true;
+    }
+
+    private boolean stopBipServer() {
+        debug("Stopping BIP OBEX server");
+        synchronized (mServerLock) {
+            mShutdown = true;
+            unregisterBipServer();
+            if (mServerSockets != null) {
+                mServerSockets.shutdown(false);
+                mServerSockets = null;
+            }
+        }
+        return true;
+    }
+
+    private boolean isShutdown() {
+        synchronized (mServerLock) {
+            return mShutdown;
+        }
+    }
+
+    private int getL2capPsm() {
+        synchronized (mServerLock) {
+            return (mServerLock != null ? mServerSockets.getL2capPsm() : 0);
+        }
+    }
+
+    /**
+     * Store an image with the service and gets the image handle it's associated with.
+     */
+    public String storeImage(Image image) {
+        debug("storeImage(image='" + image + "')");
+        if (image == null || image.getImage() == null) return null;
+        return mStorage.storeImage(new CoverArt(image));
+    }
+
+    /**
+     * Get the image stored at the given image handle, if it exists
+     */
+    public CoverArt getImage(String imageHandle) {
+        debug("getImage(" + imageHandle + ")");
+        return mStorage.getImage(imageHandle);
+    }
+
+    /**
+     * Add a BIP L2CAP PSM to the AVRCP Target SDP Record
+     */
+    private void registerBipServer(int psm) {
+        debug("Add our PSM (" + psm + ") to the AVRCP Target SDP record");
+        mNativeInterface.registerBipServer(psm);
+        return;
+    }
+
+    /**
+     * Remove any BIP L2CAP PSM from the AVRCP Target SDP Record
+     */
+    private void unregisterBipServer() {
+        debug("Remove the PSM remove the AVRCP Target SDP record");
+        mNativeInterface.unregisterBipServer();
+        return;
+    }
+
+    /**
+     * Connect a device with the server
+     *
+     * Since the server cannot explicitly make clients connect, this function is internal only and
+     * provides a place for us to do required book keeping when we've decided to accept a client
+     */
+    private boolean connect(BluetoothDevice device, BluetoothSocket socket) {
+        debug("Connect '" + device + "'");
+        synchronized (mClientsLock) {
+
+            // Only allow one client at all
+            if (mClients.size() >= 1) return false;
+
+            // Only allow one client per device
+            if (mClients.containsKey(device)) return false;
+
+            // Create a BIP OBEX Server session for the client and connect
+            AvrcpBipObexServer s = new AvrcpBipObexServer(this, new AvrcpBipObexServer.Callback() {
+                public void onConnected() {
+                    mNativeInterface.setBipClientStatus(device.getAddress(), true);
+                }
+
+                public void onDisconnected() {
+                    mNativeInterface.setBipClientStatus(device.getAddress(), false);
+                }
+
+                public void onClose() {
+                    disconnect(device);
+                }
+            });
+            BluetoothObexTransport transport = new BluetoothObexTransport(socket);
+            try {
+                ServerSession session = new ServerSession(transport, s, null);
+                mClients.put(device, session);
+                return true;
+            } catch (IOException e) {
+                error(e.toString());
+                return false;
+            }
+        }
+    }
+
+    /**
+     * Explicitly disconnect a device from our BIP server if its connected.
+     */
+    public void disconnect(BluetoothDevice device) {
+        debug("disconnect '" + device + "'");
+        // Closing the server session closes the underlying transport, which closes the underlying
+        // socket as well. No need to maintain and close anything else.
+        synchronized (mClientsLock) {
+            if (mClients.containsKey(device)) {
+                mNativeInterface.setBipClientStatus(device.getAddress(), false);
+                ServerSession session = mClients.get(device);
+                mClients.remove(device);
+                session.close();
+            }
+        }
+    }
+
+    /**
+     * A Socket Acceptor to handle incoming connections to our BIP Server.
+     *
+     * If we are accepting connections and the device is permitted, then this class will create a
+     * session with our AvrcpBipObexServer.
+     */
+    private class SocketAcceptor implements IObexConnectionHandler {
+        @Override
+        public synchronized boolean onConnect(BluetoothDevice device, BluetoothSocket socket) {
+            debug("onConnect() - device=" + device + ", socket=" + socket);
+            if (isShutdown()) return false;
+            return connect(device, socket);
+        }
+
+        @Override
+        public synchronized void onAcceptFailed() {
+            error("OnAcceptFailed()");
+            if (isShutdown()) {
+                error("Failed to accept incoming connection due to shutdown");
+            } else {
+                // restart
+                stop();
+                start();
+            }
+        }
+    }
+
+    /**
+     * Dump useful debug information about this service to a string
+     */
+    public void dump(StringBuilder sb) {
+        int psm = getL2capPsm();
+        sb.append("AvrcpCoverArtService:");
+        sb.append("\n\tpsm = " + (psm == 0 ? "null" : psm));
+        mStorage.dump(sb);
+        synchronized (mClientsLock) {
+            sb.append("\n\tclients = " + Arrays.toString(mClients.keySet().toArray()));
+        }
+        sb.append("\n");
+    }
+
+    /**
+     * Print a message to DEBUG if debug output is enabled
+     */
+    private void debug(String msg) {
+        if (DEBUG) {
+            Log.d(TAG, msg);
+        }
+    }
+
+    /**
+     * Print a message to ERROR
+     */
+    private void error(String msg) {
+        Log.e(TAG, msg);
+    }
+}
diff --git a/src/com/android/bluetooth/avrcp/AvrcpCoverArtStorage.java b/src/com/android/bluetooth/avrcp/AvrcpCoverArtStorage.java
new file mode 100644
index 0000000..fde4d3b
--- /dev/null
+++ b/src/com/android/bluetooth/avrcp/AvrcpCoverArtStorage.java
@@ -0,0 +1,212 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.bluetooth.avrcp;
+
+import android.util.Log;
+
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+/**
+ * A class abstracting the storage method of cover art images
+ */
+final class AvrcpCoverArtStorage {
+    private static final String TAG = "AvrcpCoverArtStorage";
+    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+
+    private final Object mHandlesLock = new Object();
+    private int mNextImageHandle = 0;
+
+    private final Object mImagesLock = new Object();
+    private final int mMaxImages;
+    private final Map<String, String> mImageHandles;
+    private final Map<String, CoverArt> mImages;
+
+    /**
+     * Make an image storage object with no bounds on the amount of images it can store
+     */
+    AvrcpCoverArtStorage() {
+        this(0);
+    }
+
+    /**
+     * Make an image storage object with a bound on the amount of images it can store
+     */
+    AvrcpCoverArtStorage(int maxSize) {
+        if (maxSize < 0) {
+            throw new IllegalArgumentException("maxSize < 0");
+        }
+        mMaxImages = maxSize;
+
+        mImageHandles = new HashMap<String, String>();
+
+        // Using a LinkedHashMap allows us to having items ordered LRU -> MRU (true param does this)
+        // This way, if we need run out of space we can remove from the front to remove the least
+        // recently accessed items
+        mImages = new LinkedHashMap<String, CoverArt>(0, 0.75f /* default load factor */, true);
+    }
+
+    /**
+     * Store an image and get the image handle it's been associated with.
+     */
+    public String storeImage(CoverArt coverArt) {
+        debug("storeImage(CoverArt='" + coverArt + "')");
+        if (coverArt == null || coverArt.getImage() == null) {
+            debug("Received a null image");
+            return null;
+        }
+
+        String imageHandle = null;
+        String hash = coverArt.getImageHash();
+        if (hash == null) {
+            error("Failed to get the hash of the image");
+            return null;
+        }
+
+        synchronized (mImagesLock) {
+            if (mImageHandles.containsKey(hash)) {
+                debug("Already have image of hash '" + hash + "'");
+                imageHandle = mImageHandles.get(hash);
+                debug("Sending back existing handle '" + imageHandle + "'");
+                return imageHandle;
+            } else {
+                debug("Got a new image, hash='" + hash + "'");
+                imageHandle = getNextImageHandle();
+                if (imageHandle != null) {
+                    mImageHandles.put(hash, imageHandle);
+                }
+            }
+
+            if (imageHandle != null) {
+                debug("Image " + coverArt + " stored at handle '" + imageHandle + "'");
+                coverArt.setImageHandle(imageHandle);
+                mImages.put(imageHandle, coverArt);
+                trimToSize();
+            } else {
+                error("Failed to store image. Could not get a handle.");
+            }
+        }
+        return imageHandle;
+    }
+
+    /**
+     * Get the image stored at the given image handle, if it exists
+     */
+    public CoverArt getImage(String imageHandle) {
+        debug("getImage(" + imageHandle + ")");
+        if (imageHandle == null) return null;
+        synchronized (mImagesLock) {
+            CoverArt coverArt = mImages.get(imageHandle);
+            debug("Image handle '" + imageHandle + "' -> " + coverArt);
+            return coverArt;
+        }
+    }
+
+    /**
+     * Clear out all stored images and image handles
+     */
+    public void clear() {
+        synchronized (mImagesLock) {
+            mImages.clear();
+            mImageHandles.clear();
+        }
+
+        synchronized (mHandlesLock) {
+            mNextImageHandle = 0;
+        }
+    }
+
+    private void trimToSize() {
+        if (mMaxImages <= 0) return;
+        synchronized (mImagesLock) {
+            while (mImages.size() > mMaxImages) {
+                Map.Entry<String, CoverArt> entry = mImages.entrySet().iterator().next();
+                String imageHandle = entry.getKey();
+                CoverArt coverArt = entry.getValue();
+                debug("Evicting '" + imageHandle + "' -> " + coverArt);
+                mImages.remove(imageHandle);
+                mImageHandles.remove(coverArt.getImageHash());
+            }
+        }
+    }
+
+    /**
+     * Get the next available image handle value if one is available.
+     *
+     * Values are integers in the domain [0, 9999999], represented as zero-padded strings. Getting
+     * an image handle assumes you will use it.
+     */
+    private String getNextImageHandle() {
+        synchronized (mHandlesLock) {
+            if (mNextImageHandle > 9999999) {
+                error("No more image handles left");
+                return null;
+            }
+
+            String handle = String.valueOf(mNextImageHandle);
+            while (handle.length() != 7) {
+                handle = "0" + handle;
+            }
+
+            debug("Allocated handle " + mNextImageHandle + " --> '" + handle + "'");
+            mNextImageHandle++;
+            return handle;
+        }
+    }
+
+    public void dump(StringBuilder sb) {
+        int bytes = 0;
+        sb.append("\n\timages (" + mImageHandles.size());
+        if (mMaxImages > 0) sb.append(" / " + mMaxImages);
+        sb.append("):");
+        sb.append("\n\t\tHandle   : Hash                              : CoverArt");
+        synchronized (mImagesLock) {
+            // Be sure to use entry set below or each access well count to the ordering
+            for (Map.Entry<String, CoverArt> entry : mImages.entrySet()) {
+                String imageHandle = entry.getKey();
+                CoverArt coverArt = entry.getValue();
+                String hash = "<           NOT IN SET          >";
+                for (String key : mImageHandles.keySet()) {
+                    String handle = mImageHandles.get(key);
+                    if (imageHandle.equals(handle)) {
+                        hash = key;
+                    }
+                }
+                sb.append(String.format("\n\t\t%-8s : %-32s : %s", imageHandle, hash, coverArt));
+                bytes += coverArt.size();
+            }
+        }
+        sb.append("\n\tImage bytes: " + bytes);
+    }
+
+    /**
+     * Print a message to DEBUG if debug output is enabled
+     */
+    private void debug(String msg) {
+        if (DEBUG) {
+            Log.d(TAG, msg);
+        }
+    }
+
+    /**
+     * Print a message to ERROR
+     */
+    private void error(String msg) {
+        Log.e(TAG, msg);
+    }
+}
diff --git a/src/com/android/bluetooth/avrcp/AvrcpNativeInterface.java b/src/com/android/bluetooth/avrcp/AvrcpNativeInterface.java
index 7faa543..e86fb98 100644
--- a/src/com/android/bluetooth/avrcp/AvrcpNativeInterface.java
+++ b/src/com/android/bluetooth/avrcp/AvrcpNativeInterface.java
@@ -16,10 +16,16 @@
 
 package com.android.bluetooth.avrcp;
 
+import android.annotation.RequiresPermission;
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothDevice;
 import android.util.Log;
 
+import com.android.bluetooth.audio_util.ListItem;
+import com.android.bluetooth.audio_util.Metadata;
+import com.android.bluetooth.audio_util.PlayStatus;
+import com.android.bluetooth.audio_util.PlayerInfo;
+
 import java.util.List;
 
 /**
@@ -57,6 +63,20 @@
         cleanupNative();
     }
 
+    void registerBipServer(int l2capPsm) {
+        d("Register our BIP server at psm=" + l2capPsm);
+        registerBipServerNative(l2capPsm);
+    }
+
+    void unregisterBipServer() {
+        d("Unregister any BIP server");
+        unregisterBipServerNative();
+    }
+
+    void setBipClientStatus(String bdaddr, boolean connected) {
+        setBipClientStatusNative(bdaddr, connected);
+    }
+
     Metadata getCurrentSongInfo() {
         d("getCurrentSongInfo");
         if (mAvrcpService == null) {
@@ -235,6 +255,8 @@
 
     private static native void classInitNative();
     private native void initNative();
+    private native void registerBipServerNative(int l2capPsm);
+    private native void unregisterBipServerNative();
     private native void sendMediaUpdateNative(
             boolean trackChanged, boolean playState, boolean playPos);
     private native void sendFolderUpdateNative(
@@ -246,6 +268,7 @@
     private native boolean connectDeviceNative(String bdaddr);
     private native boolean disconnectDeviceNative(String bdaddr);
     private native void sendVolumeChangedNative(String bdaddr, int volume);
+    private native void setBipClientStatusNative(String bdaddr, boolean connected);
 
     private static void d(String msg) {
         if (DEBUG) {
diff --git a/src/com/android/bluetooth/avrcp/AvrcpTargetService.java b/src/com/android/bluetooth/avrcp/AvrcpTargetService.java
index 17e5a10..644ef34 100644
--- a/src/com/android/bluetooth/avrcp/AvrcpTargetService.java
+++ b/src/com/android/bluetooth/avrcp/AvrcpTargetService.java
@@ -16,6 +16,7 @@
 
 package com.android.bluetooth.avrcp;
 
+import android.annotation.RequiresPermission;
 import android.bluetooth.BluetoothA2dp;
 import android.bluetooth.BluetoothDevice;
 import android.bluetooth.BluetoothProfile;
@@ -31,11 +32,20 @@
 import android.util.Log;
 
 import com.android.bluetooth.BluetoothMetricsProto;
+import com.android.bluetooth.R;
 import com.android.bluetooth.Utils;
 import com.android.bluetooth.a2dp.A2dpService;
+import com.android.bluetooth.audio_util.BTAudioEventLogger;
+import com.android.bluetooth.audio_util.MediaData;
+import com.android.bluetooth.audio_util.MediaPlayerList;
+import com.android.bluetooth.audio_util.MediaPlayerWrapper;
+import com.android.bluetooth.audio_util.Metadata;
+import com.android.bluetooth.audio_util.PlayStatus;
+import com.android.bluetooth.audio_util.PlayerInfo;
 import com.android.bluetooth.btservice.MetricsLogger;
 import com.android.bluetooth.btservice.ProfileService;
 import com.android.bluetooth.btservice.ServiceFactory;
+import com.android.internal.annotations.VisibleForTesting;
 
 import java.util.List;
 import java.util.Objects;
@@ -46,16 +56,17 @@
  */
 public class AvrcpTargetService extends ProfileService {
     private static final String TAG = "AvrcpTargetService";
-    private static final boolean DEBUG = true;
+    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
     private static final String AVRCP_ENABLE_PROPERTY = "persist.bluetooth.enablenewavrcp";
 
     private static final int AVRCP_MAX_VOL = 127;
     private static final int MEDIA_KEY_EVENT_LOGGER_SIZE = 20;
     private static final String MEDIA_KEY_EVENT_LOGGER_TITLE = "Media Key Events";
     private static int sDeviceMaxVolume = 0;
-    private final AvrcpEventLogger mMediaKeyEventLogger = new AvrcpEventLogger(
+    private final BTAudioEventLogger mMediaKeyEventLogger = new BTAudioEventLogger(
             MEDIA_KEY_EVENT_LOGGER_SIZE, MEDIA_KEY_EVENT_LOGGER_TITLE);
 
+    private AvrcpVersion mAvrcpVersion;
     private MediaPlayerList mMediaPlayerList;
     private AudioManager mAudioManager;
     private AvrcpBroadcastReceiver mReceiver;
@@ -66,10 +77,12 @@
     // Only used to see if the metadata has changed from its previous value
     private MediaData mCurrentData;
 
+    // Cover Art Service (Storage + BIP Server)
+    private AvrcpCoverArtService mAvrcpCoverArtService = null;
+
     private static AvrcpTargetService sInstance = null;
 
-    class ListCallback implements MediaPlayerList.MediaUpdateCallback,
-            MediaPlayerList.FolderUpdateCallback {
+    class ListCallback implements MediaPlayerList.MediaUpdateCallback {
         @Override
         public void run(MediaData data) {
             if (mNativeInterface == null) return;
@@ -134,12 +147,24 @@
     }
 
     /**
+     * Set the AvrcpTargetService instance.
+     */
+    @VisibleForTesting
+    public static void set(AvrcpTargetService instance) {
+        sInstance = instance;
+    }
+
+    /**
      * Get the AvrcpTargetService instance. Returns null if the service hasn't been initialized.
      */
     public static AvrcpTargetService get() {
         return sInstance;
     }
 
+    public AvrcpCoverArtService getCoverArtService() {
+        return mAvrcpCoverArtService;
+    }
+
     @Override
     public String getName() {
         return TAG;
@@ -189,6 +214,8 @@
         mNativeInterface = AvrcpNativeInterface.getInterface();
         mNativeInterface.init(AvrcpTargetService.this);
 
+        mAvrcpVersion = AvrcpVersion.getCurrentSystemPropertiesValue();
+
         mVolumeManager = new AvrcpVolumeManager(this, mAudioManager, mNativeInterface);
 
         UserManager userManager = UserManager.get(getApplicationContext());
@@ -196,6 +223,19 @@
             mMediaPlayerList.init(new ListCallback());
         }
 
+        if (getResources().getBoolean(R.bool.avrcp_target_enable_cover_art)) {
+            if (mAvrcpVersion.isAtleastVersion(AvrcpVersion.AVRCP_VERSION_1_6)) {
+                mAvrcpCoverArtService = new AvrcpCoverArtService(this);
+                boolean started = mAvrcpCoverArtService.start();
+                if (!started) {
+                    Log.e(TAG, "Failed to start cover art service");
+                    mAvrcpCoverArtService = null;
+                }
+            } else {
+                Log.e(TAG, "Please use AVRCP version 1.6 to enable cover art");
+            }
+        }
+
         mReceiver = new AvrcpBroadcastReceiver();
         IntentFilter filter = new IntentFilter();
         filter.addAction(BluetoothA2dp.ACTION_ACTIVE_DEVICE_CHANGED);
@@ -218,6 +258,11 @@
             return true;
         }
 
+        if (mAvrcpCoverArtService != null) {
+            mAvrcpCoverArtService.stop();
+        }
+        mAvrcpCoverArtService = null;
+
         sInstance = null;
         unregisterReceiver(mReceiver);
 
@@ -294,7 +339,7 @@
         return mVolumeManager.getVolume(device, mVolumeManager.getNewDeviceVolume());
     }
 
-    // TODO (apanicke): Add checks to blacklist Absolute Volume devices if they behave poorly.
+    // TODO (apanicke): Add checks to rejectlist Absolute Volume devices if they behave poorly.
     void setVolume(int avrcpVolume) {
         BluetoothDevice activeDevice = getA2dpActiveDevice();
         if (activeDevice == null) {
@@ -320,7 +365,12 @@
     }
 
     Metadata getCurrentSongInfo() {
-        return mMediaPlayerList.getCurrentSongInfo();
+        Metadata metadata = mMediaPlayerList.getCurrentSongInfo();
+        if (mAvrcpCoverArtService != null && metadata.image != null) {
+            String imageHandle = mAvrcpCoverArtService.storeImage(metadata.image);
+            if (imageHandle != null) metadata.image.setImageHandle(imageHandle);
+        }
+        return metadata;
     }
 
     PlayStatus getPlayState() {
@@ -340,7 +390,31 @@
     }
 
     List<Metadata> getNowPlayingList() {
-        return mMediaPlayerList.getNowPlayingList();
+        String currentMediaId = getCurrentMediaId();
+        Metadata currentTrack = null;
+        String imageHandle = null;
+        List<Metadata> nowPlayingList = mMediaPlayerList.getNowPlayingList();
+        if (mAvrcpCoverArtService != null) {
+            for (Metadata metadata : nowPlayingList) {
+                if (metadata.mediaId == currentMediaId) {
+                    currentTrack = metadata;
+                } else if (metadata.image != null) {
+                    imageHandle = mAvrcpCoverArtService.storeImage(metadata.image);
+                    if (imageHandle != null) {
+                        metadata.image.setImageHandle(imageHandle);
+                    }
+                }
+            }
+
+            // Always store the current item from the queue last so we know the image is in storage
+            if (currentTrack != null) {
+                imageHandle = mAvrcpCoverArtService.storeImage(currentTrack.image);
+                if (imageHandle != null) {
+                    currentTrack.image.setImageHandle(imageHandle);
+                }
+            }
+        }
+        return nowPlayingList;
     }
 
     int getCurrentPlayerId() {
@@ -396,6 +470,8 @@
         }
 
         StringBuilder tempBuilder = new StringBuilder();
+        tempBuilder.append("AVRCP version: " + mAvrcpVersion + "\n");
+
         if (mMediaPlayerList != null) {
             mMediaPlayerList.dump(tempBuilder);
         } else {
@@ -405,6 +481,10 @@
         mMediaKeyEventLogger.dump(tempBuilder);
         tempBuilder.append("\n");
         mVolumeManager.dump(tempBuilder);
+        if (mAvrcpCoverArtService != null) {
+            tempBuilder.append("\n");
+            mAvrcpCoverArtService.dump(tempBuilder);
+        }
 
         // Tab everything over by two spaces
         sb.append(tempBuilder.toString().replaceAll("(?m)^", "  "));
@@ -425,8 +505,7 @@
 
         @Override
         public void sendVolumeChanged(int volume) {
-            if (!Utils.checkCaller()) {
-                Log.w(TAG, "sendVolumeChanged not allowed for non-active user");
+            if (!Utils.callerIsSystemOrActiveUser(TAG, "sendVolumeChanged")) {
                 return;
             }
 
diff --git a/src/com/android/bluetooth/avrcp/AvrcpVolumeManager.java b/src/com/android/bluetooth/avrcp/AvrcpVolumeManager.java
index a0fa0b3..507fbe9 100644
--- a/src/com/android/bluetooth/avrcp/AvrcpVolumeManager.java
+++ b/src/com/android/bluetooth/avrcp/AvrcpVolumeManager.java
@@ -27,6 +27,8 @@
 import android.media.AudioManager;
 import android.util.Log;
 
+import com.android.bluetooth.audio_util.BTAudioEventLogger;
+
 import java.util.HashMap;
 import java.util.Map;
 import java.util.Objects;
@@ -37,14 +39,14 @@
 
     // All volumes are stored at system volume values, not AVRCP values
     private static final String VOLUME_MAP = "bluetooth_volume_map";
-    private static final String VOLUME_BLACKLIST = "absolute_volume_blacklist";
+    private static final String VOLUME_REJECTLIST = "absolute_volume_rejectlist";
     private static final String VOLUME_CHANGE_LOG_TITLE = "Volume Events";
     private static final int AVRCP_MAX_VOL = 127;
     private static final int STREAM_MUSIC = AudioManager.STREAM_MUSIC;
     private static final int VOLUME_CHANGE_LOGGER_SIZE = 30;
     private static int sDeviceMaxVolume = 0;
     private static int sNewDeviceVolume = 0;
-    private final AvrcpEventLogger mVolumeEventLogger = new AvrcpEventLogger(
+    private final BTAudioEventLogger mVolumeEventLogger = new BTAudioEventLogger(
             VOLUME_CHANGE_LOGGER_SIZE, VOLUME_CHANGE_LOG_TITLE);
 
     Context mContext;
@@ -135,7 +137,7 @@
     }
 
     synchronized void storeVolumeForDevice(@NonNull BluetoothDevice device) {
-        int storeVolume =  mAudioManager.getStreamVolume(STREAM_MUSIC);
+        int storeVolume =  mAudioManager.getLastAudibleStreamVolume(STREAM_MUSIC);
         storeVolumeForDevice(device, storeVolume);
     }
 
diff --git a/src/com/android/bluetooth/avrcp/helpers/AvrcpVersion.java b/src/com/android/bluetooth/avrcp/helpers/AvrcpVersion.java
new file mode 100644
index 0000000..edb468a
--- /dev/null
+++ b/src/com/android/bluetooth/avrcp/helpers/AvrcpVersion.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.bluetooth.avrcp;
+
+import android.os.SystemProperties;
+
+/**
+ * A class to represent an AVRCP version
+ */
+final class AvrcpVersion {
+    public static final AvrcpVersion AVRCP_VERSION_1_3 = new AvrcpVersion(1, 3);
+    public static final AvrcpVersion AVRCP_VERSION_1_4 = new AvrcpVersion(1, 4);
+    public static final AvrcpVersion AVRCP_VERSION_1_5 = new AvrcpVersion(1, 5);
+    public static final AvrcpVersion AVRCP_VERSION_1_6 = new AvrcpVersion(1, 6);
+
+    // System settings version strings
+    private static final String AVRCP_VERSION_PROPERTY = "persist.bluetooth.avrcpversion";
+    private static final String AVRCP_VERSION_1_3_STRING = "avrcp13";
+    private static final String AVRCP_VERSION_1_4_STRING = "avrcp14";
+    private static final String AVRCP_VERSION_1_5_STRING = "avrcp15";
+    private static final String AVRCP_VERSION_1_6_STRING = "avrcp16";
+
+    public int major;
+    public int minor;
+
+    public static AvrcpVersion getCurrentSystemPropertiesValue() {
+        String version = SystemProperties.get(AVRCP_VERSION_PROPERTY);
+        switch (version) {
+            case AVRCP_VERSION_1_3_STRING:
+                return AVRCP_VERSION_1_3;
+            case AVRCP_VERSION_1_4_STRING:
+                return AVRCP_VERSION_1_4;
+            case AVRCP_VERSION_1_5_STRING:
+                return AVRCP_VERSION_1_5;
+            case AVRCP_VERSION_1_6_STRING:
+                return AVRCP_VERSION_1_6;
+            default:
+                return new AvrcpVersion(-1, -1);
+        }
+    }
+
+    public boolean isAtleastVersion(AvrcpVersion version) {
+        if (version == null) return true;
+        if (major < version.major) return false;
+        if (major > version.major) return true;
+        if (minor < version.minor) return false;
+        if (minor > version.minor) return true;
+        return true;
+    }
+
+    AvrcpVersion(int majorVersion, int minorVersion) {
+        major = majorVersion;
+        minor = minorVersion;
+    }
+
+    public String toString() {
+        if (major < 0 || minor < 0) return "Invalid";
+        return major + "." + minor;
+    }
+}
diff --git a/src/com/android/bluetooth/avrcp/helpers/CoverArt.java b/src/com/android/bluetooth/avrcp/helpers/CoverArt.java
new file mode 100644
index 0000000..8f09949
--- /dev/null
+++ b/src/com/android/bluetooth/avrcp/helpers/CoverArt.java
@@ -0,0 +1,225 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.bluetooth.avrcp;
+
+import android.graphics.Bitmap;
+import android.util.Log;
+
+import com.android.bluetooth.audio_util.Image;
+import com.android.bluetooth.avrcpcontroller.BipEncoding;
+import com.android.bluetooth.avrcpcontroller.BipImageDescriptor;
+import com.android.bluetooth.avrcpcontroller.BipImageFormat;
+import com.android.bluetooth.avrcpcontroller.BipImageProperties;
+import com.android.bluetooth.avrcpcontroller.BipPixel;
+
+import java.io.ByteArrayOutputStream;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+
+/**
+ * An object to represent a piece of cover artwork/
+ *
+ * This object abstracts away the actual storage method and provides a means for others to
+ * understand available formats and get the underlying image in a particular format.
+ *
+ * All return values are ready to use by a BIP server.
+ */
+public class CoverArt {
+    private static final String TAG = "CoverArt";
+    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+    private static final BipPixel PIXEL_THUMBNAIL = BipPixel.createFixed(200, 200);
+
+    private String mImageHandle = null;
+    private Bitmap mImage = null;
+
+    /**
+     * Create a CoverArt object from an audio_util Image abstraction
+     */
+    CoverArt(Image image) {
+        // Create a scaled version of the image for now, as consumers don't need
+        // anything larger than this at the moment. Also makes each image gathered
+        // the same dimensions for hashing purposes.
+        mImage = Bitmap.createScaledBitmap(image.getImage(), 200, 200, false);
+    }
+
+    /**
+     * Get the image handle that has been associated with this image.
+     *
+     * If this returns null then you will fail to generate image properties
+     */
+    public String getImageHandle() {
+        return mImageHandle;
+    }
+
+    /**
+     * Set the image handle that has been associated with this image.
+     *
+     * This is required to generate image properties
+     */
+    public void setImageHandle(String handle) {
+        mImageHandle = handle;
+    }
+
+    /**
+     * Covert a Bitmap to a byte array with an image format without lossy compression
+     */
+    private byte[] toByteArray(Bitmap bitmap) {
+        if (bitmap == null) return null;
+        ByteArrayOutputStream buffer = new ByteArrayOutputStream(
+                    bitmap.getWidth() * bitmap.getHeight());
+        bitmap.compress(Bitmap.CompressFormat.PNG, 100, buffer);
+        return buffer.toByteArray();
+    }
+
+    /**
+     * Get a hash code of this CoverArt image
+     */
+    public String getImageHash() {
+        byte[] image = toByteArray(mImage);
+        if (image == null) return null;
+        String hash = null;
+        try {
+            final byte[] digestBytes;
+            final MessageDigest digest = MessageDigest.getInstance("MD5");
+            digest.update(/* Bitmap to input stream */ image);
+            byte[] messageDigest = digest.digest();
+
+            StringBuffer hexString = new StringBuffer();
+            for (int i = 0; i < messageDigest.length; i++) {
+                hexString.append(Integer.toHexString(0xFF & messageDigest[i]));
+            }
+            hash = hexString.toString();
+        } catch (NoSuchAlgorithmException e) {
+            Log.e(TAG, "Failed to hash bitmap", e);
+        }
+        return hash;
+    }
+
+    /**
+     * Get the cover artwork image bytes in the native format
+     */
+    public byte[] getImage() {
+        debug("GetImage(native)");
+        if (mImage == null) return null;
+        byte[] bytes = null;
+        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+        mImage.compress(Bitmap.CompressFormat.JPEG, 100, outputStream);
+        bytes = outputStream.toByteArray();
+        return bytes;
+    }
+
+    /**
+     * Get the cover artwork image bytes in the given encoding and pixel size
+     */
+    public byte[] getImage(BipImageDescriptor descriptor) {
+        debug("GetImage(descriptor=" + descriptor);
+        if (mImage == null) return null;
+        if (descriptor == null) return getImage();
+        if (!isDescriptorValid(descriptor)) {
+            error("Given format isn't available for this image");
+            return null;
+        }
+
+        byte[] bytes = null;
+        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+        mImage.compress(Bitmap.CompressFormat.JPEG, 100, outputStream);
+        bytes = outputStream.toByteArray();
+        return bytes;
+    }
+
+    /**
+     * Determine if a given image descriptor is valid
+     */
+    private boolean isDescriptorValid(BipImageDescriptor descriptor) {
+        debug("isDescriptorValid(descriptor=" + descriptor + ")");
+        if (descriptor == null) return false;
+
+        BipEncoding encoding = descriptor.getEncoding();
+        BipPixel pixel = descriptor.getPixel();
+
+        if (encoding.getType() == BipEncoding.JPEG && PIXEL_THUMBNAIL.equals(pixel)) {
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Get the cover artwork image bytes as a 200 x 200 JPEG thumbnail
+     */
+    public byte[] getThumbnail() {
+        debug("GetImageThumbnail()");
+        if (mImage == null) return null;
+        byte[] bytes = null;
+        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+        mImage.compress(Bitmap.CompressFormat.JPEG, 100, outputStream);
+        bytes = outputStream.toByteArray();
+        return bytes;
+    }
+
+    /**
+     * Get the set of image properties that the cover artwork can be turned into
+     */
+    public BipImageProperties getImageProperties() {
+        debug("GetImageProperties()");
+        if (mImage == null) {
+            error("Can't associate properties with a null image");
+            return null;
+        }
+        if (mImageHandle == null) {
+            error("No handle has been associated with this image. Cannot build properties.");
+            return null;
+        }
+        BipImageProperties.Builder builder = new BipImageProperties.Builder();
+        BipEncoding encoding = new BipEncoding(BipEncoding.JPEG);
+        BipPixel pixel = BipPixel.createFixed(200, 200);
+        BipImageFormat format = BipImageFormat.createNative(encoding, pixel, -1);
+
+        builder.setImageHandle(mImageHandle);
+        builder.addNativeFormat(format);
+
+        BipImageProperties properties = builder.build();
+        return properties;
+    }
+
+    /**
+     * Get the storage size of this image in bytes
+     */
+    public int size() {
+        return mImage != null ? mImage.getAllocationByteCount() : 0;
+    }
+
+    @Override
+    public String toString() {
+        return "{handle=" + mImageHandle + ", size=" + size() + " }";
+    }
+
+    /**
+     * Print a message to DEBUG if debug output is enabled
+     */
+    private void debug(String msg) {
+        if (DEBUG) {
+            Log.d(TAG, msg);
+        }
+    }
+
+    /**
+     * Print a message to ERROR
+     */
+    private void error(String msg) {
+        Log.e(TAG, msg);
+    }
+}
diff --git a/src/com/android/bluetooth/avrcp/helpers/Metadata.java b/src/com/android/bluetooth/avrcp/helpers/Metadata.java
deleted file mode 100644
index ce97ea0..0000000
--- a/src/com/android/bluetooth/avrcp/helpers/Metadata.java
+++ /dev/null
@@ -1,64 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.bluetooth.avrcp;
-
-import java.util.Objects;
-
-class Metadata implements Cloneable {
-    public String mediaId;
-    public String title;
-    public String artist;
-    public String album;
-    public String trackNum;
-    public String numTracks;
-    public String genre;
-    public String duration;
-
-    @Override
-    public Metadata clone() {
-        Metadata data = new Metadata();
-        data.mediaId = mediaId;
-        data.title = title;
-        data.artist = artist;
-        data.album = album;
-        data.trackNum = trackNum;
-        data.numTracks = numTracks;
-        data.genre = genre;
-        data.duration = duration;
-        return data;
-    }
-
-    @Override
-    public boolean equals(Object o) {
-        if (o == null) return false;
-        if (!(o instanceof Metadata)) return false;
-
-        final Metadata m = (Metadata) o;
-        if (!Objects.equals(title, m.title)) return false;
-        if (!Objects.equals(artist, m.artist)) return false;
-        if (!Objects.equals(album, m.album)) return false;
-        return true;
-    }
-
-    @Override
-    public String toString() {
-        return "{ mediaId=\"" + mediaId + "\" title=\"" + title + "\" artist=\"" + artist
-                + "\" album=\"" + album + "\" duration=" + duration
-                + " trackPosition=" + trackNum + "/" + numTracks + " }";
-
-    }
-}
diff --git a/src/com/android/bluetooth/avrcp/helpers/Util.java b/src/com/android/bluetooth/avrcp/helpers/Util.java
deleted file mode 100644
index e09377b..0000000
--- a/src/com/android/bluetooth/avrcp/helpers/Util.java
+++ /dev/null
@@ -1,240 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.bluetooth.avrcp;
-
-import android.content.Context;
-import android.content.pm.PackageManager;
-import android.media.MediaDescription;
-import android.media.MediaMetadata;
-import android.media.browse.MediaBrowser.MediaItem;
-import android.media.session.MediaSession;
-import android.os.Bundle;
-import android.util.Log;
-
-import java.util.ArrayList;
-import java.util.List;
-
-class Util {
-    public static String TAG = "AvrcpUtil";
-    public static boolean DEBUG = false;
-
-    private static final String GPM_KEY = "com.google.android.music.mediasession.music_metadata";
-
-    // TODO (apanicke): Remove this prefix later, for now it makes debugging easier.
-    public static final String NOW_PLAYING_PREFIX = "NowPlayingId";
-
-    public static final Metadata empty_data() {
-        Metadata ret = new Metadata();
-        ret.mediaId = "Not Provided";
-        ret.title = "Not Provided";
-        ret.artist = "";
-        ret.album = "";
-        ret.genre = "";
-        ret.trackNum = "1";
-        ret.numTracks = "1";
-        ret.duration = "0";
-        return ret;
-    }
-
-    public static Metadata bundleToMetadata(Bundle bundle) {
-        if (bundle == null) return empty_data();
-
-        Metadata temp = new Metadata();
-        temp.title = bundle.getString(MediaMetadata.METADATA_KEY_TITLE, "Not Provided");
-        temp.artist = bundle.getString(MediaMetadata.METADATA_KEY_ARTIST, "");
-        temp.album = bundle.getString(MediaMetadata.METADATA_KEY_ALBUM, "");
-        temp.trackNum = "" + bundle.getLong(MediaMetadata.METADATA_KEY_TRACK_NUMBER, 1);
-        temp.numTracks = "" + bundle.getLong(MediaMetadata.METADATA_KEY_NUM_TRACKS, 1);
-        temp.genre = bundle.getString(MediaMetadata.METADATA_KEY_GENRE, "");
-        temp.duration = "" + bundle.getLong(MediaMetadata.METADATA_KEY_DURATION, 0);
-        return temp;
-    }
-
-    public static Bundle descriptionToBundle(MediaDescription desc) {
-        Bundle ret = new Bundle();
-        if (desc == null) return ret;
-
-        if (desc.getTitle() != null) {
-            ret.putString(MediaMetadata.METADATA_KEY_TITLE, desc.getTitle().toString());
-        }
-
-        if (desc.getSubtitle() != null) {
-            ret.putString(MediaMetadata.METADATA_KEY_ARTIST, desc.getSubtitle().toString());
-        }
-
-        if (desc.getDescription() != null) {
-            ret.putString(MediaMetadata.METADATA_KEY_ALBUM, desc.getDescription().toString());
-        }
-
-        // If the bundle has title or artist use those over the description title or subtitle.
-        if (desc.getExtras() != null) ret.putAll(desc.getExtras());
-
-        if (ret.containsKey(GPM_KEY)) {
-            if (DEBUG) Log.d(TAG, "MediaDescription contains GPM data");
-            ret.putAll(mediaMetadataToBundle((MediaMetadata) ret.get(GPM_KEY)));
-        }
-
-        return ret;
-    }
-
-    public static Bundle mediaMetadataToBundle(MediaMetadata data) {
-        Bundle bundle = new Bundle();
-        if (data == null) return bundle;
-
-        if (data.containsKey(MediaMetadata.METADATA_KEY_TITLE)) {
-            bundle.putString(MediaMetadata.METADATA_KEY_TITLE,
-                    data.getString(MediaMetadata.METADATA_KEY_TITLE));
-        }
-
-        if (data.containsKey(MediaMetadata.METADATA_KEY_ARTIST)) {
-            bundle.putString(MediaMetadata.METADATA_KEY_ARTIST,
-                    data.getString(MediaMetadata.METADATA_KEY_ARTIST));
-        }
-
-        if (data.containsKey(MediaMetadata.METADATA_KEY_ALBUM)) {
-            bundle.putString(MediaMetadata.METADATA_KEY_ALBUM,
-                    data.getString(MediaMetadata.METADATA_KEY_ALBUM));
-        }
-
-        if (data.containsKey(MediaMetadata.METADATA_KEY_TRACK_NUMBER)) {
-            bundle.putLong(MediaMetadata.METADATA_KEY_TRACK_NUMBER,
-                    data.getLong(MediaMetadata.METADATA_KEY_TRACK_NUMBER));
-        }
-
-        if (data.containsKey(MediaMetadata.METADATA_KEY_NUM_TRACKS)) {
-            bundle.putLong(MediaMetadata.METADATA_KEY_NUM_TRACKS,
-                    data.getLong(MediaMetadata.METADATA_KEY_NUM_TRACKS));
-        }
-
-        if (data.containsKey(MediaMetadata.METADATA_KEY_GENRE)) {
-            bundle.putString(MediaMetadata.METADATA_KEY_GENRE,
-                    data.getString(MediaMetadata.METADATA_KEY_GENRE));
-        }
-
-        if (data.containsKey(MediaMetadata.METADATA_KEY_DURATION)) {
-            bundle.putLong(MediaMetadata.METADATA_KEY_DURATION,
-                    data.getLong(MediaMetadata.METADATA_KEY_DURATION));
-        }
-
-        return bundle;
-    }
-
-    public static Metadata toMetadata(MediaSession.QueueItem item) {
-        if (item == null) {
-            return empty_data();
-        }
-
-        Bundle bundle = descriptionToBundle(item.getDescription());
-
-        if (DEBUG) {
-            for (String key : bundle.keySet()) {
-                Log.d(TAG, "toMetadata: QueueItem: ContainsKey: " + key);
-            }
-        }
-
-        Metadata ret = bundleToMetadata(bundle);
-
-        // For Queue Items, the Media Id will always be just its Queue ID
-        // We don't need to use its actual ID since we don't promise UIDS being valid
-        // between a file system and it's now playing list.
-        ret.mediaId = NOW_PLAYING_PREFIX + item.getQueueId();
-
-        return ret;
-    }
-
-    public static Metadata toMetadata(MediaMetadata data) {
-        if (data == null) {
-            return empty_data();
-        }
-
-        MediaDescription desc = data.getDescription();
-
-        Bundle dataBundle = mediaMetadataToBundle(data);
-        Bundle bundle = descriptionToBundle(data.getDescription());
-
-        // Prioritize the media metadata over the media description
-        bundle.putAll(dataBundle);
-
-        if (DEBUG) {
-            for (String key : bundle.keySet()) {
-                Log.d(TAG, "toMetadata: MediaMetadata: ContainsKey: " + key);
-            }
-        }
-
-        Metadata ret = bundleToMetadata(bundle);
-
-        // This will always be currsong. The AVRCP service will overwrite the mediaId if it needs to
-        // TODO (apanicke): Remove when the service is ready, right now it makes debugging much more
-        // convenient
-        ret.mediaId = "currsong";
-
-        return ret;
-    }
-
-    public static Metadata toMetadata(MediaItem item) {
-        if (item == null) {
-            return empty_data();
-        }
-
-        Bundle bundle = descriptionToBundle(item.getDescription());
-
-        if (DEBUG) {
-            for (String key : bundle.keySet()) {
-                Log.d(TAG, "toMetadata: MediaItem: ContainsKey: " + key);
-            }
-        }
-
-        Metadata ret = bundleToMetadata(bundle);
-        ret.mediaId = item.getMediaId();
-
-        return ret;
-    }
-
-    public static List<Metadata> toMetadataList(List<MediaSession.QueueItem> items) {
-        ArrayList<Metadata> list = new ArrayList<Metadata>();
-
-        if (items == null) return list;
-
-        for (int i = 0; i < items.size(); i++) {
-            Metadata data = toMetadata(items.get(i));
-            data.trackNum = "" + (i + 1);
-            data.numTracks = "" + items.size();
-            list.add(data);
-        }
-
-        return list;
-    }
-
-    // Helper method to close a list of ListItems so that if the callee wants
-    // to mutate the list they can do it without affecting any internally cached info
-    public static List<ListItem> cloneList(List<ListItem> list) {
-        List<ListItem> clone = new ArrayList<ListItem>(list.size());
-        for (ListItem item : list) clone.add(item.clone());
-        return clone;
-    }
-
-    public static String getDisplayName(Context context, String packageName) {
-        try {
-            PackageManager manager = context.getPackageManager();
-            return manager.getApplicationLabel(manager.getApplicationInfo(packageName, 0))
-                    .toString();
-        } catch (Exception e) {
-            Log.w(TAG, "Name Not Found using package name: " + packageName);
-            return packageName;
-        }
-    }
-}
diff --git a/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerService.java b/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerService.java
index b6907ec..90cb326 100755
--- a/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerService.java
+++ b/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerService.java
@@ -16,11 +16,14 @@
 
 package com.android.bluetooth.avrcpcontroller;
 
+import android.annotation.RequiresPermission;
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothAvrcpPlayerSettings;
 import android.bluetooth.BluetoothDevice;
 import android.bluetooth.BluetoothProfile;
 import android.bluetooth.IBluetoothAvrcpController;
+import android.content.Attributable;
+import android.content.AttributionSource;
 import android.content.Intent;
 import android.support.v4.media.MediaBrowserCompat.MediaItem;
 import android.support.v4.media.session.PlaybackStateCompat;
@@ -28,10 +31,13 @@
 
 import com.android.bluetooth.R;
 import com.android.bluetooth.Utils;
+import com.android.bluetooth.a2dpsink.A2dpSinkService;
+import com.android.bluetooth.btservice.AdapterService;
 import com.android.bluetooth.btservice.ProfileService;
 
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collections;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
@@ -88,12 +94,19 @@
     public static final int KEY_STATE_PRESSED = 0;
     public static final int KEY_STATE_RELEASED = 1;
 
+    /* Active Device State Variables */
+    public static final int DEVICE_STATE_INACTIVE = 0;
+    public static final int DEVICE_STATE_ACTIVE = 1;
+
     static BrowseTree sBrowseTree;
     private static AvrcpControllerService sService;
-    private final BluetoothAdapter mAdapter;
+
+    private AdapterService mAdapterService;
 
     protected Map<BluetoothDevice, AvrcpControllerStateMachine> mDeviceStateMap =
             new ConcurrentHashMap<>(1);
+    private BluetoothDevice mActiveDevice = null;
+    private final Object mActiveDeviceLock = new Object();
 
     private boolean mCoverArtEnabled = false;
     protected AvrcpCoverArtManager mCoverArtManager;
@@ -121,14 +134,10 @@
         classInitNative();
     }
 
-    public AvrcpControllerService() {
-        super();
-        mAdapter = BluetoothAdapter.getDefaultAdapter();
-    }
-
     @Override
     protected synchronized boolean start() {
         initNative();
+        mAdapterService = AdapterService.getAdapterService();
         mCoverArtEnabled = getResources().getBoolean(R.bool.avrcp_controller_enable_cover_art);
         if (mCoverArtEnabled) {
             mCoverArtManager = new AvrcpCoverArtManager(this, new ImageDownloadCallback());
@@ -139,11 +148,13 @@
         // Start the media browser service.
         Intent startIntent = new Intent(this, BluetoothMediaBrowserService.class);
         startService(startIntent);
+        setActiveDevice(null);
         return true;
     }
 
     @Override
     protected synchronized boolean stop() {
+        setActiveDevice(null);
         Intent stopIntent = new Intent(this, BluetoothMediaBrowserService.class);
         stopService(stopIntent);
         for (AvrcpControllerStateMachine stateMachine : mDeviceStateMap.values()) {
@@ -163,6 +174,80 @@
         return sService;
     }
 
+    /**
+     * Get the current active device
+     */
+    public BluetoothDevice getActiveDevice() {
+        synchronized (mActiveDeviceLock) {
+            return mActiveDevice;
+        }
+    }
+
+    /**
+     * Set the current active device, notify devices of activity status
+     */
+    private boolean setActiveDevice(BluetoothDevice device) {
+        A2dpSinkService a2dpSinkService = A2dpSinkService.getA2dpSinkService();
+        if (a2dpSinkService == null) {
+            return false;
+        }
+
+        BluetoothDevice currentActiveDevice = getActiveDevice();
+        if ((device == null && currentActiveDevice == null)
+                || (device != null && device.equals(currentActiveDevice))) {
+            return true;
+        }
+
+        // Try and update the active device
+        synchronized (mActiveDeviceLock) {
+            if (a2dpSinkService.setActiveDevice(device)) {
+                mActiveDevice = device;
+
+                // Pause the old active device
+                if (currentActiveDevice != null) {
+                    AvrcpControllerStateMachine oldStateMachine =
+                            getStateMachine(currentActiveDevice);
+                    if (oldStateMachine != null) {
+                        oldStateMachine.setDeviceState(DEVICE_STATE_INACTIVE);
+                    }
+                }
+
+                AvrcpControllerStateMachine stateMachine = getStateMachine(device);
+                if (stateMachine != null) {
+                    stateMachine.setDeviceState(DEVICE_STATE_ACTIVE);
+                } else {
+                    BluetoothMediaBrowserService.reset();
+                }
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private int toPlaybackStateFromJni(int fromJni) {
+        int playbackState = PlaybackStateCompat.STATE_NONE;
+        switch (fromJni) {
+            case JNI_PLAY_STATUS_STOPPED:
+                playbackState = PlaybackStateCompat.STATE_STOPPED;
+                break;
+            case JNI_PLAY_STATUS_PLAYING:
+                playbackState = PlaybackStateCompat.STATE_PLAYING;
+                break;
+            case JNI_PLAY_STATUS_PAUSED:
+                playbackState = PlaybackStateCompat.STATE_PAUSED;
+                break;
+            case JNI_PLAY_STATUS_FWD_SEEK:
+                playbackState = PlaybackStateCompat.STATE_FAST_FORWARDING;
+                break;
+            case JNI_PLAY_STATUS_REV_SEEK:
+                playbackState = PlaybackStateCompat.STATE_REWINDING;
+                break;
+            default:
+                playbackState = PlaybackStateCompat.STATE_NONE;
+        }
+        return playbackState;
+    }
+
     protected AvrcpControllerStateMachine newStateMachine(BluetoothDevice device) {
         return new AvrcpControllerStateMachine(device, this);
     }
@@ -198,6 +283,10 @@
                 requestedNode = stateMachine.findNode(parentMediaId);
                 if (requestedNode != null) {
                     if (DBG) Log.d(TAG, "Found a node");
+                    BluetoothDevice device = stateMachine.getDevice();
+                    if (device != null) {
+                        setActiveDevice(device);
+                    }
                     stateMachine.playItem(requestedNode);
                     break;
                 }
@@ -234,6 +323,12 @@
             if (DBG) Log.d(TAG, "Didn't find a node");
             return new ArrayList(0);
         } else {
+            // If we found a node and it belongs to a device then go ahead and make it active
+            BluetoothDevice device = requestedNode.getDevice();
+            if (device != null) {
+                setActiveDevice(device);
+            }
+
             if (!requestedNode.isCached()) {
                 if (DBG) Log.d(TAG, "node is not cached");
                 refreshContents(requestedNode);
@@ -253,16 +348,14 @@
             implements IProfileServiceBinder {
         private AvrcpControllerService mService;
 
-        private AvrcpControllerService getService() {
-            if (!Utils.checkCaller()) {
-                Log.w(TAG, "AVRCP call not allowed for non-active user");
+        @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
+        private AvrcpControllerService getService(AttributionSource source) {
+            if (!Utils.checkCallerIsSystemOrActiveUser(TAG)
+                    || !Utils.checkServiceAvailable(mService, TAG)
+                    || !Utils.checkConnectPermissionForDataDelivery(mService, source, TAG)) {
                 return null;
             }
-
-            if (mService != null) {
-                return mService;
-            }
-            return null;
+            return mService;
         }
 
         AvrcpControllerServiceBinder(AvrcpControllerService service) {
@@ -275,8 +368,8 @@
         }
 
         @Override
-        public List<BluetoothDevice> getConnectedDevices() {
-            AvrcpControllerService service = getService();
+        public List<BluetoothDevice> getConnectedDevices(AttributionSource source) {
+            AvrcpControllerService service = getService(source);
             if (service == null) {
                 return new ArrayList<BluetoothDevice>(0);
             }
@@ -284,8 +377,9 @@
         }
 
         @Override
-        public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
-            AvrcpControllerService service = getService();
+        public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states,
+                AttributionSource source) {
+            AvrcpControllerService service = getService(source);
             if (service == null) {
                 return new ArrayList<BluetoothDevice>(0);
             }
@@ -293,8 +387,9 @@
         }
 
         @Override
-        public int getConnectionState(BluetoothDevice device) {
-            AvrcpControllerService service = getService();
+        public int getConnectionState(BluetoothDevice device, AttributionSource source) {
+            Attributable.setAttributionSource(device, source);
+            AvrcpControllerService service = getService(source);
             if (service == null) {
                 return BluetoothProfile.STATE_DISCONNECTED;
             }
@@ -302,19 +397,35 @@
         }
 
         @Override
-        public void sendGroupNavigationCmd(BluetoothDevice device, int keyCode, int keyState) {
+        public void sendGroupNavigationCmd(BluetoothDevice device, int keyCode, int keyState,
+                AttributionSource source) {
+            Attributable.setAttributionSource(device, source);
+            AvrcpControllerService service = getService(source);
+            if (service == null) {
+                return;
+            }
             Log.w(TAG, "sendGroupNavigationCmd not implemented");
-            return;
         }
 
         @Override
-        public boolean setPlayerApplicationSetting(BluetoothAvrcpPlayerSettings settings) {
+        public boolean setPlayerApplicationSetting(BluetoothAvrcpPlayerSettings settings,
+                AttributionSource source) {
+            AvrcpControllerService service = getService(source);
+            if (service == null) {
+                return false;
+            }
             Log.w(TAG, "setPlayerApplicationSetting not implemented");
             return false;
         }
 
         @Override
-        public BluetoothAvrcpPlayerSettings getPlayerSettings(BluetoothDevice device) {
+        public BluetoothAvrcpPlayerSettings getPlayerSettings(BluetoothDevice device,
+                AttributionSource source) {
+            Attributable.setAttributionSource(device, source);
+            AvrcpControllerService service = getService(source);
+            if (service == null) {
+                return null;
+            }
             Log.w(TAG, "getPlayerSettings not implemented");
             return null;
         }
@@ -340,7 +451,7 @@
     // Called by JNI when a device has connected or disconnected.
     private synchronized void onConnectionStateChanged(boolean remoteControlConnected,
             boolean browsingConnected, byte[] address) {
-        BluetoothDevice device = mAdapter.getRemoteDevice(address);
+        BluetoothDevice device = getAnonymousDevice(address);
         if (DBG) {
             Log.d(TAG, "onConnectionStateChanged " + remoteControlConnected + " "
                     + browsingConnected + device);
@@ -355,8 +466,15 @@
         AvrcpControllerStateMachine stateMachine = getOrCreateStateMachine(device);
         if (remoteControlConnected || browsingConnected) {
             stateMachine.connect(event);
+            // The first device to connect gets to be the active device
+            if (getActiveDevice() == null) {
+                setActiveDevice(device);
+            }
         } else {
             stateMachine.disconnect();
+            if (device.equals(getActiveDevice())) {
+                setActiveDevice(null);
+            }
         }
     }
 
@@ -367,7 +485,7 @@
 
     // Called by JNI to notify Avrcp of a remote device's Cover Art PSM
     private void getRcPsm(byte[] address, int psm) {
-        BluetoothDevice device = mAdapter.getRemoteDevice(address);
+        BluetoothDevice device = getAnonymousDevice(address);
         if (DBG) Log.d(TAG, "getRcPsm(device=" + device + ", psm=" + psm + ")");
         AvrcpControllerStateMachine stateMachine = getOrCreateStateMachine(device);
         if (stateMachine != null) {
@@ -386,7 +504,7 @@
         if (DBG) {
             Log.d(TAG, "handleRegisterNotificationAbsVol");
         }
-        BluetoothDevice device = mAdapter.getRemoteDevice(address);
+        BluetoothDevice device = getAnonymousDevice(address);
         AvrcpControllerStateMachine stateMachine = getStateMachine(device);
         if (stateMachine != null) {
             stateMachine.sendMessage(
@@ -399,7 +517,7 @@
         if (DBG) {
             Log.d(TAG, "handleSetAbsVolume ");
         }
-        BluetoothDevice device = mAdapter.getRemoteDevice(address);
+        BluetoothDevice device = getAnonymousDevice(address);
         AvrcpControllerStateMachine stateMachine = getStateMachine(device);
         if (stateMachine != null) {
             stateMachine.sendMessage(AvrcpControllerStateMachine.MESSAGE_PROCESS_SET_ABS_VOL_CMD,
@@ -414,7 +532,7 @@
             Log.d(TAG, "onTrackChanged");
         }
 
-        BluetoothDevice device = mAdapter.getRemoteDevice(address);
+        BluetoothDevice device = getAnonymousDevice(address);
         AvrcpControllerStateMachine stateMachine = getStateMachine(device);
         if (stateMachine != null) {
             AvrcpItem.Builder aib = new AvrcpItem.Builder();
@@ -440,7 +558,7 @@
         if (DBG) {
             Log.d(TAG, "onPlayPositionChanged pos " + currSongPosition);
         }
-        BluetoothDevice device = mAdapter.getRemoteDevice(address);
+        BluetoothDevice device = getAnonymousDevice(address);
         AvrcpControllerStateMachine stateMachine = getStateMachine(device);
         if (stateMachine != null) {
             stateMachine.sendMessage(
@@ -454,31 +572,12 @@
         if (DBG) {
             Log.d(TAG, "onPlayStatusChanged " + playStatus);
         }
-        int playbackState = PlaybackStateCompat.STATE_NONE;
-        switch (playStatus) {
-            case JNI_PLAY_STATUS_STOPPED:
-                playbackState = PlaybackStateCompat.STATE_STOPPED;
-                break;
-            case JNI_PLAY_STATUS_PLAYING:
-                playbackState = PlaybackStateCompat.STATE_PLAYING;
-                break;
-            case JNI_PLAY_STATUS_PAUSED:
-                playbackState = PlaybackStateCompat.STATE_PAUSED;
-                break;
-            case JNI_PLAY_STATUS_FWD_SEEK:
-                playbackState = PlaybackStateCompat.STATE_FAST_FORWARDING;
-                break;
-            case JNI_PLAY_STATUS_REV_SEEK:
-                playbackState = PlaybackStateCompat.STATE_REWINDING;
-                break;
-            default:
-                playbackState = PlaybackStateCompat.STATE_NONE;
-        }
-        BluetoothDevice device = mAdapter.getRemoteDevice(address);
+        BluetoothDevice device = getAnonymousDevice(address);
         AvrcpControllerStateMachine stateMachine = getStateMachine(device);
         if (stateMachine != null) {
             stateMachine.sendMessage(
-                    AvrcpControllerStateMachine.MESSAGE_PROCESS_PLAY_STATUS_CHANGED, playbackState);
+                    AvrcpControllerStateMachine.MESSAGE_PROCESS_PLAY_STATUS_CHANGED,
+                    toPlaybackStateFromJni(playStatus));
         }
     }
 
@@ -488,7 +587,7 @@
         if (DBG) {
             Log.d(TAG, "handlePlayerAppSetting rspLen = " + rspLen);
         }
-        BluetoothDevice device = mAdapter.getRemoteDevice(address);
+        BluetoothDevice device = getAnonymousDevice(address);
         AvrcpControllerStateMachine stateMachine = getStateMachine(device);
         if (stateMachine != null) {
             PlayerApplicationSettings supportedSettings =
@@ -504,7 +603,7 @@
         if (DBG) {
             Log.d(TAG, "onPlayerAppSettingChanged ");
         }
-        BluetoothDevice device = mAdapter.getRemoteDevice(address);
+        BluetoothDevice device = getAnonymousDevice(address);
         AvrcpControllerStateMachine stateMachine = getStateMachine(device);
         if (stateMachine != null) {
 
@@ -520,7 +619,7 @@
         if (DBG) {
             Log.d(TAG," onAvailablePlayerChanged");
         }
-        BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address);
+        BluetoothDevice device = getAnonymousDevice(address);
 
         AvrcpControllerStateMachine stateMachine = getStateMachine(device);
         if (stateMachine != null) {
@@ -535,7 +634,7 @@
                     + items.length + " items.");
         }
 
-        BluetoothDevice device = mAdapter.getRemoteDevice(address);
+        BluetoothDevice device = getAnonymousDevice(address);
         List<AvrcpItem> itemsList = new ArrayList<>();
         for (AvrcpItem item : items) {
             if (VDBG) Log.d(TAG, item.toString());
@@ -566,7 +665,7 @@
             itemsList.add(item);
         }
 
-        BluetoothDevice device = mAdapter.getRemoteDevice(address);
+        BluetoothDevice device = getAnonymousDevice(address);
         AvrcpControllerStateMachine stateMachine = getStateMachine(device);
         if (stateMachine != null) {
             stateMachine.sendMessage(AvrcpControllerStateMachine.MESSAGE_PROCESS_GET_PLAYER_ITEMS,
@@ -582,7 +681,7 @@
                     + " attrids: " + attrIds + " attrVals: " + attrVals);
         }
 
-        BluetoothDevice device = mAdapter.getRemoteDevice(address);
+        BluetoothDevice device = getAnonymousDevice(address);
         AvrcpItem.Builder aib = new AvrcpItem.Builder().fromAvrcpAttributeArray(attrIds, attrVals);
         aib.setDevice(device);
         aib.setItemType(AvrcpItem.TYPE_MEDIA);
@@ -601,7 +700,7 @@
                     + name + " playable " + playable);
         }
 
-        BluetoothDevice device = mAdapter.getRemoteDevice(address);
+        BluetoothDevice device = getAnonymousDevice(address);
         AvrcpItem.Builder aib = new AvrcpItem.Builder();
         aib.setDevice(device);
         aib.setItemType(AvrcpItem.TYPE_FOLDER);
@@ -622,17 +721,22 @@
                             + transportFlags + " play status " + playStatus + " player type "
                             + playerType);
         }
-        BluetoothDevice device = mAdapter.getRemoteDevice(address);
-        AvrcpPlayer player = new AvrcpPlayer(device, id, name, transportFlags, playStatus,
-                playerType);
-        return player;
+        BluetoothDevice device = getAnonymousDevice(address);
+        AvrcpPlayer.Builder apb = new AvrcpPlayer.Builder();
+        apb.setDevice(device);
+        apb.setPlayerId(id);
+        apb.setPlayerType(playerType);
+        apb.setSupportedFeatures(transportFlags);
+        apb.setName(name);
+        apb.setPlayStatus(toPlaybackStateFromJni(playStatus));
+        return apb.build();
     }
 
     private void handleChangeFolderRsp(byte[] address, int count) {
         if (DBG) {
             Log.d(TAG, "handleChangeFolderRsp count: " + count);
         }
-        BluetoothDevice device = mAdapter.getRemoteDevice(address);
+        BluetoothDevice device = getAnonymousDevice(address);
         AvrcpControllerStateMachine stateMachine = getStateMachine(device);
         if (stateMachine != null) {
             stateMachine.sendMessage(AvrcpControllerStateMachine.MESSAGE_PROCESS_FOLDER_PATH,
@@ -644,7 +748,7 @@
         if (DBG) {
             Log.d(TAG, "handleSetBrowsedPlayerRsp depth: " + depth);
         }
-        BluetoothDevice device = mAdapter.getRemoteDevice(address);
+        BluetoothDevice device = getAnonymousDevice(address);
 
         AvrcpControllerStateMachine stateMachine = getStateMachine(device);
         if (stateMachine != null) {
@@ -657,7 +761,7 @@
         if (DBG) {
             Log.d(TAG, "handleSetAddressedPlayerRsp status: " + status);
         }
-        BluetoothDevice device = mAdapter.getRemoteDevice(address);
+        BluetoothDevice device = getAnonymousDevice(address);
 
         AvrcpControllerStateMachine stateMachine = getStateMachine(device);
         if (stateMachine != null) {
@@ -670,7 +774,7 @@
         if (DBG) {
             Log.d(TAG, "handleAddressedPlayerChanged id: " + id);
         }
-        BluetoothDevice device = mAdapter.getRemoteDevice(address);
+        BluetoothDevice device = getAnonymousDevice(address);
 
         AvrcpControllerStateMachine stateMachine = getStateMachine(device);
         if (stateMachine != null) {
@@ -683,7 +787,7 @@
         if (DBG) {
             Log.d(TAG, "handleNowPlayingContentChanged");
         }
-        BluetoothDevice device = mAdapter.getRemoteDevice(address);
+        BluetoothDevice device = getAnonymousDevice(address);
 
         AvrcpControllerStateMachine stateMachine = getStateMachine(device);
         if (stateMachine != null) {
@@ -729,6 +833,10 @@
      * Remove state machine from device map once it is no longer needed.
      */
     public void removeStateMachine(AvrcpControllerStateMachine stateMachine) {
+        BluetoothDevice device = stateMachine.getDevice();
+        if (device.equals(getActiveDevice())) {
+            setActiveDevice(null);
+        }
         mDeviceStateMap.remove(stateMachine.getDevice());
     }
 
@@ -737,6 +845,9 @@
     }
 
     protected AvrcpControllerStateMachine getStateMachine(BluetoothDevice device) {
+        if (device == null) {
+            return null;
+        }
         return mDeviceStateMap.get(device);
     }
 
@@ -757,7 +868,7 @@
     List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
         if (DBG) Log.d(TAG, "getDevicesMatchingConnectionStates" + Arrays.toString(states));
         List<BluetoothDevice> deviceList = new ArrayList<>();
-        Set<BluetoothDevice> bondedDevices = mAdapter.getBondedDevices();
+        BluetoothDevice[] bondedDevices = mAdapterService.getBondedDevices();
         int connectionState;
         for (BluetoothDevice device : bondedDevices) {
             connectionState = getConnectionState(device);
@@ -783,6 +894,7 @@
     public void dump(StringBuilder sb) {
         super.dump(sb);
         ProfileService.println(sb, "Devices Tracked = " + mDeviceStateMap.size());
+        ProfileService.println(sb, "Active Device = " + mActiveDevice);
 
         for (AvrcpControllerStateMachine stateMachine : mDeviceStateMap.values()) {
             ProfileService.println(sb,
@@ -795,6 +907,8 @@
         if (mCoverArtManager != null) {
             sb.append("\n  " + mCoverArtManager.toString());
         }
+
+        sb.append("\n  " + BluetoothMediaBrowserService.dump() + "\n");
     }
 
     /*JNI*/
diff --git a/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerStateMachine.java b/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerStateMachine.java
index e26dc47..96f4f82 100755
--- a/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerStateMachine.java
+++ b/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerStateMachine.java
@@ -16,6 +16,8 @@
 
 package com.android.bluetooth.avrcpcontroller;
 
+import static android.Manifest.permission.BLUETOOTH_CONNECT;
+
 import android.bluetooth.BluetoothAvrcpController;
 import android.bluetooth.BluetoothDevice;
 import android.bluetooth.BluetoothProfile;
@@ -25,6 +27,7 @@
 import android.net.Uri;
 import android.os.Bundle;
 import android.os.Message;
+import android.support.v4.media.MediaBrowserCompat.MediaItem;
 import android.support.v4.media.session.MediaSessionCompat;
 import android.support.v4.media.session.PlaybackStateCompat;
 import android.util.Log;
@@ -55,6 +58,7 @@
     //0->99 Events from Outside
     public static final int CONNECT = 1;
     public static final int DISCONNECT = 2;
+    public static final int ACTIVE_DEVICE_CHANGE = 3;
 
     //100->199 Internal Events
     protected static final int CLEANUP = 100;
@@ -124,9 +128,11 @@
     boolean mRemoteControlConnected = false;
     boolean mBrowsingConnected = false;
     final BrowseTree mBrowseTree;
-    private AvrcpPlayer mAddressedPlayer = new AvrcpPlayer();
-    private int mAddressedPlayerId = -1;
-    private SparseArray<AvrcpPlayer> mAvailablePlayerList = new SparseArray<AvrcpPlayer>();
+
+    private AvrcpPlayer mAddressedPlayer;
+    private int mAddressedPlayerId;
+    private SparseArray<AvrcpPlayer> mAvailablePlayerList;
+
     private int mVolumeChangedNotificationsToIgnore = 0;
     private int mVolumeNotificationLabel = -1;
 
@@ -146,6 +152,20 @@
         mCoverArtManager = service.getCoverArtManager();
         logD(device.toString());
 
+        mAvailablePlayerList = new SparseArray<AvrcpPlayer>();
+        mAddressedPlayerId = AvrcpPlayer.DEFAULT_ID;
+
+        AvrcpPlayer.Builder apb = new AvrcpPlayer.Builder();
+        apb.setDevice(mDevice);
+        apb.setPlayerId(mAddressedPlayerId);
+        apb.setSupportedFeature(AvrcpPlayer.FEATURE_PLAY);
+        apb.setSupportedFeature(AvrcpPlayer.FEATURE_PAUSE);
+        apb.setSupportedFeature(AvrcpPlayer.FEATURE_STOP);
+        apb.setSupportedFeature(AvrcpPlayer.FEATURE_FORWARD);
+        apb.setSupportedFeature(AvrcpPlayer.FEATURE_PREVIOUS);
+        mAddressedPlayer = apb.build();
+        mAvailablePlayerList.put(mAddressedPlayerId, mAddressedPlayer);
+
         mBrowseTree = new BrowseTree(mDevice);
         mDisconnected = new Disconnected();
         mConnecting = new Connecting();
@@ -184,7 +204,7 @@
      *
      * @return device in focus
      */
-    public synchronized BluetoothDevice getDevice() {
+    public BluetoothDevice getDevice() {
         return mDevice;
     }
 
@@ -214,6 +234,16 @@
         return mAddressedPlayer.getCurrentTrack();
     }
 
+    @VisibleForTesting
+    int getAddressedPlayerId() {
+        return mAddressedPlayerId;
+    }
+
+    @VisibleForTesting
+    SparseArray<AvrcpPlayer> getAvailablePlayers() {
+        return mAvailablePlayerList;
+    }
+
     /**
      * Dump the current State Machine to the string builder.
      *
@@ -221,57 +251,38 @@
      */
     public void dump(StringBuilder sb) {
         ProfileService.println(sb, "mDevice: " + mDevice.getAddress() + "("
-                + mDevice.getName() + ") " + this.toString());
+                + Utils.getName(mDevice) + ") " + this.toString());
         ProfileService.println(sb, "isActive: " + isActive());
+        ProfileService.println(sb, "Control: " + mRemoteControlConnected);
+        ProfileService.println(sb, "Browsing: " + mBrowsingConnected);
+        ProfileService.println(sb, "Cover Art: "
+                + (mCoverArtManager.getState(mDevice) == BluetoothProfile.STATE_CONNECTED));
+
+        ProfileService.println(sb, "Addressed Player ID: " + mAddressedPlayerId);
+        ProfileService.println(sb, "Available Players (" + mAvailablePlayerList.size() + "): ");
+        for (int i = 0; i < mAvailablePlayerList.size(); i++) {
+            AvrcpPlayer player = mAvailablePlayerList.valueAt(i);
+            boolean isAddressed = (player.getId() == mAddressedPlayerId);
+            ProfileService.println(sb, "\t" + (isAddressed ? "(Addressed) " : "") + player);
+        }
+
+        List<MediaItem> queue = null;
+        if (mBrowseTree.mNowPlayingNode != null) {
+            queue = mBrowseTree.mNowPlayingNode.getContents();
+        }
+        ProfileService.println(sb, "Queue (" + (queue == null ? 0 : queue.size()) + "): " + queue);
     }
 
     @VisibleForTesting
     boolean isActive() {
-        return mDevice == sActiveDevice;
-    }
-
-    /*
-     * requestActive
-     *
-     * Set the current device active if nothing an already connected device isn't playing
-     */
-    private boolean requestActive() {
-        if (sActiveDevice == null
-                || BluetoothMediaBrowserService.getPlaybackState()
-                != PlaybackStateCompat.STATE_PLAYING) {
-            return setActive(true);
-        }
-        return false;
+        return mDevice.equals(mService.getActiveDevice());
     }
 
     /**
      * Attempt to set the active status for this device
      */
-    boolean setActive(boolean becomeActive) {
-        logD("setActive(" + becomeActive + ")");
-        if (becomeActive) {
-            if (isActive()) {
-                return true;
-            }
-
-            A2dpSinkService a2dpSinkService = A2dpSinkService.getA2dpSinkService();
-            if (a2dpSinkService == null) {
-                return false;
-            }
-
-            if (a2dpSinkService.setActiveDeviceNative(mDeviceAddress)) {
-                sActiveDevice = mDevice;
-                BluetoothMediaBrowserService.addressedPlayerChanged(mSessionCallbacks);
-                BluetoothMediaBrowserService.notifyChanged(mAddressedPlayer.getPlaybackState());
-                BluetoothMediaBrowserService.notifyChanged(mBrowseTree.mNowPlayingNode);
-            }
-            return mDevice == sActiveDevice;
-        } else if (isActive()) {
-            sActiveDevice = null;
-            BluetoothMediaBrowserService.trackChanged(null);
-            BluetoothMediaBrowserService.addressedPlayerChanged(null);
-        }
-        return true;
+    public void setDeviceState(int state) {
+        sendMessage(ACTIVE_DEVICE_CHANGE, state);
     }
 
     @Override
@@ -286,11 +297,8 @@
     }
 
     synchronized void onBrowsingConnected() {
-        if (mBrowsingConnected) return;
-        mService.sBrowseTree.mRootNode.addChild(mBrowseTree.mRootNode);
-        BluetoothMediaBrowserService.notifyChanged(mService
-                .sBrowseTree.mRootNode);
         mBrowsingConnected = true;
+        requestContents(mBrowseTree.mRootNode);
     }
 
     synchronized void onBrowsingDisconnected() {
@@ -300,13 +308,11 @@
         String previousTrackUuid = previousTrack != null ? previousTrack.getCoverArtUuid() : null;
         mAddressedPlayer.updateCurrentTrack(null);
         mBrowseTree.mNowPlayingNode.setCached(false);
+        mBrowseTree.mRootNode.setCached(false);
         if (isActive()) {
             BluetoothMediaBrowserService.notifyChanged(mBrowseTree.mNowPlayingNode);
+            BluetoothMediaBrowserService.notifyChanged(mBrowseTree.mRootNode);
         }
-        mService.sBrowseTree.mRootNode.removeChild(
-                mBrowseTree.mRootNode);
-        BluetoothMediaBrowserService.notifyChanged(mService
-                .sBrowseTree.mRootNode);
         removeUnusedArtwork(previousTrackUuid);
         removeUnusedArtworkFromBrowseTree();
         mBrowsingConnected = false;
@@ -426,6 +432,10 @@
                 case CLEANUP:
                     mService.removeStateMachine(AvrcpControllerStateMachine.this);
                     break;
+                case ACTIVE_DEVICE_CHANGE:
+                    // Wait until we're connected to process this
+                    deferMessage(message);
+                    break;
             }
             return true;
         }
@@ -448,8 +458,9 @@
         @Override
         public void enter() {
             if (mMostRecentState == BluetoothProfile.STATE_CONNECTING) {
-                requestActive();
                 broadcastConnectionStateChanged(BluetoothProfile.STATE_CONNECTED);
+                mService.sBrowseTree.mRootNode.addChild(mBrowseTree.mRootNode);
+                BluetoothMediaBrowserService.notifyChanged(mService.sBrowseTree.mRootNode);
                 connectCoverArt(); // only works if we have a valid PSM
             } else {
                 logD("ReEnteringConnected");
@@ -461,6 +472,21 @@
         public boolean processMessage(Message msg) {
             logD(STATE_TAG + " processMessage " + msg.what);
             switch (msg.what) {
+                case ACTIVE_DEVICE_CHANGE:
+                    int state = msg.arg1;
+                    if (state == AvrcpControllerService.DEVICE_STATE_ACTIVE) {
+                        BluetoothMediaBrowserService.addressedPlayerChanged(mSessionCallbacks);
+                        BluetoothMediaBrowserService.trackChanged(
+                                mAddressedPlayer.getCurrentTrack());
+                        BluetoothMediaBrowserService.notifyChanged(
+                                mAddressedPlayer.getPlaybackState());
+                        BluetoothMediaBrowserService.notifyChanged(mBrowseTree.mNowPlayingNode);
+                    } else {
+                        sendMessage(MSG_AVRCP_PASSTHRU,
+                                AvrcpControllerService.PASS_THRU_CMD_ID_PAUSE);
+                    }
+                    return true;
+
                 case MESSAGE_PROCESS_SET_ABS_VOL_CMD:
                     mVolumeChangedNotificationsToIgnore++;
                     removeMessages(MESSAGE_INTERNAL_ABS_VOL_TIMEOUT);
@@ -553,8 +579,10 @@
                     return true;
 
                 case MESSAGE_PROCESS_ADDRESSED_PLAYER_CHANGED:
+                    int oldAddressedPlayerId = mAddressedPlayerId;
                     mAddressedPlayerId = msg.arg1;
-                    logD("AddressedPlayer = " + mAddressedPlayerId);
+                    logD("AddressedPlayer changed " + oldAddressedPlayerId + " -> "
+                            + mAddressedPlayerId);
 
                     // The now playing list is tied to the addressed player by specification in
                     // AVRCP 5.9.1. A new addressed player means our now playing content is now
@@ -563,22 +591,37 @@
                     if (isActive()) {
                         BluetoothMediaBrowserService.notifyChanged(mBrowseTree.mNowPlayingNode);
                     }
-
-                    AvrcpPlayer updatedPlayer = mAvailablePlayerList.get(mAddressedPlayerId);
-                    if (updatedPlayer != null) {
-                        mAddressedPlayer = updatedPlayer;
-                        // If the new player supports the now playing feature then fetch it
-                        if (mAddressedPlayer.supportsFeature(AvrcpPlayer.FEATURE_NOW_PLAYING)) {
-                            sendMessage(MESSAGE_GET_FOLDER_ITEMS, mBrowseTree.mNowPlayingNode);
-                        }
-                        logD("AddressedPlayer = " + mAddressedPlayer.getName());
-                    } else {
-                        logD("Addressed player changed to unknown ID=" + mAddressedPlayerId);
-                        mBrowseTree.mRootNode.setCached(false);
-                        mBrowseTree.mRootNode.setExpectedChildren(255);
-                        BluetoothMediaBrowserService.notifyChanged(mBrowseTree.mRootNode);
-                    }
                     removeUnusedArtworkFromBrowseTree();
+
+                    // For devices that support browsing, we *may* have an AvrcpPlayer with player
+                    // metadata already. We could also be in the middle fetching it. If the player
+                    // isn't there then we need to ensure that a default Addressed AvrcpPlayer is
+                    // created to represent it. It can be updated if/when we do fetch the player.
+                    if (!mAvailablePlayerList.contains(mAddressedPlayerId)) {
+                        logD("Available player set does not contain the new Addressed Player");
+                        AvrcpPlayer.Builder apb = new AvrcpPlayer.Builder();
+                        apb.setDevice(mDevice);
+                        apb.setPlayerId(mAddressedPlayerId);
+                        apb.setSupportedFeature(AvrcpPlayer.FEATURE_PLAY);
+                        apb.setSupportedFeature(AvrcpPlayer.FEATURE_PAUSE);
+                        apb.setSupportedFeature(AvrcpPlayer.FEATURE_STOP);
+                        apb.setSupportedFeature(AvrcpPlayer.FEATURE_FORWARD);
+                        apb.setSupportedFeature(AvrcpPlayer.FEATURE_PREVIOUS);
+                        mAvailablePlayerList.put(mAddressedPlayerId, apb.build());
+                    }
+
+                    // Set our new addressed player object from our set of available players that's
+                    // guaranteed to have the addressed player now.
+                    mAddressedPlayer = mAvailablePlayerList.get(mAddressedPlayerId);
+
+                    // Fetch metadata including the now playing list if the new player supports the
+                    // now playing feature
+                    mService.getCurrentMetadataNative(Utils.getByteAddress(mDevice));
+                    mService.getPlaybackStateNative(Utils.getByteAddress(mDevice));
+                    if (mAddressedPlayer.supportsFeature(AvrcpPlayer.FEATURE_NOW_PLAYING)) {
+                        sendMessage(MESSAGE_GET_FOLDER_ITEMS, mBrowseTree.mNowPlayingNode);
+                    }
+                    logD("AddressedPlayer = " + mAddressedPlayer);
                     return true;
 
                 case MESSAGE_PROCESS_SUPPORTED_APPLICATION_SETTINGS:
@@ -643,7 +686,6 @@
         }
 
         private void processPlayItem(BrowseTree.BrowseNode node) {
-            setActive(true);
             if (node == null) {
                 Log.w(TAG, "Invalid item to play");
             } else {
@@ -711,6 +753,7 @@
             mBrowseTree.mRootNode.setExpectedChildren(255);
             BluetoothMediaBrowserService.notifyChanged(mBrowseTree.mRootNode);
             removeUnusedArtworkFromBrowseTree();
+            requestContents(mBrowseTree.mRootNode);
         }
     }
 
@@ -812,13 +855,58 @@
                     break;
 
                 case MESSAGE_PROCESS_GET_PLAYER_ITEMS:
+                    logD("Received new available player items");
                     BrowseTree.BrowseNode rootNode = mBrowseTree.mRootNode;
+
+                    // The specification is not firm on what receiving available player changes
+                    // means relative to the existing player IDs, the addressed player and any
+                    // currently saved play status, track or now playing list metadata. We're going
+                    // to assume nothing and act verbosely, as some devices are known to reuse
+                    // Player IDs.
                     if (!rootNode.isCached()) {
                         List<AvrcpPlayer> playerList = (List<AvrcpPlayer>) msg.obj;
+
+                        // Since players hold metadata, including cover art handles that point to
+                        // stored images, be sure to save image UUIDs so we can see if we can
+                        // remove them from storage after setting our new player object
+                        ArrayList<String> coverArtUuids = new ArrayList<String>();
+                        for (int i = 0; i < mAvailablePlayerList.size(); i++) {
+                            AvrcpPlayer player = mAvailablePlayerList.valueAt(i);
+                            AvrcpItem track = player.getCurrentTrack();
+                            if (track != null && track.getCoverArtUuid() != null) {
+                                coverArtUuids.add(track.getCoverArtUuid());
+                            }
+                        }
+
                         mAvailablePlayerList.clear();
                         for (AvrcpPlayer player : playerList) {
                             mAvailablePlayerList.put(player.getId(), player);
                         }
+
+                        // If our new set of players contains our addressed player again then we
+                        // will replace it and re-download metadata. If not, we'll re-use the old
+                        // player to save the metadata queries.
+                        if (!mAvailablePlayerList.contains(mAddressedPlayerId)) {
+                            logD("Available player set doesn't contain the addressed player");
+                            mAvailablePlayerList.put(mAddressedPlayerId, mAddressedPlayer);
+                        } else {
+                            logD("Update addressed player with new available player metadata");
+                            mAddressedPlayer = mAvailablePlayerList.get(mAddressedPlayerId);
+                            mService.getCurrentMetadataNative(Utils.getByteAddress(mDevice));
+                            mService.getPlaybackStateNative(Utils.getByteAddress(mDevice));
+                            mBrowseTree.mNowPlayingNode.setCached(false);
+                            if (mAddressedPlayer.supportsFeature(AvrcpPlayer.FEATURE_NOW_PLAYING)) {
+                                sendMessage(MESSAGE_GET_FOLDER_ITEMS, mBrowseTree.mNowPlayingNode);
+                            }
+                        }
+                        logD("AddressedPlayer = " + mAddressedPlayer);
+
+                        // Check old cover art UUIDs for deletion
+                        for (String uuid : coverArtUuids) {
+                            removeUnusedArtwork(uuid);
+                        }
+
+                        // Make sure our browse tree matches our received Available Player set only
                         rootNode.addChildren(playerList);
                         mBrowseTree.setCurrentBrowsedFolder(BrowseTree.ROOT);
                         rootNode.setExpectedChildren(playerList.size());
@@ -970,7 +1058,10 @@
         public void enter() {
             disconnectCoverArt();
             onBrowsingDisconnected();
-            setActive(false);
+            if (mService.sBrowseTree != null) {
+                mService.sBrowseTree.mRootNode.removeChild(mBrowseTree.mRootNode);
+                BluetoothMediaBrowserService.notifyChanged(mService.sBrowseTree.mRootNode);
+            }
             broadcastConnectionStateChanged(BluetoothProfile.STATE_DISCONNECTING);
             transitionTo(mDisconnected);
         }
@@ -1161,7 +1252,7 @@
         intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mDevice);
         intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
         mMostRecentState = currentState;
-        mService.sendBroadcast(intent, ProfileService.BLUETOOTH_PERM);
+        mService.sendBroadcast(intent, BLUETOOTH_CONNECT, Utils.getTempAllowlistBroadcastOptions());
     }
 
     private boolean shouldRequestFocus() {
diff --git a/src/com/android/bluetooth/avrcpcontroller/AvrcpCoverArtManager.java b/src/com/android/bluetooth/avrcpcontroller/AvrcpCoverArtManager.java
index c6de62d..0a3ec20 100644
--- a/src/com/android/bluetooth/avrcpcontroller/AvrcpCoverArtManager.java
+++ b/src/com/android/bluetooth/avrcpcontroller/AvrcpCoverArtManager.java
@@ -99,7 +99,7 @@
         }
 
         public String getHandleUuid(String handle) {
-            if (handle == null) return null;
+            if (!isValidImageHandle(handle)) return null;
             String newUuid = UUID.randomUUID().toString();
             String existingUuid = mUuids.putIfAbsent(handle, newUuid);
             if (existingUuid != null) return existingUuid;
@@ -121,6 +121,24 @@
         }
     }
 
+    /**
+     * Validate an image handle meets the AVRCP and BIP specifications
+     *
+     * By the BIP specification that AVRCP uses, "Image handles are 7 character long strings
+     * containing only the digits 0 to 9."
+     *
+     * @return True if the input string is a valid image handle
+     */
+    public static boolean isValidImageHandle(String handle) {
+        if (handle == null || handle.length() != 7) return false;
+        for (char c : handle.toCharArray()) {
+            if (!Character.isDigit(c)) {
+                return false;
+            }
+        }
+        return true;
+    }
+
     public AvrcpCoverArtManager(AvrcpControllerService service, Callback callback) {
         mService = service;
         mCoverArtStorage = new AvrcpCoverArtStorage(mService);
@@ -225,7 +243,7 @@
      */
     public String getUuidForHandle(BluetoothDevice device, String handle) {
         AvrcpBipSession session = getSession(device);
-        if (session == null || handle == null) return null;
+        if (session == null || !isValidImageHandle(handle)) return null;
         return session.getHandleUuid(handle);
     }
 
@@ -359,6 +377,9 @@
      * @return A descriptor containing the desirable download format
      */
     private BipImageDescriptor determineImageDescriptor(BipImageProperties properties) {
+        if (properties == null || !properties.isValid()) {
+            warn("Provided properties don't meet the spec. Requesting thumbnail format anyway.");
+        }
         BipImageDescriptor.Builder builder = new BipImageDescriptor.Builder();
         switch (mDownloadScheme) {
             // BIP Specification says a blank/null descriptor signals to pull the native format
diff --git a/src/com/android/bluetooth/avrcpcontroller/AvrcpItem.java b/src/com/android/bluetooth/avrcpcontroller/AvrcpItem.java
index ca5d3d6..9bb9875 100644
--- a/src/com/android/bluetooth/avrcpcontroller/AvrcpItem.java
+++ b/src/com/android/bluetooth/avrcpcontroller/AvrcpItem.java
@@ -232,12 +232,17 @@
         return new MediaItem(descriptionBuilder.build(), flags);
     }
 
+    private static String parseImageHandle(String handle) {
+        return AvrcpCoverArtManager.isValidImageHandle(handle) ? handle : null;
+    }
+
     @Override
     public String toString() {
         return "AvrcpItem{mUuid=" + mUuid + ", mUid=" + mUid + ", mItemType=" + mItemType
                 + ", mType=" + mType + ", mDisplayableName=" + mDisplayableName
-                + ", mTitle=" + mTitle + ", mPlayable=" + mPlayable + ", mBrowsable="
-                + mBrowsable + ", mCoverArtHandle=" + getCoverArtHandle()
+                + ", mTitle=" + mTitle + " mPlayingTime=" + mPlayingTime + " mTrack="
+                + mTrackNumber + "/" + mTotalNumberOfTracks + ", mPlayable=" + mPlayable
+                + ", mBrowsable=" + mBrowsable + ", mCoverArtHandle=" + getCoverArtHandle()
                 + ", mImageUuid=" + mImageUuid + ", mImageUri" + mImageUri + "}";
     }
 
@@ -338,7 +343,7 @@
                         }
                         break;
                     case MEDIA_ATTRIBUTE_COVER_ART_HANDLE:
-                        mAvrcpItem.mCoverArtHandle = attrMap[i];
+                        mAvrcpItem.mCoverArtHandle = parseImageHandle(attrMap[i]);
                         break;
                 }
             }
@@ -515,13 +520,13 @@
         }
 
         /**
-         * Set the cover art handle for the AvrcpItem you are building
+         * Set the cover art handle for the AvrcpItem you are building.
          *
          * @param coverArtHandle The cover art image handle provided by a remote device
          * @return This object, so you can continue building
          */
         public Builder setCoverArtHandle(String coverArtHandle) {
-            mAvrcpItem.mCoverArtHandle = coverArtHandle;
+            mAvrcpItem.mCoverArtHandle = parseImageHandle(coverArtHandle);
             return this;
         }
 
diff --git a/src/com/android/bluetooth/avrcpcontroller/AvrcpPlayer.java b/src/com/android/bluetooth/avrcpcontroller/AvrcpPlayer.java
index 14be687..872ad36 100644
--- a/src/com/android/bluetooth/avrcpcontroller/AvrcpPlayer.java
+++ b/src/com/android/bluetooth/avrcpcontroller/AvrcpPlayer.java
@@ -32,7 +32,17 @@
     private static final String TAG = "AvrcpPlayer";
     private static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG);
 
-    public static final int INVALID_ID = -1;
+    public static final int DEFAULT_ID = -1;
+
+    public static final int TYPE_UNKNOWN = -1;
+    public static final int TYPE_AUDIO = 0;
+    public static final int TYPE_VIDEO = 1;
+    public static final int TYPE_BROADCASTING_AUDIO = 2;
+    public static final int TYPE_BROADCASTING_VIDEO = 3;
+
+    public static final int SUB_TYPE_UNKNOWN = -1;
+    public static final int SUB_TYPE_AUDIO_BOOK = 0;
+    public static final int SUB_TYPE_PODCAST = 1;
 
     public static final int FEATURE_PLAY = 40;
     public static final int FEATURE_STOP = 41;
@@ -60,31 +70,18 @@
             new PlayerApplicationSettings();
     private PlayerApplicationSettings mCurrentPlayerApplicationSettings;
 
-    AvrcpPlayer() {
-        mDevice = null;
-        mId = INVALID_ID;
-        //Set Default Actions in case Player data isn't available.
-        mAvailableActions = PlaybackStateCompat.ACTION_PAUSE | PlaybackStateCompat.ACTION_PLAY
-                | PlaybackStateCompat.ACTION_SKIP_TO_NEXT
-                | PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS
-                | PlaybackStateCompat.ACTION_STOP | PlaybackStateCompat.ACTION_PREPARE;
-        PlaybackStateCompat.Builder playbackStateBuilder = new PlaybackStateCompat.Builder()
-                .setActions(mAvailableActions);
-        mPlaybackStateCompat = playbackStateBuilder.build();
-    }
-
-    AvrcpPlayer(BluetoothDevice device, int id, String name, byte[] playerFeatures, int playStatus,
-            int playerType) {
+    private AvrcpPlayer(BluetoothDevice device, int id, int playerType, int playerSubType,
+            String name, byte[] playerFeatures, int playStatus) {
         mDevice = device;
         mId = id;
         mName = name;
-        mPlayStatus = playStatus;
         mPlayerType = playerType;
         mPlayerFeatures = Arrays.copyOf(playerFeatures, playerFeatures.length);
         PlaybackStateCompat.Builder playbackStateBuilder = new PlaybackStateCompat.Builder()
                 .setActions(mAvailableActions);
         mPlaybackStateCompat = playbackStateBuilder.build();
         updateAvailableActions();
+        setPlayStatus(playStatus);
     }
 
     public BluetoothDevice getDevice() {
@@ -112,8 +109,10 @@
     }
 
     public void setPlayStatus(int playStatus) {
-        mPlayTime += mPlaySpeed * (SystemClock.elapsedRealtime()
-                - mPlaybackStateCompat.getLastPositionUpdateTime());
+        if (mPlayTime != PlaybackStateCompat.PLAYBACK_POSITION_UNKNOWN) {
+            mPlayTime += mPlaySpeed * (SystemClock.elapsedRealtime()
+                    - mPlaybackStateCompat.getLastPositionUpdateTime());
+        }
         mPlayStatus = playStatus;
         switch (mPlayStatus) {
             case PlaybackStateCompat.STATE_STOPPED:
@@ -202,6 +201,7 @@
     }
 
     private void updateAvailableActions() {
+        mAvailableActions = PlaybackStateCompat.ACTION_PREPARE;
         if (supportsFeature(FEATURE_PLAY)) {
             mAvailableActions = mAvailableActions | PlaybackStateCompat.ACTION_PLAY;
         }
@@ -236,4 +236,140 @@
 
         if (DBG) Log.d(TAG, "Supported Actions = " + mAvailableActions);
     }
+
+    @Override
+    public String toString() {
+        return "<AvrcpPlayer id=" + mId + " name=" + mName + " track=" + mCurrentTrack
+                + " playState=" + mPlaybackStateCompat + ">";
+    }
+
+    /**
+     * A Builder object for an AvrcpPlayer
+     */
+    public static class Builder {
+        private static final String TAG = "AvrcpPlayer.Builder";
+        private static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG);
+
+        private BluetoothDevice mDevice = null;
+        private int mPlayerId = AvrcpPlayer.DEFAULT_ID;
+        private int mPlayerType = AvrcpPlayer.TYPE_UNKNOWN;
+        private int mPlayerSubType = AvrcpPlayer.SUB_TYPE_UNKNOWN;
+        private String mPlayerName = null;
+        private byte[] mSupportedFeatures = new byte[16];
+
+        private int mPlayStatus = PlaybackStateCompat.STATE_NONE;
+        private long mPlayTime = PlaybackStateCompat.PLAYBACK_POSITION_UNKNOWN;
+        private float mPlaySpeed = 1;
+        private long mPlayTimeUpdate = 0;
+
+        private AvrcpItem mTrack = null;
+
+        /**
+         * Set the device that this Player came from
+         *
+         * @param device The BleutoothDevice representing the remote device
+         * @return This object, so you can continue building
+         */
+        public Builder setDevice(BluetoothDevice device) {
+            mDevice = device;
+            return this;
+        }
+
+        /**
+         * Set the Player ID for this Player
+         *
+         * @param playerId The ID for this player, defined in AVRCP 6.10.2.1
+         * @return This object, so you can continue building
+         */
+        public Builder setPlayerId(int playerId) {
+            mPlayerId = playerId;
+            return this;
+        }
+
+        /**
+         * Set the Player Type for this Player
+         *
+         * @param playerType The type for this player, defined in AVRCP 6.10.2.1
+         * @return This object, so you can continue building
+         */
+        public Builder setPlayerType(int playerType) {
+            mPlayerType = playerType;
+            return this;
+        }
+
+        /**
+         * Set the Player Sub-type for this Player
+         *
+         * @param playerSubType The sub-type for this player, defined in AVRCP 6.10.2.1
+         * @return This object, so you can continue building
+         */
+        public Builder setPlayerSubType(int playerSubType) {
+            mPlayerSubType = playerSubType;
+            return this;
+        }
+
+        /**
+         * Set the name for this Player. This is what users will see when browsing.
+         *
+         * @param name The name for this player, defined in AVRCP 6.10.2.1
+         * @return This object, so you can continue building
+         */
+        public Builder setName(String name) {
+            mPlayerName = name;
+            return this;
+        }
+
+        /**
+         * Set the entire set of supported features for this Player.
+         *
+         * @param features The feature set for this player, defined in AVRCP 6.10.2.1
+         * @return This object, so you can continue building
+         */
+        public Builder setSupportedFeatures(byte[] supportedFeatures) {
+            mSupportedFeatures = supportedFeatures;
+            return this;
+        }
+
+        /**
+         * Set a single features as supported for this Player.
+         *
+         * @param feature The feature for this player, defined in AVRCP 6.10.2.1
+         * @return This object, so you can continue building
+         */
+        public Builder setSupportedFeature(int feature) {
+            int byteNumber = feature / 8;
+            byte bitMask = (byte) (1 << (feature % 8));
+            mSupportedFeatures[byteNumber] = (byte) (mSupportedFeatures[byteNumber] | bitMask);
+            return this;
+        }
+
+        /**
+         * Set the initial play status of the Player.
+         *
+         * @param playStatus The play state for this player as a PlaybackStateCompat.STATE_* value
+         * @return This object, so you can continue building
+         */
+        public Builder setPlayStatus(int playStatus) {
+            mPlayStatus = playStatus;
+            return this;
+        }
+
+        /**
+         * Set the initial play status of the Player.
+         *
+         * @param track The initial track for this player
+         * @return This object, so you can continue building
+         */
+        public Builder setCurrentTrack(AvrcpItem track) {
+            mTrack = track;
+            return this;
+        }
+
+        public AvrcpPlayer build() {
+            AvrcpPlayer player = new AvrcpPlayer(mDevice, mPlayerId, mPlayerType, mPlayerSubType,
+                    mPlayerName, mSupportedFeatures, mPlayStatus);
+            player.updateCurrentTrack(mTrack);
+            return player;
+        }
+    }
 }
diff --git a/src/com/android/bluetooth/avrcpcontroller/BluetoothMediaBrowserService.java b/src/com/android/bluetooth/avrcpcontroller/BluetoothMediaBrowserService.java
index 26bec40..f88872f 100644
--- a/src/com/android/bluetooth/avrcpcontroller/BluetoothMediaBrowserService.java
+++ b/src/com/android/bluetooth/avrcpcontroller/BluetoothMediaBrowserService.java
@@ -17,9 +17,13 @@
 package com.android.bluetooth.avrcpcontroller;
 
 import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
+import android.content.Context;
 import android.content.Intent;
+import android.content.IntentFilter;
 import android.os.Bundle;
 import android.support.v4.media.MediaBrowserCompat.MediaItem;
+import android.support.v4.media.MediaMetadataCompat;
 import android.support.v4.media.session.MediaControllerCompat;
 import android.support.v4.media.session.MediaSessionCompat;
 import android.support.v4.media.session.PlaybackStateCompat;
@@ -75,6 +79,26 @@
     public static final String ERROR_RESOLUTION_ACTION_LABEL =
             "android.media.extras.ERROR_RESOLUTION_ACTION_LABEL";
 
+    // Receiver for making sure our error message text matches the system locale
+    private class LocaleChangedReceiver extends BroadcastReceiver {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            String action = intent.getAction();
+            if (action.equals(Intent.ACTION_LOCALE_CHANGED)) {
+                if (sBluetoothMediaBrowserService == null) return;
+                MediaSessionCompat session = sBluetoothMediaBrowserService.getSession();
+                MediaControllerCompat controller = session.getController();
+                PlaybackStateCompat playbackState =
+                        controller == null ? null : controller.getPlaybackState();
+                if (playbackState != null && playbackState.getErrorMessage() != null) {
+                    setErrorPlaybackState();
+                }
+            }
+        }
+    }
+
+    private LocaleChangedReceiver mReceiver;
+
     /**
      * Initialize this BluetoothMediaBrowserService, creating our MediaSessionCompat, MediaPlayer
      * and MediaMetaData, and setting up mechanisms to talk with the AvrcpControllerService.
@@ -93,6 +117,17 @@
         mSession.setQueue(mMediaQueue);
         setErrorPlaybackState();
         sBluetoothMediaBrowserService = this;
+
+        mReceiver = new LocaleChangedReceiver();
+        IntentFilter filter = new IntentFilter();
+        filter.addAction(Intent.ACTION_LOCALE_CHANGED);
+        registerReceiver(mReceiver, filter);
+    }
+
+    @Override
+    public void onDestroy() {
+        unregisterReceiver(mReceiver);
+        mReceiver = null;
     }
 
     List<MediaItem> getContents(final String parentMediaId) {
@@ -154,19 +189,21 @@
     private void updateNowPlayingQueue(BrowseTree.BrowseNode node) {
         List<MediaItem> songList = node.getContents();
         mMediaQueue.clear();
-        if (songList != null) {
+        if (songList != null && songList.size() > 0) {
             for (MediaItem song : songList) {
                 mMediaQueue.add(new MediaSessionCompat.QueueItem(
                         song.getDescription(),
                         mMediaQueue.size()));
             }
+            mSession.setQueue(mMediaQueue);
+        } else {
+            mSession.setQueue(null);
         }
-        mSession.setQueue(mMediaQueue);
     }
 
     private void clearNowPlayingQueue() {
         mMediaQueue.clear();
-        mSession.setQueue(mMediaQueue);
+        mSession.setQueue(null);
     }
 
     static synchronized void notifyChanged(BrowseTree.BrowseNode node) {
@@ -284,4 +321,60 @@
             return null;
         }
     }
+
+    /**
+     * Reset the state of BluetoothMediaBrowserService to that before a device connected
+     */
+    public static synchronized void reset() {
+        if (sBluetoothMediaBrowserService != null) {
+            sBluetoothMediaBrowserService.clearNowPlayingQueue();
+            sBluetoothMediaBrowserService.mSession.setMetadata(null);
+            sBluetoothMediaBrowserService.setErrorPlaybackState();
+            sBluetoothMediaBrowserService.mSession.setCallback(null);
+            if (DBG) Log.d(TAG, "Service state has been reset");
+        } else {
+            Log.w(TAG, "reset unavailable");
+        }
+    }
+
+    /**
+     * Get the state of the BluetoothMediaBrowserService as a debug string
+     */
+    public static synchronized String dump() {
+        StringBuilder sb = new StringBuilder();
+        sb.append(TAG + ":");
+        if (sBluetoothMediaBrowserService != null) {
+            MediaSessionCompat session = sBluetoothMediaBrowserService.getSession();
+            MediaControllerCompat controller = session.getController();
+            MediaMetadataCompat metadata = controller == null ? null : controller.getMetadata();
+            PlaybackStateCompat playbackState =
+                    controller == null ? null : controller.getPlaybackState();
+            List<MediaSessionCompat.QueueItem> queue =
+                    controller == null ? null : controller.getQueue();
+            if (metadata != null) {
+                sb.append("\n    track={");
+                sb.append("title=" + metadata.getString(MediaMetadataCompat.METADATA_KEY_TITLE));
+                sb.append(", artist="
+                        + metadata.getString(MediaMetadataCompat.METADATA_KEY_ARTIST));
+                sb.append(", album=" + metadata.getString(MediaMetadataCompat.METADATA_KEY_ALBUM));
+                sb.append(", track_number="
+                        + metadata.getLong(MediaMetadataCompat.METADATA_KEY_TRACK_NUMBER));
+                sb.append(", total_tracks="
+                        + metadata.getLong(MediaMetadataCompat.METADATA_KEY_NUM_TRACKS));
+                sb.append(", genre=" + metadata.getString(MediaMetadataCompat.METADATA_KEY_GENRE));
+                sb.append(", album_art="
+                        + metadata.getString(MediaMetadataCompat.METADATA_KEY_ALBUM_ART_URI));
+                sb.append("}");
+            } else {
+                sb.append("\n    track=" + metadata);
+            }
+            sb.append("\n    playbackState=" + playbackState);
+            sb.append("\n    queue=" + queue);
+            sb.append("\n    internal_queue=" + sBluetoothMediaBrowserService.mMediaQueue);
+        } else {
+            Log.w(TAG, "dump Unavailable");
+            sb.append(" null");
+        }
+        return sb.toString();
+    }
 }
diff --git a/src/com/android/bluetooth/avrcpcontroller/BrowseTree.java b/src/com/android/bluetooth/avrcpcontroller/BrowseTree.java
index 4ade35d..508c68c 100644
--- a/src/com/android/bluetooth/avrcpcontroller/BrowseTree.java
+++ b/src/com/android/bluetooth/avrcpcontroller/BrowseTree.java
@@ -21,6 +21,8 @@
 import android.support.v4.media.MediaBrowserCompat.MediaItem;
 import android.util.Log;
 
+import com.android.bluetooth.Utils;
+
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.HashSet;
@@ -76,8 +78,9 @@
         } else {
             mRootNode = new BrowseNode(new AvrcpItem.Builder().setDevice(device)
                     .setUuid(ROOT + device.getAddress().toString())
-                    .setTitle(device.getName()).setBrowsable(true).build());
+                    .setTitle(Utils.getName(device)).setBrowsable(true).build());
         }
+
         mRootNode.mBrowseScope = AvrcpControllerService.BROWSE_SCOPE_PLAYER_LIST;
         mRootNode.setExpectedChildren(255);
 
@@ -156,8 +159,8 @@
             AvrcpItem.Builder aid = new AvrcpItem.Builder();
             aid.setDevice(device);
             aid.setUuid(playerKey);
-            aid.setDisplayableName(device.getName());
-            aid.setTitle(device.getName());
+            aid.setDisplayableName(Utils.getName(device));
+            aid.setTitle(Utils.getName(device));
             aid.setBrowsable(true);
             mItem = aid.build();
         }
diff --git a/src/com/android/bluetooth/avrcpcontroller/bip/BipImageProperties.java b/src/com/android/bluetooth/avrcpcontroller/bip/BipImageProperties.java
index 70d935a..a2265d1 100644
--- a/src/com/android/bluetooth/avrcpcontroller/bip/BipImageProperties.java
+++ b/src/com/android/bluetooth/avrcpcontroller/bip/BipImageProperties.java
@@ -43,6 +43,13 @@
  * various transformations. Attachments describes other items that can be downloaded that are
  * associated with the image (text, sounds, etc.)
  *
+ * The specification requires that
+ *     1. The fixed version string of "1.0" is used
+ *     2. There is an image handle
+ *     3. The "imaging thumbnail format" is included. This is defined for BIP in section 4.4.3
+ *        (160x120 JPEG) and redefined for AVRCP in section 5.14.2.2.1 I (200x200 JPEG). It can be
+ *        either a native or variant format.
+ *
  * Example:
  *     <image-properties version="1.0" handle="123456789">
  *     <native encoding="JPEG" pixel="1280*1024" size="1048576"/>
@@ -143,6 +150,11 @@
     private String mFriendlyName = null;
 
     /**
+     * Whether we have the required imaging thumbnail format
+     */
+    private boolean mHasThumbnailFormat = false;
+
+    /**
      * The various sets of available formats.
      */
     private ArrayList<BipImageFormat> mNativeFormats;
@@ -220,6 +232,10 @@
         return mImageHandle;
     }
 
+    public String getVersion() {
+        return mVersion;
+    }
+
     public String getFriendlyName() {
         return mFriendlyName;
     }
@@ -243,6 +259,10 @@
                     + "' but expected '" + BipImageFormat.FORMAT_NATIVE + "'");
         }
         mNativeFormats.add(format);
+
+        if (!mHasThumbnailFormat && isThumbnailFormat(format)) {
+            mHasThumbnailFormat = true;
+        }
     }
 
     private void addVariantFormat(BipImageFormat format) {
@@ -252,6 +272,29 @@
                     + "' but expected '" + BipImageFormat.FORMAT_VARIANT + "'");
         }
         mVariantFormats.add(format);
+
+        if (!mHasThumbnailFormat && isThumbnailFormat(format)) {
+            mHasThumbnailFormat = true;
+        }
+    }
+
+    private boolean isThumbnailFormat(BipImageFormat format) {
+        if (format == null) return false;
+
+        BipEncoding encoding = format.getEncoding();
+        if (encoding == null || encoding.getType() != BipEncoding.JPEG) return false;
+
+        BipPixel pixel = format.getPixel();
+        if (pixel == null) return false;
+        switch (pixel.getType()) {
+            case BipPixel.TYPE_FIXED:
+                return pixel.getMaxWidth() == 200 && pixel.getMaxHeight() == 200;
+            case BipPixel.TYPE_RESIZE_MODIFIED_ASPECT_RATIO:
+                return pixel.getMaxWidth() >= 200 && pixel.getMaxHeight() >= 200;
+            case BipPixel.TYPE_RESIZE_FIXED_ASPECT_RATIO:
+                return pixel.getMaxWidth() == pixel.getMaxHeight() && pixel.getMaxWidth() >= 200;
+        }
+        return false;
     }
 
     private void addAttachment(BipAttachmentFormat format) {
@@ -268,8 +311,11 @@
             xmlMsgElement.startDocument("UTF-8", true);
             xmlMsgElement.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
             xmlMsgElement.startTag(null, "image-properties");
-            xmlMsgElement.attribute(null, "version", mVersion);
-            xmlMsgElement.attribute(null, "handle", mImageHandle);
+            if (mVersion != null) xmlMsgElement.attribute(null, "version", mVersion);
+            if (mImageHandle != null) xmlMsgElement.attribute(null, "handle", mImageHandle);
+            if (mFriendlyName != null) {
+                xmlMsgElement.attribute(null, "friendly-name", mFriendlyName);
+            }
 
             for (BipImageFormat format : mNativeFormats) {
                 BipEncoding encoding = format.getEncoding();
@@ -354,9 +400,12 @@
     /**
      * Serialize this object into a byte array
      *
+     * Objects that are not valid will fail to serialize and return null.
+     *
      * @return Byte array representing this object, ready to send over OBEX, or null on error.
      */
     public byte[] serialize() {
+        if (!isValid()) return null;
         String s = toString();
         try {
             return s != null ? s.getBytes("UTF-8") : null;
@@ -365,6 +414,19 @@
         }
     }
 
+    /**
+     * Determine if the contents of this BipImageProperties object are valid and meet the
+     * specification requirements:
+     *     1. Include the fixed 1.0 version
+     *     2. Include an image handle
+     *     3. Have the thumbnail format as either the native or variant
+     *
+     * @return True if our contents are valid, false otherwise
+     */
+    public boolean isValid() {
+        return sVersion.equals(mVersion) && mImageHandle != null && mHasThumbnailFormat;
+    }
+
     private static void warn(String msg) {
         Log.w(TAG, msg);
     }
diff --git a/src/com/android/bluetooth/btservice/AbstractionLayer.java b/src/com/android/bluetooth/btservice/AbstractionLayer.java
index e15104d..203d3b0 100644
--- a/src/com/android/bluetooth/btservice/AbstractionLayer.java
+++ b/src/com/android/bluetooth/btservice/AbstractionLayer.java
@@ -48,6 +48,8 @@
     static final int BT_PROPERTY_REMOTE_VERSION_INFO = 0x0C;
     static final int BT_PROPERTY_LOCAL_LE_FEATURES = 0x0D;
 
+    static final int BT_PROPERTY_DYNAMIC_AUDIO_BUFFER = 0x10;
+
     public static final int BT_DEVICE_TYPE_BREDR = 0x01;
     public static final int BT_DEVICE_TYPE_BLE = 0x02;
     public static final int BT_DEVICE_TYPE_DUAL = 0x03;
diff --git a/src/com/android/bluetooth/btservice/ActiveDeviceManager.java b/src/com/android/bluetooth/btservice/ActiveDeviceManager.java
index 69381c5..74300e7 100644
--- a/src/com/android/bluetooth/btservice/ActiveDeviceManager.java
+++ b/src/com/android/bluetooth/btservice/ActiveDeviceManager.java
@@ -16,6 +16,8 @@
 
 package com.android.bluetooth.btservice;
 
+import android.annotation.RequiresPermission;
+import android.annotation.SuppressLint;
 import android.bluetooth.BluetoothA2dp;
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothDevice;
@@ -325,6 +327,7 @@
     }
 
     /** Notifications of audio device connection and disconnection events. */
+    @SuppressLint("AndroidFrameworkRequiresPermission")
     private class AudioManagerAudioDeviceCallback extends AudioDeviceCallback {
         private boolean isWiredAudioHeadset(AudioDeviceInfo deviceInfo) {
             switch (deviceInfo.getType()) {
@@ -434,6 +437,7 @@
         mA2dpActiveDevice = device;
     }
 
+    @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
     private void setHfpActiveDevice(BluetoothDevice device) {
         if (DBG) {
             Log.d(TAG, "setHfpActiveDevice(" + device + ")");
@@ -497,6 +501,7 @@
      * It might be called multiple times each time a wired audio device is connected.
      */
     @VisibleForTesting
+    @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
     void wiredAudioDeviceConnected() {
         if (DBG) {
             Log.d(TAG, "wiredAudioDeviceConnected");
diff --git a/src/com/android/bluetooth/btservice/AdapterProperties.java b/src/com/android/bluetooth/btservice/AdapterProperties.java
index 2737082..b36d39d 100644
--- a/src/com/android/bluetooth/btservice/AdapterProperties.java
+++ b/src/com/android/bluetooth/btservice/AdapterProperties.java
@@ -16,6 +16,9 @@
 
 package com.android.bluetooth.btservice;
 
+import static android.Manifest.permission.BLUETOOTH_CONNECT;
+import static android.Manifest.permission.BLUETOOTH_SCAN;
+
 import android.bluetooth.BluetoothA2dp;
 import android.bluetooth.BluetoothA2dpSink;
 import android.bluetooth.BluetoothAdapter;
@@ -34,6 +37,8 @@
 import android.bluetooth.BluetoothPbapClient;
 import android.bluetooth.BluetoothProfile;
 import android.bluetooth.BluetoothSap;
+import android.bluetooth.BufferConstraint;
+import android.bluetooth.BufferConstraints;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
@@ -52,7 +57,9 @@
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
+import java.util.ArrayList;
 import java.util.HashMap;
+import java.util.List;
 import java.util.concurrent.CopyOnWriteArrayList;
 
 class AdapterProperties {
@@ -115,6 +122,11 @@
     private boolean mIsLePeriodicAdvertisingSupported;
     private int mLeMaximumAdvertisingDataLength;
 
+    private int mIsDynamicAudioBufferSizeSupported;
+    private int mDynamicAudioBufferSizeSupportedCodecsGroup1;
+    private int mDynamicAudioBufferSizeSupportedCodecsGroup2;
+    private List<BufferConstraint> mBufferConstraintList;
+
     private boolean mReceiverRegistered;
     private BroadcastReceiver mReceiver = new BroadcastReceiver() {
         @Override
@@ -238,19 +250,22 @@
         mBondedDevices.clear();
         invalidateBluetoothCaches();
     }
-
     private static void invalidateGetProfileConnectionStateCache() {
         BluetoothAdapter.invalidateGetProfileConnectionStateCache();
     }
     private static void invalidateIsOffloadedFilteringSupportedCache() {
         BluetoothAdapter.invalidateIsOffloadedFilteringSupportedCache();
     }
+    private static void invalidateGetConnectionStateCache() {
+        BluetoothAdapter.invalidateGetAdapterConnectionStateCache();
+    }
     private static void invalidateGetBondStateCache() {
         BluetoothDevice.invalidateBluetoothGetBondStateCache();
     }
     private static void invalidateBluetoothCaches() {
         invalidateGetProfileConnectionStateCache();
         invalidateIsOffloadedFilteringSupportedCache();
+        invalidateGetConnectionStateCache();
         invalidateGetBondStateCache();
     }
 
@@ -390,6 +405,7 @@
      */
     void setConnectionState(int connectionState) {
         mConnectionState = connectionState;
+        invalidateGetConnectionStateCache();
     }
 
     /**
@@ -513,6 +529,44 @@
     }
 
     /**
+     * @return Dynamic Audio Buffer support
+     */
+    int getDynamicBufferSupport() {
+        if (!mA2dpOffloadEnabled) {
+            // TODO: Enable Dynamic Audio Buffer for A2DP software encoding when ready.
+            mIsDynamicAudioBufferSizeSupported =
+                BluetoothA2dp.DYNAMIC_BUFFER_SUPPORT_NONE;
+        } else {
+            if ((mDynamicAudioBufferSizeSupportedCodecsGroup1 != 0)
+                    || (mDynamicAudioBufferSizeSupportedCodecsGroup2 != 0)) {
+                mIsDynamicAudioBufferSizeSupported =
+                    BluetoothA2dp.DYNAMIC_BUFFER_SUPPORT_A2DP_OFFLOAD;
+            } else {
+                mIsDynamicAudioBufferSizeSupported =
+                    BluetoothA2dp.DYNAMIC_BUFFER_SUPPORT_NONE;
+            }
+        }
+        return mIsDynamicAudioBufferSizeSupported;
+    }
+
+    /**
+     * @return Dynamic Audio Buffer Capability
+     */
+    BufferConstraints getBufferConstraints() {
+        return new BufferConstraints(mBufferConstraintList);
+    }
+
+    /**
+     * Set the dynamic audio buffer size
+     *
+     * @param codec the codecs to set
+     * @param size the size to set
+     */
+    boolean setBufferLengthMillis(int codec, int value) {
+        return mService.setBufferLengthMillisNative(codec, value);
+    }
+
+    /**
      * @return the mBondedDevices
      */
     BluetoothDevice[] getBondedDevices() {
@@ -641,7 +695,8 @@
                     Log.w(TAG, "ADAPTER_CONNECTION_STATE_CHANGE: unexpected transition for profile="
                             + profile + ", device=" + device + ", " + prevState + " -> " + state);
                 }
-                mService.sendBroadcastAsUser(intent, UserHandle.ALL, AdapterService.BLUETOOTH_PERM);
+                mService.sendBroadcastAsUser(intent, UserHandle.ALL, BLUETOOTH_CONNECT,
+                        Utils.getTempAllowlistBroadcastOptions());
             }
         }
     }
@@ -804,18 +859,17 @@
                         intent.putExtra(BluetoothAdapter.EXTRA_LOCAL_NAME, mName);
                         intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
                         mService.sendBroadcastAsUser(intent, UserHandle.ALL,
-                                AdapterService.BLUETOOTH_PERM);
+                                BLUETOOTH_CONNECT, Utils.getTempAllowlistBroadcastOptions());
                         debugLog("Name is: " + mName);
                         break;
                     case AbstractionLayer.BT_PROPERTY_BDADDR:
                         mAddress = val;
                         String address = Utils.getAddressStringFromByte(mAddress);
-                        debugLog("Address is:" + address);
                         intent = new Intent(BluetoothAdapter.ACTION_BLUETOOTH_ADDRESS_CHANGED);
                         intent.putExtra(BluetoothAdapter.EXTRA_BLUETOOTH_ADDRESS, address);
                         intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
                         mService.sendBroadcastAsUser(intent, UserHandle.ALL,
-                                AdapterService.BLUETOOTH_PERM);
+                                BLUETOOTH_CONNECT, Utils.getTempAllowlistBroadcastOptions());
                         break;
                     case AbstractionLayer.BT_PROPERTY_CLASS_OF_DEVICE:
                         if (val == null || val.length != 3) {
@@ -835,7 +889,8 @@
                         intent = new Intent(BluetoothAdapter.ACTION_SCAN_MODE_CHANGED);
                         intent.putExtra(BluetoothAdapter.EXTRA_SCAN_MODE, mScanMode);
                         intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
-                        mService.sendBroadcast(intent, AdapterService.BLUETOOTH_PERM);
+                        mService.sendBroadcast(intent, BLUETOOTH_SCAN,
+                                Utils.getTempAllowlistBroadcastOptions());
                         debugLog("Scan Mode:" + mScanMode);
                         break;
                     case AbstractionLayer.BT_PROPERTY_UUIDS:
@@ -860,6 +915,10 @@
                         updateFeatureSupport(val);
                         break;
 
+                    case AbstractionLayer.BT_PROPERTY_DYNAMIC_AUDIO_BUFFER:
+                        updateDynamicAudioBufferSupport(val);
+                        break;
+
                     case AbstractionLayer.BT_PROPERTY_LOCAL_IO_CAPS:
                         mLocalIOCapability = Utils.byteArrayToInt(val);
                         debugLog("mLocalIOCapability set to " + mLocalIOCapability);
@@ -894,6 +953,10 @@
         mIsLePeriodicAdvertisingSupported = ((0xFF & ((int) val[17])) != 0);
         mLeMaximumAdvertisingDataLength =
                 (0xFF & ((int) val[18])) + ((0xFF & ((int) val[19])) << 8);
+        mDynamicAudioBufferSizeSupportedCodecsGroup1 =
+                ((0xFF & ((int) val[21])) << 8) + (0xFF & ((int) val[20]));
+        mDynamicAudioBufferSizeSupportedCodecsGroup2 =
+                ((0xFF & ((int) val[23])) << 8) + (0xFF & ((int) val[22]));
 
         Log.d(TAG, "BT_PROPERTY_LOCAL_LE_FEATURES: update from BT controller"
                 + " mNumOfAdvertisementInstancesSupported = "
@@ -909,10 +972,36 @@
                 + mIsLe2MPhySupported + " mIsLeCodedPhySupported = " + mIsLeCodedPhySupported
                 + " mIsLeExtendedAdvertisingSupported = " + mIsLeExtendedAdvertisingSupported
                 + " mIsLePeriodicAdvertisingSupported = " + mIsLePeriodicAdvertisingSupported
-                + " mLeMaximumAdvertisingDataLength = " + mLeMaximumAdvertisingDataLength);
+                + " mLeMaximumAdvertisingDataLength = " + mLeMaximumAdvertisingDataLength
+                + " mDynamicAudioBufferSizeSupportedCodecsGroup1 = "
+                + mDynamicAudioBufferSizeSupportedCodecsGroup1
+                + " mDynamicAudioBufferSizeSupportedCodecsGroup2 = "
+                + mDynamicAudioBufferSizeSupportedCodecsGroup2);
         invalidateIsOffloadedFilteringSupportedCache();
     }
 
+    private void updateDynamicAudioBufferSupport(byte[] val) {
+        // bufferConstraints is the table indicates the capability of all the codecs
+        // with buffer time. The raw is codec number, and the column is buffer type. There are 3
+        // buffer types - default/maximum/minimum.
+        // The maximum number of raw is BUFFER_CODEC_MAX_NUM(32).
+        // The maximum number of column is BUFFER_TYPE_MAX(3).
+        // The array element indicates the buffer time, the size is two octet.
+        mBufferConstraintList = new ArrayList<BufferConstraint>();
+
+        for (int i = 0; i < BufferConstraints.BUFFER_CODEC_MAX_NUM; i++) {
+            int defaultBufferTime = ((0xFF & ((int) val[i * 6 + 1])) << 8)
+                    + (0xFF & ((int) val[i * 6]));
+            int maximumBufferTime = ((0xFF & ((int) val[i * 6 + 3])) << 8)
+                    + (0xFF & ((int) val[i * 6 + 2]));
+            int minimumBufferTime = ((0xFF & ((int) val[i * 6 + 5])) << 8)
+                    + (0xFF & ((int) val[i * 6 + 4]));
+            BufferConstraint bufferConstraint = new BufferConstraint(defaultBufferTime,
+                    maximumBufferTime, minimumBufferTime);
+            mBufferConstraintList.add(bufferConstraint);
+        }
+    }
+
     void onBluetoothReady() {
         debugLog("onBluetoothReady, state=" + BluetoothAdapter.nameForState(getState())
                 + ", ScanMode=" + mScanMode);
@@ -948,12 +1037,14 @@
                 mService.clearDiscoveringPackages();
                 mDiscoveryEndMs = System.currentTimeMillis();
                 intent = new Intent(BluetoothAdapter.ACTION_DISCOVERY_FINISHED);
-                mService.sendBroadcast(intent, AdapterService.BLUETOOTH_PERM);
+                mService.sendBroadcast(intent, BLUETOOTH_SCAN,
+                        Utils.getTempAllowlistBroadcastOptions());
             } else if (state == AbstractionLayer.BT_DISCOVERY_STARTED) {
                 mDiscovering = true;
                 mDiscoveryEndMs = System.currentTimeMillis() + DEFAULT_DISCOVERY_TIMEOUT_MS;
                 intent = new Intent(BluetoothAdapter.ACTION_DISCOVERY_STARTED);
-                mService.sendBroadcast(intent, AdapterService.BLUETOOTH_PERM);
+                mService.sendBroadcast(intent, BLUETOOTH_SCAN,
+                        Utils.getTempAllowlistBroadcastOptions());
             }
         }
     }
@@ -975,7 +1066,7 @@
         for (BluetoothDevice device : mBondedDevices) {
             writer.println(
                     "    " + device.getAddress() + " [" + dumpDeviceType(device.getType()) + "] "
-                            + device.getName());
+                            + Utils.getName(device));
         }
     }
 
diff --git a/src/com/android/bluetooth/btservice/AdapterService.java b/src/com/android/bluetooth/btservice/AdapterService.java
index cb64b87..dc4dd32 100644
--- a/src/com/android/bluetooth/btservice/AdapterService.java
+++ b/src/com/android/bluetooth/btservice/AdapterService.java
@@ -16,23 +16,29 @@
 
 package com.android.bluetooth.btservice;
 
+import static android.text.format.DateUtils.MINUTE_IN_MILLIS;
+import static android.text.format.DateUtils.SECOND_IN_MILLIS;
+
 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.enforceCdmAssociation;
 import static com.android.bluetooth.Utils.enforceDumpPermission;
 import static com.android.bluetooth.Utils.enforceLocalMacAddressPermission;
+import static com.android.bluetooth.Utils.hasBluetoothPrivilegedPermission;
+import static com.android.bluetooth.Utils.isPackageNameAccurate;
 
-import android.annotation.Nullable;
+import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
+import android.annotation.SuppressLint;
 import android.app.ActivityManager;
 import android.app.AlarmManager;
 import android.app.AppOpsManager;
 import android.app.PendingIntent;
-import android.app.PropertyInvalidatedCache;
 import android.app.Service;
 import android.app.admin.DevicePolicyManager;
+import android.bluetooth.BluetoothA2dp;
 import android.bluetooth.BluetoothActivityEnergyInfo;
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothAdapter.ActiveDeviceUse;
@@ -40,14 +46,22 @@
 import android.bluetooth.BluetoothDevice;
 import android.bluetooth.BluetoothProfile;
 import android.bluetooth.BluetoothProtoEnums;
+import android.bluetooth.BluetoothStatusCodes;
 import android.bluetooth.BluetoothUuid;
+import android.bluetooth.BufferConstraints;
 import android.bluetooth.IBluetooth;
 import android.bluetooth.IBluetoothCallback;
+import android.bluetooth.IBluetoothConnectionCallback;
 import android.bluetooth.IBluetoothMetadataListener;
+import android.bluetooth.IBluetoothOobDataCallback;
 import android.bluetooth.IBluetoothSocketManager;
 import android.bluetooth.OobData;
 import android.bluetooth.UidTraffic;
+import android.companion.CompanionDeviceManager;
+import android.content.Attributable;
+import android.content.AttributionSource;
 import android.content.BroadcastReceiver;
+import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
@@ -57,6 +71,7 @@
 import android.os.BatteryStats;
 import android.os.Binder;
 import android.os.Bundle;
+import android.os.BytesMatcher;
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.Looper;
@@ -71,6 +86,7 @@
 import android.os.SystemProperties;
 import android.os.UserHandle;
 import android.os.UserManager;
+import android.provider.DeviceConfig;
 import android.provider.Settings;
 import android.text.TextUtils;
 import android.util.Base64;
@@ -83,6 +99,7 @@
 import com.android.bluetooth.a2dp.A2dpService;
 import com.android.bluetooth.a2dpsink.A2dpSinkService;
 import com.android.bluetooth.btservice.RemoteDevices.DeviceProperties;
+import com.android.bluetooth.btservice.activityattribution.ActivityAttributionService;
 import com.android.bluetooth.btservice.bluetoothkeystore.BluetoothKeystoreService;
 import com.android.bluetooth.btservice.storage.DatabaseManager;
 import com.android.bluetooth.btservice.storage.MetadataDatabase;
@@ -99,21 +116,32 @@
 import com.android.bluetooth.pbapclient.PbapClientService;
 import com.android.bluetooth.sap.SapService;
 import com.android.bluetooth.sdp.SdpManager;
+import com.android.bluetooth.telephony.BluetoothInCallService;
 import com.android.internal.R;
+import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.app.IBatteryStats;
+import com.android.internal.os.BackgroundThread;
+import com.android.internal.os.BinderCallsStats;
 import com.android.internal.util.ArrayUtils;
 
+import libcore.util.SneakyThrow;
+
 import com.google.protobuf.InvalidProtocolBufferException;
 
 import java.io.FileDescriptor;
 import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.PrintWriter;
+import java.util.ArrayDeque;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.List;
+import java.util.Set;
+import java.util.function.Predicate;
+import java.util.regex.Pattern;
 
 public class AdapterService extends Service {
     private static final String TAG = "BluetoothAdapterService";
@@ -131,6 +159,7 @@
     private long mEnergyUsedTotalVoltAmpSecMicro;
     private final SparseArray<UidTraffic> mUidTraffic = new SparseArray<>();
 
+    private final ArrayList<String> mStartedProfiles = new ArrayList<>();
     private final ArrayList<ProfileService> mRegisteredProfiles = new ArrayList<>();
     private final ArrayList<ProfileService> mRunningProfiles = new ArrayList<>();
 
@@ -150,10 +179,8 @@
     private String mSnoopLogSettingAtEnable = "empty";
     private String mDefaultSnoopLogSettingAtEnable = "empty";
 
-    public static final String BLUETOOTH_ADMIN_PERM = android.Manifest.permission.BLUETOOTH_ADMIN;
     public static final String BLUETOOTH_PRIVILEGED =
             android.Manifest.permission.BLUETOOTH_PRIVILEGED;
-    static final String BLUETOOTH_PERM = android.Manifest.permission.BLUETOOTH;
     static final String LOCAL_MAC_ADDRESS_PERM = android.Manifest.permission.LOCAL_MAC_ADDRESS;
     static final String RECEIVE_MAP_PERM = android.Manifest.permission.RECEIVE_BLUETOOTH_MAP;
 
@@ -165,6 +192,30 @@
 
     private static final int CONTROLLER_ENERGY_UPDATE_TIMEOUT_MILLIS = 30;
 
+    private static final ComponentName BLUETOOTH_INCALLSERVICE_COMPONENT =
+            new ComponentName("com.android.bluetooth",
+                    BluetoothInCallService.class.getCanonicalName());
+
+    // Report ID definition
+    public enum BqrQualityReportId {
+        QUALITY_REPORT_ID_MONITOR_MODE(0x01),
+        QUALITY_REPORT_ID_APPROACH_LSTO(0x02),
+        QUALITY_REPORT_ID_A2DP_AUDIO_CHOPPY(0x03),
+        QUALITY_REPORT_ID_SCO_VOICE_CHOPPY(0x04),
+        QUALITY_REPORT_ID_ROOT_INFLAMMATION(0x05),
+        QUALITY_REPORT_ID_LMP_LL_MESSAGE_TRACE(0x11),
+        QUALITY_REPORT_ID_BT_SCHEDULING_TRACE(0x12),
+        QUALITY_REPORT_ID_CONTROLLER_DBG_INFO(0x13);
+
+        private final int value;
+        private BqrQualityReportId(int value) {
+            this.value = value;
+        }
+        public int getValue() {
+            return value;
+        }
+    };
+
     private final ArrayList<DiscoveringPackage> mDiscoveringPackages = new ArrayList<>();
 
     static {
@@ -174,7 +225,6 @@
     private static AdapterService sAdapterService;
 
     public static synchronized AdapterService getAdapterService() {
-        Log.d(TAG, "getAdapterService() - returning " + sAdapterService);
         return sAdapterService;
     }
 
@@ -206,10 +256,12 @@
     private final HashMap<BluetoothDevice, ArrayList<IBluetoothMetadataListener>>
             mMetadataListeners = new HashMap<>();
     private final HashMap<String, Integer> mProfileServicesState = new HashMap<String, Integer>();
+    private Set<IBluetoothConnectionCallback> mBluetoothConnectionCallbacks = new HashSet<>();
     //Only BluetoothManagerService should be registered
     private RemoteCallbackList<IBluetoothCallback> mCallbacks;
     private int mCurrentRequestId;
     private boolean mQuietmode = false;
+    private HashMap<String, CallerInfo> mBondAttemptCallerInfo = new HashMap<>();
 
     private AlarmManager mAlarmManager;
     private PendingIntent mPendingAlarm;
@@ -218,6 +270,7 @@
     private PowerManager.WakeLock mWakeLock;
     private String mWakeLockName;
     private UserManager mUserManager;
+    private CompanionDeviceManager mCompanionDeviceManager;
 
     private PhonePolicy mPhonePolicy;
     private ActiveDeviceManager mActiveDeviceManager;
@@ -230,6 +283,7 @@
     private BluetoothKeystoreService mBluetoothKeystoreService;
     private A2dpService mA2dpService;
     private A2dpSinkService mA2dpSinkService;
+    private ActivityAttributionService mActivityAttributionService;
     private HeadsetService mHeadsetService;
     private HeadsetClientService mHeadsetClientService;
     private BluetoothMapService mMapService;
@@ -242,6 +296,10 @@
     private HearingAidService mHearingAidService;
     private SapService mSapService;
 
+    private BinderCallsStats.SettingsObserver mBinderCallsSettingsObserver;
+
+    private volatile boolean mTestModeEnabled = false;
+
     /**
      * Register a {@link ProfileService} with AdapterService.
      *
@@ -276,6 +334,16 @@
         mHandler.sendMessage(m);
     }
 
+    /**
+     * Confirm whether the ProfileService is started expectedly.
+     *
+     * @param string the service simple name.
+     * @return true if the service is started expectedly, false otherwise.
+     */
+    public boolean isStartedProfile(String serviceSampleName) {
+        return mStartedProfiles.contains(serviceSampleName);
+    }
+
     private static final int MESSAGE_PROFILE_SERVICE_STATE_CHANGED = 1;
     private static final int MESSAGE_PROFILE_SERVICE_REGISTERED = 2;
     private static final int MESSAGE_PROFILE_SERVICE_UNREGISTERED = 3;
@@ -339,6 +407,7 @@
                         initProfileServices();
                         getAdapterPropertyNative(AbstractionLayer.BT_PROPERTY_LOCAL_IO_CAPS);
                         getAdapterPropertyNative(AbstractionLayer.BT_PROPERTY_LOCAL_IO_CAPS_BLE);
+                        getAdapterPropertyNative(AbstractionLayer.BT_PROPERTY_DYNAMIC_AUDIO_BUFFER);
                         mAdapterStateMachine.sendMessage(AdapterState.BREDR_STARTED);
                     }
                     break;
@@ -431,6 +500,7 @@
     public void onCreate() {
         super.onCreate();
         debugLog("onCreate()");
+        mDeviceConfigListener.start();
         mRemoteDevices = new RemoteDevices(this, Looper.getMainLooper());
         mRemoteDevices.init();
         clearDiscoveringPackages();
@@ -438,14 +508,24 @@
         mAdapterProperties = new AdapterProperties(this);
         mAdapterStateMachine = AdapterState.make(this);
         mJniCallbacks = new JniCallbacks(this, mAdapterProperties);
-        mBluetoothKeystoreService = new BluetoothKeystoreService(isNiapMode());
+        mBluetoothKeystoreService = new BluetoothKeystoreService(isCommonCriteriaMode());
         mBluetoothKeystoreService.start();
+        mActivityAttributionService = new ActivityAttributionService();
+        mActivityAttributionService.start();
         int configCompareResult = mBluetoothKeystoreService.getCompareResult();
 
+        // Start tracking Binder latency for the bluetooth process.
+        mBinderCallsSettingsObserver = new BinderCallsStats.SettingsObserver(
+                getApplicationContext(),
+                new BinderCallsStats(
+                        new BinderCallsStats.Injector(),
+                        com.android.internal.os.BinderLatencyProto.Dims.BLUETOOTH));
+
         // Android TV doesn't show consent dialogs for just works and encryption only le pairing
         boolean isAtvDevice = getApplicationContext().getPackageManager().hasSystemFeature(
                 PackageManager.FEATURE_LEANBACK_ONLY);
-        initNative(isGuest(), isNiapMode(), configCompareResult, isAtvDevice);
+        initNative(isGuest(), isCommonCriteriaMode(), configCompareResult, getInitFlags(),
+                isAtvDevice);
         mNativeAvailable = true;
         mCallbacks = new RemoteCallbackList<IBluetoothCallback>();
         mAppOps = getSystemService(AppOpsManager.class);
@@ -458,12 +538,16 @@
         mUserManager = (UserManager) getSystemService(Context.USER_SERVICE);
         mBatteryStats = IBatteryStats.Stub.asInterface(
                 ServiceManager.getService(BatteryStats.SERVICE_NAME));
+        mCompanionDeviceManager = getSystemService(CompanionDeviceManager.class);
 
         mBluetoothKeystoreService.initJni();
 
         mSdpManager = SdpManager.init(this);
         registerReceiver(mAlarmBroadcastReceiver, new IntentFilter(ACTION_ALARM_WAKEUP));
 
+        mDatabaseManager = new DatabaseManager(this);
+        mDatabaseManager.start(MetadataDatabase.createDatabase(this));
+
         // 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.
         if (getResources().getBoolean(com.android.bluetooth.R.bool.enable_phone_policy)) {
@@ -477,9 +561,6 @@
         mActiveDeviceManager = new ActiveDeviceManager(this, new ServiceFactory());
         mActiveDeviceManager.start();
 
-        mDatabaseManager = new DatabaseManager(this);
-        mDatabaseManager.start(MetadataDatabase.createDatabase(this));
-
         mSilenceDeviceManager = new SilenceDeviceManager(this, new ServiceFactory(),
                 Looper.getMainLooper());
         mSilenceDeviceManager.start();
@@ -506,8 +587,10 @@
         }.execute();
 
         try {
-            int systemUiUid = getApplicationContext().getPackageManager().getPackageUid(
-                    "com.android.systemui", PackageManager.MATCH_SYSTEM_ONLY);
+            int systemUiUid = getApplicationContext()
+                    .createContextAsUser(UserHandle.SYSTEM, /* flags= */ 0)
+                    .getPackageManager()
+                    .getPackageUid("com.android.systemui", PackageManager.MATCH_SYSTEM_ONLY);
 
             Utils.setSystemUiUid(systemUiUid);
         } catch (PackageManager.NameNotFoundException e) {
@@ -705,6 +788,46 @@
         }
     }
 
+    void linkQualityReportCallback(
+            long timestamp,
+            int reportId,
+            int rssi,
+            int snr,
+            int retransmissionCount,
+            int packetsNotReceiveCount,
+            int negativeAcknowledgementCount) {
+        BluetoothInCallService bluetoothInCallService = BluetoothInCallService.getInstance();
+
+        if (reportId == BqrQualityReportId.QUALITY_REPORT_ID_SCO_VOICE_CHOPPY.getValue()) {
+            if (bluetoothInCallService == null) {
+                Log.w(TAG, "No BluetoothInCallService while trying to send BQR."
+                        + " timestamp: " + timestamp + " reportId: " + reportId
+                        + " rssi: " + rssi + " snr: " + snr
+                        + " retransmissionCount: " + retransmissionCount
+                        + " packetsNotReceiveCount: " + packetsNotReceiveCount
+                        + " negativeAcknowledgementCount: " + negativeAcknowledgementCount);
+                return;
+            }
+            bluetoothInCallService.sendBluetoothCallQualityReport(
+                    timestamp, rssi, snr, retransmissionCount,
+                    packetsNotReceiveCount, negativeAcknowledgementCount);
+        }
+    }
+
+    /**
+     * Enable/disable BluetoothInCallService
+     *
+     * @param enable to enable/disable BluetoothInCallService.
+     */
+    public void enableBluetoothInCallService(boolean enable) {
+        debugLog("enableBluetoothInCallService() - Enable = " + enable);
+        getPackageManager().setComponentEnabledSetting(
+                BLUETOOTH_INCALLSERVICE_COMPONENT,
+                enable ? PackageManager.COMPONENT_ENABLED_STATE_ENABLED
+                        : PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
+                PackageManager.DONT_KILL_APP);
+    }
+
     void cleanup() {
         debugLog("cleanup()");
         if (mCleaningUp) {
@@ -760,6 +883,10 @@
             mBluetoothKeystoreService.cleanup();
         }
 
+        if (mActivityAttributionService != null) {
+            mActivityAttributionService.cleanup();
+        }
+
         if (mNativeAvailable) {
             debugLog("cleanup() - Cleaning up adapter native");
             cleanupNative();
@@ -810,9 +937,15 @@
         BluetoothAdapter.invalidateIsOffloadedFilteringSupportedCache();
         BluetoothDevice.invalidateBluetoothGetBondStateCache();
         BluetoothAdapter.invalidateBluetoothGetStateCache();
+        BluetoothAdapter.invalidateGetAdapterConnectionStateCache();
     }
 
     private void setProfileServiceState(Class service, int state) {
+        if (state == BluetoothAdapter.STATE_ON) {
+            mStartedProfiles.add(service.getSimpleName());
+        } else if (state == BluetoothAdapter.STATE_OFF) {
+            mStartedProfiles.remove(service.getSimpleName());
+        }
         Intent intent = new Intent(this, service);
         intent.putExtra(EXTRA_ACTION, ACTION_SERVICE_STATE_CHANGED);
         intent.putExtra(BluetoothAdapter.EXTRA_STATE, state);
@@ -838,6 +971,7 @@
      * @param device            is the remote device we wish to connect to
      * @return true if the profile is supported by both the local and remote device, false otherwise
      */
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED)
     private boolean isSupported(ParcelUuid[] localDeviceUuids, ParcelUuid[] remoteDeviceUuids,
             int profile, BluetoothDevice device) {
         if (remoteDeviceUuids == null || remoteDeviceUuids.length == 0) {
@@ -906,6 +1040,7 @@
      * @param device is the device for which we are checking if any profiles are enabled
      * @return true if any profile is enabled, false otherwise
      */
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED)
     private boolean isAnyProfileEnabled(BluetoothDevice device) {
 
         if (mA2dpService != null && mA2dpService.getConnectionPolicy(device)
@@ -955,6 +1090,10 @@
      * @param device is the device with which we are connecting the profiles
      * @return true
      */
+    @RequiresPermission(allOf = {
+            android.Manifest.permission.BLUETOOTH_PRIVILEGED,
+            android.Manifest.permission.MODIFY_PHONE_STATE,
+    })
     private boolean connectEnabledProfiles(BluetoothDevice device) {
         ParcelUuid[] remoteDeviceUuids = getRemoteUuids(device);
         ParcelUuid[] localDeviceUuids = mAdapterProperties.getUuids();
@@ -1105,100 +1244,118 @@
                 return BluetoothAdapter.STATE_OFF;
             }
 
-            enforceBluetoothPermission(service);
-
             return service.getState();
         }
 
         @Override
-        public boolean enable(boolean quietMode) {
+        public boolean enable(boolean quietMode, AttributionSource attributionSource) {
             AdapterService service = getService();
-            if (service == null || !callerIsSystemOrActiveUser(TAG, "enable")) {
+            if (service == null || !callerIsSystemOrActiveUser(TAG, "enable")
+                    || !Utils.checkConnectPermissionForDataDelivery(
+                            service, attributionSource, "AdapterService enable")) {
                 return false;
             }
 
-            enforceBluetoothAdminPermission(service);
-
             return service.enable(quietMode);
         }
 
         @Override
-        public boolean disable() {
+        public boolean disable(AttributionSource attributionSource) {
             AdapterService service = getService();
-            if (service == null || !callerIsSystemOrActiveUser(TAG, "disable")) {
+            if (service == null || !callerIsSystemOrActiveUser(TAG, "disable")
+                    || !Utils.checkConnectPermissionForDataDelivery(
+                            service, attributionSource, "AdapterService disable")) {
                 return false;
             }
 
-            enforceBluetoothAdminPermission(service);
-
             return service.disable();
         }
 
         @Override
         public String getAddress() {
+            return getAddressWithAttribution(Utils.getCallingAttributionSource());
+        }
+
+        @Override
+        public String getAddressWithAttribution(AttributionSource attributionSource) {
             AdapterService service = getService();
-            if (service == null || !callerIsSystemOrActiveOrManagedUser(service, TAG, "getAddress")) {
+            if (service == null || !callerIsSystemOrActiveOrManagedUser(service, TAG, "getAddress")
+                    || !Utils.checkConnectPermissionForDataDelivery(
+                            service, attributionSource, "AdapterService getAddress")) {
                 return null;
             }
 
-            enforceBluetoothPermission(service);
             enforceLocalMacAddressPermission(service);
 
             return Utils.getAddressStringFromByte(service.mAdapterProperties.getAddress());
         }
 
         @Override
-        public ParcelUuid[] getUuids() {
+        public ParcelUuid[] getUuids(AttributionSource attributionSource) {
             AdapterService service = getService();
-            if (service == null || !callerIsSystemOrActiveUser(TAG, "getUuids")) {
+            if (service == null || !callerIsSystemOrActiveUser(TAG, "getUuids")
+                    || !Utils.checkConnectPermissionForDataDelivery(
+                            service, attributionSource, "AdapterService getUuids")) {
                 return new ParcelUuid[0];
             }
 
-            enforceBluetoothPermission(service);
-
             return service.mAdapterProperties.getUuids();
         }
 
         @Override
-        public String getName() {
+        public String getName(AttributionSource attributionSource) {
             AdapterService service = getService();
-            if (service == null || !callerIsSystemOrActiveUser(TAG, "getName")) {
+            if (service == null || !callerIsSystemOrActiveUser(TAG, "getName")
+                    || !Utils.checkConnectPermissionForDataDelivery(
+                            service, attributionSource, "AdapterService getName")) {
                 return null;
             }
 
-            enforceBluetoothPermission(service);
-
             return service.getName();
         }
 
         @Override
-        public boolean setName(String name) {
+        public int getNameLengthForAdvertise(AttributionSource attributionSource) {
             AdapterService service = getService();
-            if (service == null || !callerIsSystemOrActiveUser(TAG, "setName")) {
-                return false;
+            if (service == null || !callerIsSystemOrActiveUser(TAG, "getNameLengthForAdvertise")
+                    || !Utils.checkAdvertisePermissionForDataDelivery(
+                            service, attributionSource, TAG)) {
+                return -1;
             }
 
-            enforceBluetoothAdminPermission(service);
+            return service.getNameLengthForAdvertise();
+        }
+
+        @Override
+        public boolean setName(String name, AttributionSource attributionSource) {
+            AdapterService service = getService();
+            if (service == null || !callerIsSystemOrActiveUser(TAG, "setName")
+                    || !Utils.checkConnectPermissionForDataDelivery(
+                            service, attributionSource, "AdapterService setName")) {
+                return false;
+            }
 
             return service.mAdapterProperties.setName(name);
         }
 
         @Override
-        public BluetoothClass getBluetoothClass() {
+        public BluetoothClass getBluetoothClass(AttributionSource attributionSource) {
             AdapterService service = getService();
-            if (service == null || !callerIsSystemOrActiveUser(TAG, "getBluetoothClass")) {
+            if (service == null || !callerIsSystemOrActiveUser(TAG, "getBluetoothClass")
+                    || !Utils.checkConnectPermissionForDataDelivery(
+                            service, attributionSource, "AdapterSource getBluetoothClass")) {
                 return null;
             }
 
-            enforceBluetoothAdminPermission(service);
-
             return service.mAdapterProperties.getBluetoothClass();
         }
 
         @Override
-        public boolean setBluetoothClass(BluetoothClass bluetoothClass) {
+        public boolean setBluetoothClass(BluetoothClass bluetoothClass, AttributionSource source) {
             AdapterService service = getService();
-            if (service == null || !callerIsSystemOrActiveUser(TAG, "setBluetoothClass")) {
+            if (service == null
+                    || !Utils.checkCallerIsSystemOrActiveUser(TAG)
+                    || !Utils.checkConnectPermissionForDataDelivery(service, source, TAG)) {
                 return false;
             }
 
@@ -1215,21 +1372,23 @@
         }
 
         @Override
-        public int getIoCapability() {
+        public int getIoCapability(AttributionSource attributionSource) {
             AdapterService service = getService();
-            if (service == null || !callerIsSystemOrActiveUser(TAG, "getIoCapability")) {
+            if (service == null || !callerIsSystemOrActiveUser(TAG, "getIoCapability")
+                    || !Utils.checkConnectPermissionForDataDelivery(
+                            service, attributionSource, "AdapterService getIoCapability")) {
                 return BluetoothAdapter.IO_CAPABILITY_UNKNOWN;
             }
 
-            enforceBluetoothAdminPermission(service);
-
             return service.mAdapterProperties.getIoCapability();
         }
 
         @Override
-        public boolean setIoCapability(int capability) {
+        public boolean setIoCapability(int capability, AttributionSource source) {
             AdapterService service = getService();
-            if (service == null || !callerIsSystemOrActiveUser(TAG, "setIoCapability")) {
+            if (service == null
+                    || !Utils.checkCallerIsSystemOrActiveUser(TAG)
+                    || !Utils.checkConnectPermissionForDataDelivery(service, source, TAG)) {
                 return false;
             }
 
@@ -1243,21 +1402,23 @@
         }
 
         @Override
-        public int getLeIoCapability() {
+        public int getLeIoCapability(AttributionSource attributionSource) {
             AdapterService service = getService();
-            if (service == null || !callerIsSystemOrActiveUser(TAG, "getLeIoCapability")) {
+            if (service == null || !callerIsSystemOrActiveUser(TAG, "getLeIoCapability")
+                    || !Utils.checkConnectPermissionForDataDelivery(
+                            service, attributionSource, "AdapterService getLeIoCapability")) {
                 return BluetoothAdapter.IO_CAPABILITY_UNKNOWN;
             }
 
-            enforceBluetoothAdminPermission(service);
-
             return service.mAdapterProperties.getLeIoCapability();
         }
 
         @Override
-        public boolean setLeIoCapability(int capability) {
+        public boolean setLeIoCapability(int capability, AttributionSource source) {
             AdapterService service = getService();
-            if (service == null || !callerIsSystemOrActiveUser(TAG, "setLeIoCapability")) {
+            if (service == null
+                    || !Utils.checkCallerIsSystemOrActiveUser(TAG)
+                    || !Utils.checkConnectPermissionForDataDelivery(service, source, TAG)) {
                 return false;
             }
 
@@ -1271,21 +1432,23 @@
         }
 
         @Override
-        public int getScanMode() {
+        public int getScanMode(AttributionSource attributionSource) {
             AdapterService service = getService();
-            if (service == null || !callerIsSystemOrActiveOrManagedUser(service, TAG, "getScanMode")) {
+            if (service == null || !callerIsSystemOrActiveOrManagedUser(service, TAG, "getScanMode")
+                    || !Utils.checkScanPermissionForDataDelivery(
+                            service, attributionSource, "AdapterService getScanMode")) {
                 return BluetoothAdapter.SCAN_MODE_NONE;
             }
 
-            enforceBluetoothPermission(service);
-
             return service.mAdapterProperties.getScanMode();
         }
 
         @Override
-        public boolean setScanMode(int mode, int duration) {
+        public boolean setScanMode(int mode, int duration, AttributionSource attributionSource) {
             AdapterService service = getService();
-            if (service == null || !callerIsSystemOrActiveUser(TAG, "setScanMode")) {
+            if (service == null || !callerIsSystemOrActiveUser(TAG, "setScanMode")
+                    || !Utils.checkScanPermissionForDataDelivery(
+                            service, attributionSource, "AdapterService setScanMode")) {
                 return false;
             }
 
@@ -1296,21 +1459,23 @@
         }
 
         @Override
-        public int getDiscoverableTimeout() {
+        public int getDiscoverableTimeout(AttributionSource attributionSource) {
             AdapterService service = getService();
-            if (service == null || !callerIsSystemOrActiveUser(TAG, "getDiscoverableTimeout")) {
+            if (service == null || !callerIsSystemOrActiveUser(TAG, "getDiscoverableTimeout")
+                    || !Utils.checkScanPermissionForDataDelivery(
+                            service, attributionSource, "AdapterService getDiscoverableTimeout")) {
                 return 0;
             }
 
-            enforceBluetoothPermission(service);
-
             return service.mAdapterProperties.getDiscoverableTimeout();
         }
 
         @Override
-        public boolean setDiscoverableTimeout(int timeout) {
+        public boolean setDiscoverableTimeout(int timeout, AttributionSource attributionSource) {
             AdapterService service = getService();
-            if (service == null || !callerIsSystemOrActiveUser(TAG, "setDiscoverableTimeout")) {
+            if (service == null || !callerIsSystemOrActiveUser(TAG, "setDiscoverableTimeout")
+                    || !Utils.checkScanPermissionForDataDelivery(
+                            service, attributionSource, "AdapterService setDiscoverableTimeout")) {
                 return false;
             }
 
@@ -1320,46 +1485,52 @@
         }
 
         @Override
-        public boolean startDiscovery(String callingPackage, String callingFeatureId) {
+        public boolean startDiscovery(AttributionSource attributionSource) {
             AdapterService service = getService();
             if (service == null || !callerIsSystemOrActiveUser(TAG, "startDiscovery")) {
                 return false;
             }
 
-            enforceBluetoothAdminPermission(service);
-
-            return service.startDiscovery(callingPackage, callingFeatureId);
-        }
-
-        @Override
-        public boolean cancelDiscovery() {
-            AdapterService service = getService();
-            if (service == null || !callerIsSystemOrActiveUser(TAG, "cancelDiscovery")) {
+            if (!Utils.checkScanPermissionForDataDelivery(
+                    service, attributionSource, "Starting discovery.")) {
                 return false;
             }
 
-            enforceBluetoothAdminPermission(service);
+            return service.startDiscovery(attributionSource);
+        }
+
+        @Override
+        public boolean cancelDiscovery(AttributionSource attributionSource) {
+            AdapterService service = getService();
+            if (service == null || !callerIsSystemOrActiveUser(TAG, "cancelDiscovery")
+                    || !Utils.checkScanPermissionForDataDelivery(
+                            service, attributionSource, "AdapterService cancelDiscovery")) {
+                return false;
+            }
 
             service.debugLog("cancelDiscovery");
             return service.cancelDiscoveryNative();
         }
 
         @Override
-        public boolean isDiscovering() {
+        public boolean isDiscovering(AttributionSource attributionSource) {
             AdapterService service = getService();
-            if (service == null || !callerIsSystemOrActiveOrManagedUser(service, TAG, "isDiscovering")) {
+            if (service == null
+                    || !callerIsSystemOrActiveOrManagedUser(service, TAG, "isDiscovering")
+                    || !Utils.checkScanPermissionForDataDelivery(
+                            service, attributionSource, "AdapterService isDiscovering")) {
                 return false;
             }
 
-            enforceBluetoothPermission(service);
-
             return service.mAdapterProperties.isDiscovering();
         }
 
         @Override
-        public long getDiscoveryEndMillis() {
+        public long getDiscoveryEndMillis(AttributionSource source) {
             AdapterService service = getService();
-            if (service == null || !callerIsSystemOrActiveUser(TAG, "getDiscoveryEndMillis")) {
+            if (service == null
+                    || !Utils.checkCallerIsSystemOrActiveUser(TAG)
+                    || !Utils.checkConnectPermissionForDataDelivery(service, source, TAG)) {
                 return -1;
             }
 
@@ -1369,28 +1540,27 @@
         }
 
         @Override
-        public List<BluetoothDevice> getMostRecentlyConnectedDevices() {
+        public List<BluetoothDevice> getMostRecentlyConnectedDevices(
+                AttributionSource attributionSource) {
             // don't check caller, may be called from system UI
             AdapterService service = getService();
-            if (service == null) {
+            if (service == null || !Utils.checkConnectPermissionForDataDelivery(
+                    service, attributionSource, "AdapterService getMostRecentlyConnectedDevices")) {
                 return new ArrayList<>();
             }
 
-            enforceBluetoothAdminPermission(service);
-
             return service.mDatabaseManager.getMostRecentlyConnectedDevices();
         }
 
         @Override
-        public BluetoothDevice[] getBondedDevices() {
+        public BluetoothDevice[] getBondedDevices(AttributionSource attributionSource) {
             // don't check caller, may be called from system UI
             AdapterService service = getService();
-            if (service == null) {
+            if (service == null || !Utils.checkConnectPermissionForDataDelivery(
+                    service, attributionSource, "AdapterService getBondedDevices")) {
                 return new BluetoothDevice[0];
             }
 
-            enforceBluetoothPermission(service);
-
             return service.getBondedDevices();
         }
 
@@ -1402,8 +1572,6 @@
                 return BluetoothAdapter.STATE_DISCONNECTED;
             }
 
-            enforceBluetoothPermission(service);
-
             return service.mAdapterProperties.getConnectionState();
         }
 
@@ -1414,36 +1582,46 @@
         @Override
         public int getProfileConnectionState(int profile) {
             AdapterService service = getService();
-            if (service == null || !callerIsSystemOrActiveOrManagedUser(service, TAG, "getProfileConnectionState")) {
+            if (service == null
+                    || !callerIsSystemOrActiveOrManagedUser(
+                            service, TAG, "getProfileConnectionState")) {
                 return BluetoothProfile.STATE_DISCONNECTED;
             }
 
-            enforceBluetoothPermission(service);
-
             return service.mAdapterProperties.getProfileConnectionState(profile);
         }
 
         @Override
-        public boolean createBond(BluetoothDevice device, int transport, OobData oobData) {
+        public boolean createBond(BluetoothDevice device, int transport, OobData remoteP192Data,
+                OobData remoteP256Data, AttributionSource attributionSource) {
+            Attributable.setAttributionSource(device, attributionSource);
             AdapterService service = getService();
-            if (service == null || !callerIsSystemOrActiveOrManagedUser(service, TAG, "createBond")) {
+            if (service == null || !callerIsSystemOrActiveOrManagedUser(service, TAG, "createBond")
+                    || !Utils.checkConnectPermissionForDataDelivery(
+                            service, attributionSource, "AdapterService createBond")) {
                 return false;
             }
 
-            enforceBluetoothAdminPermission(service);
+            // This conditional is required to satisfy permission dependencies
+            // since createBond calls createBondOutOfBand with null value passed as data.
+            // BluetoothDevice#createBond requires BLUETOOTH_ADMIN only.
+            service.enforceBluetoothPrivilegedPermissionIfNeeded(remoteP192Data, remoteP256Data);
 
-            return service.createBond(device, transport, oobData);
+            return service.createBond(device, transport, remoteP192Data, remoteP256Data,
+                    attributionSource.getPackageName());
         }
 
         @Override
-        public boolean cancelBondProcess(BluetoothDevice device) {
+        public boolean cancelBondProcess(
+                BluetoothDevice device, AttributionSource attributionSource) {
+            Attributable.setAttributionSource(device, attributionSource);
             AdapterService service = getService();
-            if (service == null || !callerIsSystemOrActiveUser(TAG, "cancelBondProcess")) {
+            if (service == null || !callerIsSystemOrActiveUser(TAG, "cancelBondProcess")
+                    || !Utils.checkConnectPermissionForDataDelivery(
+                            service, attributionSource, "AdapterService cancelBondProcess")) {
                 return false;
             }
 
-            enforceBluetoothAdminPermission(service);
-
             DeviceProperties deviceProp = service.mRemoteDevices.getDeviceProperties(device);
             if (deviceProp != null) {
                 deviceProp.setBondingInitiatedLocally(false);
@@ -1453,18 +1631,20 @@
         }
 
         @Override
-        public boolean removeBond(BluetoothDevice device) {
+        public boolean removeBond(BluetoothDevice device, AttributionSource attributionSource) {
+            Attributable.setAttributionSource(device, attributionSource);
             AdapterService service = getService();
-            if (service == null || !callerIsSystemOrActiveUser(TAG, "removeBond")) {
+            if (service == null || !callerIsSystemOrActiveUser(TAG, "removeBond")
+                    || !Utils.checkConnectPermissionForDataDelivery(
+                            service, attributionSource, "AdapterService removeBond")) {
                 return false;
             }
 
-            enforceBluetoothAdminPermission(service);
-
             DeviceProperties deviceProp = service.mRemoteDevices.getDeviceProperties(device);
             if (deviceProp == null || deviceProp.getBondState() != BluetoothDevice.BOND_BONDED) {
                 return false;
             }
+            service.mBondAttemptCallerInfo.remove(device.getAddress());
             deviceProp.setBondingInitiatedLocally(false);
 
             Message msg = service.mBondStateMachine.obtainMessage(BondStateMachine.REMOVE_BOND);
@@ -1474,33 +1654,47 @@
         }
 
         @Override
-        public int getBondState(BluetoothDevice device) {
+        public int getBondState(BluetoothDevice device, AttributionSource attributionSource) {
             // don't check caller, may be called from system UI
+            Attributable.setAttributionSource(device, attributionSource);
             AdapterService service = getService();
-            if (service == null) {
+            if (service == null || !Utils.checkConnectPermissionForDataDelivery(
+                    service, attributionSource, "AdapterService getBondState")) {
                 return BluetoothDevice.BOND_NONE;
             }
 
-            enforceBluetoothPermission(service);
-
             return service.getBondState(device);
         }
 
         @Override
-        public boolean isBondingInitiatedLocally(BluetoothDevice device) {
+        public boolean isBondingInitiatedLocally(
+                BluetoothDevice device, AttributionSource attributionSource) {
             // don't check caller, may be called from system UI
+            Attributable.setAttributionSource(device, attributionSource);
             AdapterService service = getService();
-            if (service == null) {
+            if (service == null || !Utils.checkConnectPermissionForDataDelivery(
+                    service, attributionSource, "AdapterService isBondingInitiatedLocally")) {
                 return false;
             }
 
-            enforceBluetoothPermission(service);
-
             DeviceProperties deviceProp = service.mRemoteDevices.getDeviceProperties(device);
             return deviceProp != null && deviceProp.isBondingInitiatedLocally();
         }
 
         @Override
+        public void generateLocalOobData(int transport, IBluetoothOobDataCallback callback,
+                AttributionSource source) {
+            AdapterService service = getService();
+            if (service == null
+                    || !Utils.checkCallerIsSystemOrActiveOrManagedUser(service, TAG)
+                    || !Utils.checkConnectPermissionForDataDelivery(service, source, TAG)) {
+                return;
+            }
+            enforceBluetoothPrivilegedPermission(service);
+            service.generateLocalOobData(transport, callback);
+        }
+
+        @Override
         public long getSupportedProfiles() {
             AdapterService service = getService();
             if (service == null) {
@@ -1511,173 +1705,253 @@
 
         @Override
         public int getConnectionState(BluetoothDevice device) {
+            return getConnectionStateWithAttribution(device, Utils.getCallingAttributionSource());
+        }
+
+        @Override
+        public int getConnectionStateWithAttribution(
+                BluetoothDevice device, AttributionSource attributionSource) {
+            Attributable.setAttributionSource(device, attributionSource);
             AdapterService service = getService();
-            if (service == null) {
+            if (service == null || !Utils.checkConnectPermissionForDataDelivery(
+                    service, attributionSource, "AdapterService getConnectionState")) {
                 return 0;
             }
 
-            enforceBluetoothPermission(service);
-
             return service.getConnectionState(device);
         }
 
         @Override
-        public boolean removeActiveDevice(@ActiveDeviceUse int profiles) {
-            if (!Utils.checkCaller()) {
-                Log.w(TAG, "removeActiveDevice() - Not allowed for non-active user");
+        public boolean canBondWithoutDialog(BluetoothDevice device, AttributionSource source) {
+            Attributable.setAttributionSource(device, source);
+            AdapterService service = getService();
+            if (service == null
+                    || !Utils.checkConnectPermissionForDataDelivery(service, source, TAG)) {
                 return false;
             }
 
+            enforceBluetoothPrivilegedPermission(service);
+
+            return service.canBondWithoutDialog(device);
+        }
+
+        @Override
+        public boolean removeActiveDevice(@ActiveDeviceUse int profiles,
+                AttributionSource source) {
             AdapterService service = getService();
-            if (service == null) {
+            if (service == null
+                    || !Utils.checkCallerIsSystemOrActiveUser(TAG)
+                    || !Utils.checkConnectPermissionForDataDelivery(service, source, TAG)) {
                 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;
-            }
-
+        public boolean setActiveDevice(BluetoothDevice device, @ActiveDeviceUse int profiles,
+                AttributionSource source) {
+            Attributable.setAttributionSource(device, source);
             AdapterService service = getService();
-            if (service == null) {
+            if (service == null
+                    || !Utils.checkCallerIsSystemOrActiveUser(TAG)
+                    || !Utils.checkConnectPermissionForDataDelivery(service, source, TAG)) {
                 return false;
             }
             return service.setActiveDevice(device, profiles);
         }
 
         @Override
-        public boolean connectAllEnabledProfiles(BluetoothDevice device) {
+        public boolean connectAllEnabledProfiles(BluetoothDevice device,
+                AttributionSource source) {
+            Attributable.setAttributionSource(device, source);
             AdapterService service = getService();
-            if (service == null || !callerIsSystemOrActiveUser(TAG, "connectAllEnabledProfiles")) {
+            if (service == null
+                    || !Utils.checkCallerIsSystemOrActiveUser(TAG)
+                    || !Utils.checkConnectPermissionForDataDelivery(service, source, TAG)) {
                 return false;
             }
 
             enforceBluetoothPrivilegedPermission(service);
 
-            return service.connectAllEnabledProfiles(device);
+            try {
+                return service.connectAllEnabledProfiles(device);
+            } catch (Exception e) {
+                Log.v(TAG, "connectAllEnabledProfiles() failed", e);
+                SneakyThrow.sneakyThrow(e);
+                throw new RuntimeException(e);
+            }
         }
 
         @Override
-        public boolean disconnectAllEnabledProfiles(BluetoothDevice device) {
+        public boolean disconnectAllEnabledProfiles(BluetoothDevice device,
+                AttributionSource source) {
+            Attributable.setAttributionSource(device, source);
             AdapterService service = getService();
-            if (service == null | !callerIsSystemOrActiveUser(TAG, "disconnectAllEnabledProfiles")) {
+            if (service == null
+                    || !Utils.checkCallerIsSystemOrActiveUser(TAG)
+                    || !Utils.checkConnectPermissionForDataDelivery(service, source, TAG)) {
                 return false;
             }
 
             enforceBluetoothPrivilegedPermission(service);
 
-            return service.disconnectAllEnabledProfiles(device);
+            try {
+                return service.disconnectAllEnabledProfiles(device);
+            } catch (Exception e) {
+                Log.v(TAG, "disconnectAllEnabledProfiles() failed", e);
+                SneakyThrow.sneakyThrow(e);
+                throw new RuntimeException(e);
+            }
         }
 
         @Override
-        public String getRemoteName(BluetoothDevice device) {
+        public String getRemoteName(BluetoothDevice device, AttributionSource attributionSource) {
+            Attributable.setAttributionSource(device, attributionSource);
             AdapterService service = getService();
-            if (service == null || !callerIsSystemOrActiveOrManagedUser(service, TAG, "getRemoteName")) {
+            if (service == null
+                    || !callerIsSystemOrActiveOrManagedUser(service, TAG, "getRemoteName")
+                    || !Utils.checkConnectPermissionForDataDelivery(
+                            service, attributionSource, "AdapterService getRemoteName")) {
                 return null;
             }
 
-            enforceBluetoothPermission(service);
-
             return service.getRemoteName(device);
         }
 
         @Override
-        public int getRemoteType(BluetoothDevice device) {
+        public int getRemoteType(BluetoothDevice device, AttributionSource attributionSource) {
+            Attributable.setAttributionSource(device, attributionSource);
             AdapterService service = getService();
-            if (service == null || !callerIsSystemOrActiveOrManagedUser(service, TAG, "getRemoteType")) {
+            if (service == null
+                    || !callerIsSystemOrActiveOrManagedUser(service, TAG, "getRemoteType")
+                    || !Utils.checkConnectPermissionForDataDelivery(
+                            service, attributionSource, "AdapterService getRemoteType")) {
                 return BluetoothDevice.DEVICE_TYPE_UNKNOWN;
             }
 
-            enforceBluetoothPermission(service);
-
             DeviceProperties deviceProp = service.mRemoteDevices.getDeviceProperties(device);
             return deviceProp != null ? deviceProp.getDeviceType() : BluetoothDevice.DEVICE_TYPE_UNKNOWN;
         }
 
         @Override
         public String getRemoteAlias(BluetoothDevice device) {
+            return getRemoteAliasWithAttribution(device, Utils.getCallingAttributionSource());
+        }
+
+        @Override
+        public String getRemoteAliasWithAttribution(
+                BluetoothDevice device, AttributionSource attributionSource) {
+            Attributable.setAttributionSource(device, attributionSource);
             AdapterService service = getService();
-            if (service == null || !callerIsSystemOrActiveOrManagedUser(service, TAG, "getRemoteAlias")) {
+            if (service == null
+                    || !callerIsSystemOrActiveOrManagedUser(service, TAG, "getRemoteAlias")
+                    || !Utils.checkConnectPermissionForDataDelivery(
+                            service, attributionSource, "AdapterService getRemoteAlias")) {
                 return null;
             }
 
-            enforceBluetoothPermission(service);
-
             DeviceProperties deviceProp = service.mRemoteDevices.getDeviceProperties(device);
             return deviceProp != null ? deviceProp.getAlias() : null;
         }
 
         @Override
-        public boolean setRemoteAlias(BluetoothDevice device, String name) {
+        public int setRemoteAlias(BluetoothDevice device, String name,
+                AttributionSource attributionSource) {
+            Attributable.setAttributionSource(device, attributionSource);
             AdapterService service = getService();
-            if (service == null || !callerIsSystemOrActiveUser(TAG, "setRemoteAlias")) {
-                return false;
+            if (service == null) {
+                return BluetoothStatusCodes.ERROR_BLUETOOTH_NOT_ENABLED;
+            }
+            if (!callerIsSystemOrActiveUser(TAG, "setRemoteAlias")) {
+                return BluetoothStatusCodes.ERROR_BLUETOOTH_NOT_ALLOWED;
+            }
+            if (name != null && name.isEmpty()) {
+                throw new IllegalArgumentException("alias cannot be the empty string");
             }
 
-            enforceBluetoothPermission(service);
+            if (!hasBluetoothPrivilegedPermission(service)) {
+                if (!Utils.checkConnectPermissionForDataDelivery(
+                        service, attributionSource, "AdapterService setRemoteAlias")) {
+                    return BluetoothStatusCodes.ERROR_MISSING_BLUETOOTH_CONNECT_PERMISSION;
+                }
+                enforceCdmAssociation(service.mCompanionDeviceManager, service,
+                        attributionSource.getPackageName(), Binder.getCallingUid(), device);
+            }
 
             DeviceProperties deviceProp = service.mRemoteDevices.getDeviceProperties(device);
             if (deviceProp == null) {
-                return false;
+                return BluetoothStatusCodes.ERROR_DEVICE_NOT_BONDED;
             }
             deviceProp.setAlias(device, name);
-            return true;
+            return BluetoothStatusCodes.SUCCESS;
         }
 
         @Override
-        public int getRemoteClass(BluetoothDevice device) {
+        public int getRemoteClass(BluetoothDevice device, AttributionSource attributionSource) {
+            Attributable.setAttributionSource(device, attributionSource);
             AdapterService service = getService();
-            if (service == null || !callerIsSystemOrActiveOrManagedUser(service, TAG, "getRemoteClass")) {
+            if (service == null
+                    || !callerIsSystemOrActiveOrManagedUser(service, TAG, "getRemoteClass")
+                    || !Utils.checkConnectPermissionForDataDelivery(
+                            service, attributionSource, "AdapterService getRemoteClass")) {
                 return 0;
             }
 
-            enforceBluetoothPermission(service);
-
             DeviceProperties deviceProp = service.mRemoteDevices.getDeviceProperties(device);
             return deviceProp != null ? deviceProp.getBluetoothClass() : 0;
         }
 
         @Override
-        public ParcelUuid[] getRemoteUuids(BluetoothDevice device) {
+        public ParcelUuid[] getRemoteUuids(
+                BluetoothDevice device, AttributionSource attributionSource) {
+            Attributable.setAttributionSource(device, attributionSource);
             AdapterService service = getService();
-            if (service == null || !callerIsSystemOrActiveOrManagedUser(service, TAG, "getRemoteUuids")) {
+            if (service == null
+                    || !callerIsSystemOrActiveOrManagedUser(service, TAG, "getRemoteUuids")
+                    || !Utils.checkConnectPermissionForDataDelivery(
+                            service, attributionSource, "AdapterService getRemoteUuids")) {
                 return new ParcelUuid[0];
             }
 
-            enforceBluetoothPermission(service);
-
             return service.getRemoteUuids(device);
         }
 
         @Override
         public boolean fetchRemoteUuids(BluetoothDevice device) {
+            return fetchRemoteUuidsWithAttribution(device, Utils.getCallingAttributionSource());
+        }
+
+        @Override
+        public boolean fetchRemoteUuidsWithAttribution(
+                BluetoothDevice device, AttributionSource attributionSource) {
+            Attributable.setAttributionSource(device, attributionSource);
             AdapterService service = getService();
-            if (service == null || !callerIsSystemOrActiveOrManagedUser(service, TAG, "fetchRemoteUuids")) {
+            if (service == null
+                    || !callerIsSystemOrActiveOrManagedUser(service, TAG, "fetchRemoteUuids")
+                    || !Utils.checkConnectPermissionForDataDelivery(
+                            service, attributionSource, "AdapterService fetchRemoteUuids")) {
                 return false;
             }
 
-            enforceBluetoothPermission(service);
-
             service.mRemoteDevices.fetchUuids(device);
             return true;
         }
 
 
         @Override
-        public boolean setPin(BluetoothDevice device, boolean accept, int len, byte[] pinCode) {
+        public boolean setPin(BluetoothDevice device, boolean accept, int len, byte[] pinCode,
+                AttributionSource attributionSource) {
+            Attributable.setAttributionSource(device, attributionSource);
             AdapterService service = getService();
-            if (service == null || !callerIsSystemOrActiveUser(TAG, "setPin")) {
+            if (service == null || !callerIsSystemOrActiveUser(TAG, "setPin")
+                    || !Utils.checkConnectPermissionForDataDelivery(
+                            service, attributionSource, "AdapterService setPin")) {
                 return false;
             }
 
-            enforceBluetoothAdminPermission(service);
-
             DeviceProperties deviceProp = service.mRemoteDevices.getDeviceProperties(device);
-            // Only allow setting a pin in bonding state, or bonded state in case of security upgrade.
+            // Only allow setting a pin in bonding state, or bonded state in case of security
+            // upgrade.
             if (deviceProp == null || !deviceProp.isBondingOrBonded()) {
                 return false;
             }
@@ -1691,14 +1965,16 @@
         }
 
         @Override
-        public boolean setPasskey(BluetoothDevice device, boolean accept, int len, byte[] passkey) {
+        public boolean setPasskey(BluetoothDevice device, boolean accept, int len, byte[] passkey,
+                AttributionSource attributionSource) {
+            Attributable.setAttributionSource(device, attributionSource);
             AdapterService service = getService();
-            if (service == null || !callerIsSystemOrActiveUser(TAG, "setPasskey")) {
+            if (service == null || !callerIsSystemOrActiveUser(TAG, "setPasskey")
+                    || !Utils.checkConnectPermissionForDataDelivery(
+                            service, attributionSource, "AdapterService setPasskey")) {
                 return false;
             }
 
-            enforceBluetoothPermission(service);
-
             DeviceProperties deviceProp = service.mRemoteDevices.getDeviceProperties(device);
             if (deviceProp == null || !deviceProp.isBonding()) {
                 return false;
@@ -1717,9 +1993,13 @@
         }
 
         @Override
-        public boolean setPairingConfirmation(BluetoothDevice device, boolean accept) {
+        public boolean setPairingConfirmation(BluetoothDevice device, boolean accept,
+                AttributionSource source) {
+            Attributable.setAttributionSource(device, source);
             AdapterService service = getService();
-            if (service == null || !callerIsSystemOrActiveUser(TAG, "setPairingConfirmation")) {
+            if (service == null
+                    || !Utils.checkCallerIsSystemOrActiveUser(TAG)
+                    || !Utils.checkConnectPermissionForDataDelivery(service, source, TAG)) {
                 return false;
             }
 
@@ -1738,9 +2018,12 @@
         }
 
         @Override
-        public boolean getSilenceMode(BluetoothDevice device) {
+        public boolean getSilenceMode(BluetoothDevice device, AttributionSource source) {
+            Attributable.setAttributionSource(device, source);
             AdapterService service = getService();
-            if (service == null || !callerIsSystemOrActiveUser(TAG, "getSilenceMode")) {
+            if (service == null
+                    || !Utils.checkCallerIsSystemOrActiveUser(TAG)
+                    || !Utils.checkConnectPermissionForDataDelivery(service, source, TAG)) {
                 return false;
             }
 
@@ -1751,9 +2034,13 @@
 
 
         @Override
-        public boolean setSilenceMode(BluetoothDevice device, boolean silence) {
+        public boolean setSilenceMode(BluetoothDevice device, boolean silence,
+                AttributionSource source) {
+            Attributable.setAttributionSource(device, source);
             AdapterService service = getService();
-            if (service == null || !callerIsSystemOrActiveUser(TAG, "setSilenceMode")) {
+            if (service == null
+                    || !Utils.checkCallerIsSystemOrActiveUser(TAG)
+                    || !Utils.checkConnectPermissionForDataDelivery(service, source, TAG)) {
                 return false;
             }
 
@@ -1764,21 +2051,27 @@
         }
 
         @Override
-        public int getPhonebookAccessPermission(BluetoothDevice device) {
+        public int getPhonebookAccessPermission(
+                BluetoothDevice device, AttributionSource attributionSource) {
+            Attributable.setAttributionSource(device, attributionSource);
             AdapterService service = getService();
-            if (service == null || !callerIsSystemOrActiveUser(TAG, "getPhonebookAccessPermission")) {
+            if (service == null || !callerIsSystemOrActiveUser(TAG, "getPhonebookAccessPermission")
+                    || !Utils.checkConnectPermissionForDataDelivery(
+                    service, attributionSource, "AdapterService getPhonebookAccessPermission")) {
                 return BluetoothDevice.ACCESS_UNKNOWN;
             }
 
-            enforceBluetoothPermission(service);
-
             return service.getDeviceAccessFromPrefs(device, PHONEBOOK_ACCESS_PERMISSION_PREFERENCE_FILE);
         }
 
         @Override
-        public boolean setPhonebookAccessPermission(BluetoothDevice device, int value) {
+        public boolean setPhonebookAccessPermission(BluetoothDevice device, int value,
+                AttributionSource source) {
+            Attributable.setAttributionSource(device, source);
             AdapterService service = getService();
-            if (service == null || !callerIsSystemOrActiveUser(TAG, "setPhonebookAccessPermission")) {
+            if (service == null
+                    || !Utils.checkCallerIsSystemOrActiveUser(TAG)
+                    || !Utils.checkConnectPermissionForDataDelivery(service, source, TAG)) {
                 return false;
             }
 
@@ -1789,21 +2082,27 @@
         }
 
         @Override
-        public int getMessageAccessPermission(BluetoothDevice device) {
+        public int getMessageAccessPermission(
+                BluetoothDevice device, AttributionSource attributionSource) {
+            Attributable.setAttributionSource(device, attributionSource);
             AdapterService service = getService();
-            if (service == null || !callerIsSystemOrActiveUser(TAG, "getMessageAccessPermission")) {
+            if (service == null || !callerIsSystemOrActiveUser(TAG, "getMessageAccessPermission")
+                    || !Utils.checkConnectPermissionForDataDelivery(
+                    service, attributionSource, "AdapterService getMessageAccessPermission")) {
                 return BluetoothDevice.ACCESS_UNKNOWN;
             }
 
-            enforceBluetoothPermission(service);
-
             return service.getDeviceAccessFromPrefs(device, MESSAGE_ACCESS_PERMISSION_PREFERENCE_FILE);
         }
 
         @Override
-        public boolean setMessageAccessPermission(BluetoothDevice device, int value) {
+        public boolean setMessageAccessPermission(BluetoothDevice device, int value,
+                AttributionSource source) {
+            Attributable.setAttributionSource(device, source);
             AdapterService service = getService();
-            if (service == null || !callerIsSystemOrActiveUser(TAG, "setMessageAccessPermission")) {
+            if (service == null
+                    || !Utils.checkCallerIsSystemOrActiveUser(TAG)
+                    || !Utils.checkConnectPermissionForDataDelivery(service, source, TAG)) {
                 return false;
             }
 
@@ -1814,21 +2113,27 @@
         }
 
         @Override
-        public int getSimAccessPermission(BluetoothDevice device) {
+        public int getSimAccessPermission(
+                BluetoothDevice device, AttributionSource attributionSource) {
+            Attributable.setAttributionSource(device, attributionSource);
             AdapterService service = getService();
-            if (service == null || !callerIsSystemOrActiveUser(TAG, "getSimAccessPermission")) {
+            if (service == null || !callerIsSystemOrActiveUser(TAG, "getSimAccessPermission")
+                    || !Utils.checkConnectPermissionForDataDelivery(
+                            service, attributionSource, "AdapterService getSimAccessPermission")) {
                 return BluetoothDevice.ACCESS_UNKNOWN;
             }
 
-            enforceBluetoothPermission(service);
-
             return service.getDeviceAccessFromPrefs(device, SIM_ACCESS_PERMISSION_PREFERENCE_FILE);
         }
 
         @Override
-        public boolean setSimAccessPermission(BluetoothDevice device, int value) {
+        public boolean setSimAccessPermission(BluetoothDevice device, int value,
+                AttributionSource source) {
+            Attributable.setAttributionSource(device, source);
             AdapterService service = getService();
-            if (service == null || !callerIsSystemOrActiveUser(TAG, "setSimAccessPermission")) {
+            if (service == null
+                    || !Utils.checkCallerIsSystemOrActiveUser(TAG)
+                    || !Utils.checkConnectPermissionForDataDelivery(service, source, TAG)) {
                 return false;
             }
 
@@ -1849,14 +2154,16 @@
         }
 
         @Override
-        public boolean sdpSearch(BluetoothDevice device, ParcelUuid uuid) {
+        public boolean sdpSearch(
+                BluetoothDevice device, ParcelUuid uuid, AttributionSource attributionSource) {
+            Attributable.setAttributionSource(device, attributionSource);
             AdapterService service = getService();
-            if (service == null || !callerIsSystemOrActiveUser(TAG, "sdpSearch")) {
+            if (service == null || !callerIsSystemOrActiveUser(TAG, "sdpSearch")
+                    || !Utils.checkConnectPermissionForDataDelivery(
+                            service, attributionSource, "AdapterService sdpSearch")) {
                 return false;
             }
 
-            enforceBluetoothPermission(service);
-
             if (service.mSdpManager == null) {
                 return false;
             }
@@ -1865,14 +2172,15 @@
         }
 
         @Override
-        public int getBatteryLevel(BluetoothDevice device) {
+        public int getBatteryLevel(BluetoothDevice device, AttributionSource attributionSource) {
+            Attributable.setAttributionSource(device, attributionSource);
             AdapterService service = getService();
-            if (service == null || !callerIsSystemOrActiveUser(TAG, "getBatteryLevel")) {
+            if (service == null || !callerIsSystemOrActiveUser(TAG, "getBatteryLevel")
+                    || !Utils.checkConnectPermissionForDataDelivery(
+                            service, attributionSource, "AdapterService getBatteryLevel")) {
                 return BluetoothDevice.BATTERY_LEVEL_UNKNOWN;
             }
 
-            enforceBluetoothPermission(service);
-
             DeviceProperties deviceProp = service.mRemoteDevices.getDeviceProperties(device);
             if (deviceProp == null) {
                 return BluetoothDevice.BATTERY_LEVEL_UNKNOWN;
@@ -1881,35 +2189,35 @@
         }
 
         @Override
-        public int getMaxConnectedAudioDevices() {
+        public int getMaxConnectedAudioDevices(AttributionSource attributionSource) {
             // don't check caller, may be called from system UI
             AdapterService service = getService();
-            if (service == null) {
+            if (service == null || !Utils.checkConnectPermissionForDataDelivery(
+                    service, attributionSource, "AdapterService getMaxConnectedAudioDevices")) {
                 return AdapterProperties.MAX_CONNECTED_AUDIO_DEVICES_LOWER_BOND;
             }
 
-            enforceBluetoothPermission(service);
-
             return service.getMaxConnectedAudioDevices();
         }
 
         //@Override
-        public boolean isA2dpOffloadEnabled() {
+        @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
+        public boolean isA2dpOffloadEnabled(AttributionSource attributionSource) {
             // don't check caller, may be called from system UI
             AdapterService service = getService();
-            if (service == null) {
+            if (service == null || !Utils.checkConnectPermissionForDataDelivery(
+                    service, attributionSource, "AdapterService isA2dpOffloadEnabled")) {
                 return false;
             }
 
-            enforceBluetoothPermission(service);
-
             return service.isA2dpOffloadEnabled();
         }
 
         @Override
-        public boolean factoryReset() {
+        public boolean factoryReset(AttributionSource source) {
             AdapterService service = getService();
-            if (service == null) {
+            if (service == null
+                    || !Utils.checkConnectPermissionForDataDelivery(service, source, TAG)) {
                 return false;
             }
 
@@ -1927,9 +2235,38 @@
         }
 
         @Override
-        public void registerCallback(IBluetoothCallback callback) {
+        public boolean registerBluetoothConnectionCallback(IBluetoothConnectionCallback callback,
+                AttributionSource source) {
             AdapterService service = getService();
-            if (service == null || !callerIsSystemOrActiveUser(TAG, "registerCallback")) {
+            if (service == null
+                    || !Utils.checkCallerIsSystemOrActiveUser(TAG)
+                    || !Utils.checkConnectPermissionForDataDelivery(service, source, TAG)) {
+                return false;
+            }
+            enforceBluetoothPrivilegedPermission(service);
+            service.mBluetoothConnectionCallbacks.add(callback);
+            return true;
+        }
+
+        @Override
+        public boolean unregisterBluetoothConnectionCallback(IBluetoothConnectionCallback callback,
+                AttributionSource source) {
+            AdapterService service = getService();
+            if (service == null
+                    || !Utils.checkCallerIsSystemOrActiveUser(TAG)
+                    || !Utils.checkConnectPermissionForDataDelivery(service, source, TAG)) {
+                return false;
+            }
+            enforceBluetoothPrivilegedPermission(service);
+            return service.mBluetoothConnectionCallbacks.remove(callback);
+        }
+
+        @Override
+        public void registerCallback(IBluetoothCallback callback, AttributionSource source) {
+            AdapterService service = getService();
+            if (service == null
+                    || !Utils.checkCallerIsSystemOrActiveUser(TAG)
+                    || !Utils.checkConnectPermissionForDataDelivery(service, source, TAG)) {
                 return;
             }
 
@@ -1939,10 +2276,11 @@
         }
 
         @Override
-        public void unregisterCallback(IBluetoothCallback callback) {
+        public void unregisterCallback(IBluetoothCallback callback, AttributionSource source) {
             AdapterService service = getService();
             if (service == null || service.mCallbacks == null
-                    || !callerIsSystemOrActiveUser(TAG, "unregisterCallback")) {
+                    || !Utils.checkCallerIsSystemOrActiveUser(TAG)
+                    || !Utils.checkConnectPermissionForDataDelivery(service, source, TAG)) {
                 return;
             }
 
@@ -1958,8 +2296,6 @@
                 return false;
             }
 
-            enforceBluetoothPermission(service);
-
             int val = service.mAdapterProperties.getNumOfAdvertisementInstancesSupported();
             return val >= MIN_ADVT_INSTANCES_FOR_MA;
         }
@@ -1975,8 +2311,6 @@
                 return false;
             }
 
-            enforceBluetoothPermission(service);
-
             int val = service.getNumOfOffloadedScanFilterSupported();
             return val >= MIN_OFFLOADED_FILTERS;
         }
@@ -1988,8 +2322,6 @@
                 return false;
             }
 
-            enforceBluetoothPermission(service);
-
             int val = service.getOffloadedScanResultStorage();
             return val >= MIN_OFFLOADED_SCAN_STORAGE_BYTES;
         }
@@ -2001,8 +2333,6 @@
                 return false;
             }
 
-            enforceBluetoothPermission(service);
-
             return service.isLe2MPhySupported();
         }
 
@@ -2013,8 +2343,6 @@
                 return false;
             }
 
-            enforceBluetoothPermission(service);
-
             return service.isLeCodedPhySupported();
         }
 
@@ -2025,8 +2353,6 @@
                 return false;
             }
 
-            enforceBluetoothPermission(service);
-
             return service.isLeExtendedAdvertisingSupported();
         }
 
@@ -2037,8 +2363,6 @@
                 return false;
             }
 
-            enforceBluetoothPermission(service);
-
             return service.isLePeriodicAdvertisingSupported();
         }
 
@@ -2049,8 +2373,6 @@
                 return 0;
             }
 
-            enforceBluetoothPermission(service);
-
             return service.getLeMaximumAdvertisingDataLength();
         }
 
@@ -2061,15 +2383,14 @@
                 return false;
             }
 
-            enforceBluetoothPrivilegedPermission(service);
-
             return service.mAdapterProperties.isActivityAndEnergyReportingSupported();
         }
 
         @Override
-        public BluetoothActivityEnergyInfo reportActivityInfo() {
+        public BluetoothActivityEnergyInfo reportActivityInfo(AttributionSource source) {
             AdapterService service = getService();
-            if (service == null) {
+            if (service == null
+                    || !Utils.checkConnectPermissionForDataDelivery(service, source, TAG)) {
                 return null;
             }
 
@@ -2080,9 +2401,12 @@
 
         @Override
         public boolean registerMetadataListener(IBluetoothMetadataListener listener,
-                BluetoothDevice device) {
+                BluetoothDevice device, AttributionSource source) {
+            Attributable.setAttributionSource(device, source);
             AdapterService service = getService();
-            if (service == null || !callerIsSystemOrActiveUser(TAG, "registerMetadataListener")) {
+            if (service == null
+                    || !Utils.checkCallerIsSystemOrActiveUser(TAG)
+                    || !Utils.checkConnectPermissionForDataDelivery(service, source, TAG)) {
                 return false;
             }
 
@@ -2104,10 +2428,13 @@
         }
 
         @Override
-        public boolean unregisterMetadataListener(BluetoothDevice device) {
+        public boolean unregisterMetadataListener(BluetoothDevice device,
+                AttributionSource source) {
+            Attributable.setAttributionSource(device, source);
             AdapterService service = getService();
             if (service == null
-                    || !callerIsSystemOrActiveUser(TAG, "unregisterMetadataListener")) {
+                    || !Utils.checkCallerIsSystemOrActiveUser(TAG)
+                    || !Utils.checkConnectPermissionForDataDelivery(service, source, TAG)) {
                 return false;
             }
 
@@ -2123,9 +2450,13 @@
         }
 
         @Override
-        public boolean setMetadata(BluetoothDevice device, int key, byte[] value) {
+        public boolean setMetadata(BluetoothDevice device, int key, byte[] value,
+                AttributionSource source) {
+            Attributable.setAttributionSource(device, source);
             AdapterService service = getService();
-            if (service == null || !callerIsSystemOrActiveUser(TAG, "setMetadata")) {
+            if (service == null
+                    || !Utils.checkCallerIsSystemOrActiveUser(TAG)
+                    || !Utils.checkConnectPermissionForDataDelivery(service, source, TAG)) {
                 return false;
             }
 
@@ -2138,9 +2469,13 @@
         }
 
         @Override
-        public byte[] getMetadata(BluetoothDevice device, int key) {
+        public byte[] getMetadata(BluetoothDevice device, int key,
+                AttributionSource source) {
+            Attributable.setAttributionSource(device, source);
             AdapterService service = getService();
-            if (service == null || !callerIsSystemOrActiveUser(TAG, "getMetadata")) {
+            if (service == null
+                    || !Utils.checkCallerIsSystemOrActiveUser(TAG)
+                    || !Utils.checkConnectPermissionForDataDelivery(service, source, TAG)) {
                 return null;
             }
 
@@ -2150,16 +2485,19 @@
         }
 
         @Override
-        public void requestActivityInfo(ResultReceiver result) {
+        public void requestActivityInfo(ResultReceiver result, AttributionSource source) {
             Bundle bundle = new Bundle();
-            bundle.putParcelable(BatteryStats.RESULT_RECEIVER_CONTROLLER_KEY, reportActivityInfo());
+            bundle.putParcelable(BatteryStats.RESULT_RECEIVER_CONTROLLER_KEY,
+                    reportActivityInfo(source));
             result.send(0, bundle);
         }
 
         @Override
-        public void onLeServiceUp() {
+        public void onLeServiceUp(AttributionSource source) {
             AdapterService service = getService();
-            if (service == null || !callerIsSystemOrActiveUser(TAG, "onLeServiceUp")) {
+            if (service == null
+                    || !Utils.checkCallerIsSystemOrActiveUser(TAG)
+                    || !Utils.checkConnectPermissionForDataDelivery(service, source, TAG)) {
                 return;
             }
 
@@ -2169,9 +2507,11 @@
         }
 
         @Override
-        public void onBrEdrDown() {
+        public void onBrEdrDown(AttributionSource source) {
             AdapterService service = getService();
-            if (service == null || !callerIsSystemOrActiveUser(TAG, "onBrEdrDown")) {
+            if (service == null
+                    || !Utils.checkCallerIsSystemOrActiveUser(TAG)
+                    || !Utils.checkConnectPermissionForDataDelivery(service, source, TAG)) {
                 return;
             }
 
@@ -2197,6 +2537,10 @@
 
     // ----API Methods--------
 
+    public boolean isEnabled() {
+        return getState() == BluetoothAdapter.STATE_ON;
+    }
+
     public int getState() {
         if (mAdapterProperties != null) {
             return mAdapterProperties.getState();
@@ -2227,6 +2571,10 @@
         return mAdapterProperties.getName();
     }
 
+    public int getNameLengthForAdvertise() {
+        return mAdapterProperties.getName().length();
+    }
+
     private static boolean isValidIoCapability(int capability) {
         if (capability < 0 || capability >= BluetoothAdapter.IO_CAPABILITY_MAX) {
             Log.e(TAG, "Invalid IO capability value - " + capability);
@@ -2246,32 +2594,36 @@
         }
     }
 
-    boolean startDiscovery(String callingPackage, @Nullable String callingFeatureId) {
+    boolean startDiscovery(AttributionSource attributionSource) {
         UserHandle callingUser = UserHandle.of(UserHandle.getCallingUserId());
         debugLog("startDiscovery");
+        String callingPackage = attributionSource.getPackageName();
         mAppOps.checkPackage(Binder.getCallingUid(), callingPackage);
         boolean isQApp = Utils.isQApp(this, callingPackage);
+        boolean hasDisavowedLocation =
+                Utils.hasDisavowedLocationForScan(this, attributionSource, mTestModeEnabled);
         String permission = null;
         if (Utils.checkCallerHasNetworkSettingsPermission(this)) {
             permission = android.Manifest.permission.NETWORK_SETTINGS;
         } else if (Utils.checkCallerHasNetworkSetupWizardPermission(this)) {
             permission = android.Manifest.permission.NETWORK_SETUP_WIZARD;
-        } else if (isQApp) {
-            if (!Utils.checkCallerHasFineLocation(this, mAppOps, callingPackage, callingFeatureId,
-                    callingUser)) {
-                return false;
+        } else if (!hasDisavowedLocation) {
+            if (isQApp) {
+                if (!Utils.checkCallerHasFineLocation(this, attributionSource, callingUser)) {
+                    return false;
+                }
+                permission = android.Manifest.permission.ACCESS_FINE_LOCATION;
+            } else {
+                if (!Utils.checkCallerHasCoarseLocation(this, attributionSource, callingUser)) {
+                    return false;
+                }
+                permission = android.Manifest.permission.ACCESS_COARSE_LOCATION;
             }
-            permission = android.Manifest.permission.ACCESS_FINE_LOCATION;
-        } else {
-            if (!Utils.checkCallerHasCoarseLocation(this, mAppOps, callingPackage, callingFeatureId,
-                    callingUser)) {
-                return false;
-            }
-            permission = android.Manifest.permission.ACCESS_COARSE_LOCATION;
         }
 
         synchronized (mDiscoveringPackages) {
-            mDiscoveringPackages.add(new DiscoveringPackage(callingPackage, permission));
+            mDiscoveringPackages.add(
+                    new DiscoveringPackage(callingPackage, permission, hasDisavowedLocation));
         }
         return startDiscoveryNative();
     }
@@ -2295,12 +2647,27 @@
         return mDatabaseManager;
     }
 
-    boolean createBond(BluetoothDevice device, int transport, OobData oobData) {
+    private class CallerInfo {
+        public String callerPackageName;
+        public UserHandle user;
+    }
+
+    boolean createBond(BluetoothDevice device, int transport, OobData remoteP192Data,
+            OobData remoteP256Data, String callingPackage) {
         DeviceProperties deviceProp = mRemoteDevices.getDeviceProperties(device);
         if (deviceProp != null && deviceProp.getBondState() != BluetoothDevice.BOND_NONE) {
             return false;
         }
 
+        if (!isPackageNameAccurate(this, callingPackage, Binder.getCallingUid())) {
+            return false;
+        }
+
+        CallerInfo createBondCaller = new CallerInfo();
+        createBondCaller.callerPackageName = callingPackage;
+        createBondCaller.user = UserHandle.of(UserHandle.getCallingUserId());
+        mBondAttemptCallerInfo.put(device.getAddress(), createBondCaller);
+
         mRemoteDevices.setBondingInitiatedLocally(Utils.getByteAddress(device));
 
         // Pairing is unreliable while scanning, so cancel discovery
@@ -2311,15 +2678,73 @@
         msg.obj = device;
         msg.arg1 = transport;
 
-        if (oobData != null) {
-            Bundle oobDataBundle = new Bundle();
-            oobDataBundle.putParcelable(BondStateMachine.OOBDATA, oobData);
-            msg.setData(oobDataBundle);
+        Bundle remoteOobDatasBundle = new Bundle();
+        boolean setData = false;
+        if (remoteP192Data != null) {
+            remoteOobDatasBundle.putParcelable(BondStateMachine.OOBDATAP192, remoteP192Data);
+            setData = true;
+        }
+        if (remoteP256Data != null) {
+            remoteOobDatasBundle.putParcelable(BondStateMachine.OOBDATAP256, remoteP256Data);
+            setData = true;
+        }
+        if (setData) {
+            msg.setData(remoteOobDatasBundle);
         }
         mBondStateMachine.sendMessage(msg);
         return true;
     }
 
+    private final ArrayDeque<IBluetoothOobDataCallback> mOobDataCallbackQueue =
+            new ArrayDeque<>();
+
+    /**
+     * Fetches the local OOB data to give out to remote.
+     *
+     * @param transport - specify data transport.
+     * @param callback - callback used to receive the requested {@link Oobdata}; null will be
+     * ignored silently.
+     *
+     * @hide
+     */
+    public synchronized void generateLocalOobData(int transport,
+            IBluetoothOobDataCallback callback) {
+        if (callback == null) {
+            Log.e(TAG, "'callback' argument must not be null!");
+            return;
+        }
+        if (mOobDataCallbackQueue.peek() != null) {
+            try {
+                callback.onError(BluetoothStatusCodes.ERROR_ANOTHER_ACTIVE_OOB_REQUEST);
+                return;
+            } catch (RemoteException e) {
+                Log.e(TAG, "Failed to make callback", e);
+            }
+        }
+        mOobDataCallbackQueue.offer(callback);
+        generateLocalOobDataNative(transport);
+    }
+
+    /* package */ synchronized void notifyOobDataCallback(int transport, OobData oobData) {
+        if (mOobDataCallbackQueue.peek() == null) {
+            Log.e(TAG, "Failed to make callback, no callback exists");
+            return;
+        }
+        if (oobData == null) {
+            try {
+                mOobDataCallbackQueue.poll().onError(BluetoothStatusCodes.ERROR_UNKNOWN);
+            } catch (RemoteException e) {
+                Log.e(TAG, "Failed to make callback", e);
+            }
+        } else {
+            try {
+                mOobDataCallbackQueue.poll().onOobData(transport, oobData);
+            } catch (RemoteException e) {
+                Log.e(TAG, "Failed to make callback", e);
+            }
+        }
+    }
+
     public boolean isQuietModeEnabled() {
         debugLog("isQuetModeEnabled() - Enabled = " + mQuietmode);
         return mQuietmode;
@@ -2372,6 +2797,24 @@
     }
 
     /**
+     * Checks whether the device was recently associated with the comapnion app that called
+     * {@link BluetoothDevice#createBond}. This allows these devices to skip the pairing dialog if
+     * their pairing variant is {@link BluetoothDevice#PAIRING_VARIANT_CONSENT}.
+     *
+     * @param device the bluetooth device that is being bonded
+     * @return true if it was recently associated and we can bypass the dialog, false otherwise
+     */
+    public boolean canBondWithoutDialog(BluetoothDevice device) {
+        if (mBondAttemptCallerInfo.containsKey(device.getAddress())) {
+            CallerInfo bondCallerInfo = mBondAttemptCallerInfo.get(device.getAddress());
+
+            return mCompanionDeviceManager.canPairWithoutPrompt(bondCallerInfo.callerPackageName,
+                    device.getAddress(), bondCallerInfo.user);
+        }
+        return false;
+    }
+
+    /**
      * Sets device as the active devices for the profiles passed into the function
      *
      * @param device is the remote bluetooth device
@@ -2382,6 +2825,10 @@
      *                 {@link BluetoothAdapter#ACTIVE_DEVICE_ALL}
      * @return false if profiles value is not one of the constants we accept, true otherwise
      */
+    @RequiresPermission(allOf = {
+            android.Manifest.permission.BLUETOOTH_PRIVILEGED,
+            android.Manifest.permission.MODIFY_PHONE_STATE,
+    })
     public boolean setActiveDevice(BluetoothDevice device, @ActiveDeviceUse int profiles) {
         enforceCallingOrSelfPermission(
                 BLUETOOTH_PRIVILEGED, "Need BLUETOOTH_PRIVILEGED permission");
@@ -2435,6 +2882,10 @@
      * @param device is the remote device with which to connect these profiles
      * @return true if all profiles successfully connected, false if an error occurred
      */
+    @RequiresPermission(allOf = {
+            android.Manifest.permission.BLUETOOTH_PRIVILEGED,
+            android.Manifest.permission.MODIFY_PHONE_STATE,
+    })
     public boolean connectAllEnabledProfiles(BluetoothDevice device) {
         if (!profileServicesRunning()) {
             Log.e(TAG, "connectAllEnabledProfiles: Not all profile services running");
@@ -2524,6 +2975,7 @@
      * @param device is the remote device with which to disconnect these profiles
      * @return true if all profiles successfully disconnected, false if an error occurred
      */
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED)
     public boolean disconnectAllEnabledProfiles(BluetoothDevice device) {
         if (!profileServicesRunning()) {
             Log.e(TAG, "disconnectAllEnabledProfiles: Not all profile services bound");
@@ -2591,6 +3043,11 @@
             Log.i(TAG, "disconnectAllEnabledProfiles: Disconnecting Hearing Aid Profile");
             mHearingAidService.disconnect(device);
         }
+        if (mSapService != null && mSapService.getConnectionState(device)
+                == BluetoothProfile.STATE_CONNECTED) {
+            Log.i(TAG, "disconnectAllEnabledProfiles: Disconnecting Sap Profile");
+            mSapService.disconnect(device);
+        }
 
         return true;
     }
@@ -2627,12 +3084,91 @@
         return deviceProp.getUuids();
     }
 
+    public Set<IBluetoothConnectionCallback> getBluetoothConnectionCallbacks() {
+        return mBluetoothConnectionCallbacks;
+    }
+
+    /**
+     * Converts HCI disconnect reasons to Android disconnect reasons.
+     * <p>
+     * The HCI Error Codes used for ACL disconnect reasons propagated up from native code were
+     * copied from: {@link system/bt/stack/include/hci_error_code.h}.
+     * <p>
+     * These error codes are specified and described in Bluetooth Core Spec v5.1, Vol 2, Part D.
+     *
+     * @param hciReason is the raw HCI disconnect reason from native.
+     * @return the Android disconnect reason for apps.
+     */
+    static @BluetoothAdapter.BluetoothConnectionCallback.DisconnectReason int
+            hciToAndroidDisconnectReason(int hciReason) {
+        switch(hciReason) {
+            case /*HCI_SUCCESS*/ 0x00:
+            case /*HCI_ERR_UNSPECIFIED*/ 0x1F:
+            case /*HCI_ERR_UNDEFINED*/ 0xff:
+                return BluetoothStatusCodes.ERROR_UNKNOWN;
+            case /*HCI_ERR_ILLEGAL_COMMAND*/ 0x01:
+            case /*HCI_ERR_NO_CONNECTION*/ 0x02:
+            case /*HCI_ERR_HW_FAILURE*/ 0x03:
+            case /*HCI_ERR_DIFF_TRANSACTION_COLLISION*/ 0x2A:
+            case /*HCI_ERR_ROLE_SWITCH_PENDING*/ 0x32:
+            case /*HCI_ERR_ROLE_SWITCH_FAILED*/ 0x35:
+                return BluetoothStatusCodes.ERROR_DISCONNECT_REASON_LOCAL;
+            case /*HCI_ERR_PAGE_TIMEOUT*/ 0x04:
+            case /*HCI_ERR_CONNECTION_TOUT*/ 0x08:
+            case /*HCI_ERR_HOST_TIMEOUT*/ 0x10:
+            case /*HCI_ERR_LMP_RESPONSE_TIMEOUT*/ 0x22:
+            case /*HCI_ERR_ADVERTISING_TIMEOUT*/ 0x3C:
+            case /*HCI_ERR_CONN_FAILED_ESTABLISHMENT*/ 0x3E:
+                return BluetoothStatusCodes.ERROR_DISCONNECT_REASON_TIMEOUT;
+            case /*HCI_ERR_AUTH_FAILURE*/ 0x05:
+            case /*HCI_ERR_KEY_MISSING*/ 0x06:
+            case /*HCI_ERR_HOST_REJECT_SECURITY*/ 0x0E:
+            case /*HCI_ERR_REPEATED_ATTEMPTS*/ 0x17:
+            case /*HCI_ERR_PAIRING_NOT_ALLOWED*/ 0x18:
+            case /*HCI_ERR_ENCRY_MODE_NOT_ACCEPTABLE*/ 0x25:
+            case /*HCI_ERR_UNIT_KEY_USED*/ 0x26:
+            case /*HCI_ERR_PAIRING_WITH_UNIT_KEY_NOT_SUPPORTED*/ 0x29:
+            case /*HCI_ERR_INSUFFCIENT_SECURITY*/ 0x2F:
+            case /*HCI_ERR_HOST_BUSY_PAIRING*/ 0x38:
+                return BluetoothStatusCodes.ERROR_DISCONNECT_REASON_SECURITY;
+            case /*HCI_ERR_MEMORY_FULL*/ 0x07:
+            case /*HCI_ERR_MAX_NUM_OF_CONNECTIONS*/ 0x09:
+            case /*HCI_ERR_MAX_NUM_OF_SCOS*/ 0x0A:
+            case /*HCI_ERR_COMMAND_DISALLOWED*/ 0x0C:
+            case /*HCI_ERR_HOST_REJECT_RESOURCES*/ 0x0D:
+            case /*HCI_ERR_LIMIT_REACHED*/ 0x43:
+                return BluetoothStatusCodes.ERROR_DISCONNECT_REASON_RESOURCE_LIMIT_REACHED;
+            case /*HCI_ERR_CONNECTION_EXISTS*/ 0x0B:
+                return BluetoothStatusCodes.ERROR_DISCONNECT_REASON_CONNECTION_ALREADY_EXISTS;
+            case /*HCI_ERR_HOST_REJECT_DEVICE*/ 0x0F:
+                return BluetoothStatusCodes.ERROR_DISCONNECT_REASON_SYSTEM_POLICY;
+            case /*HCI_ERR_ILLEGAL_PARAMETER_FMT*/ 0x12:
+                return BluetoothStatusCodes.ERROR_DISCONNECT_REASON_BAD_PARAMETERS;
+            case /*HCI_ERR_PEER_USER*/ 0x13:
+                return BluetoothStatusCodes.ERROR_DISCONNECT_REASON_REMOTE_REQUEST;
+            case /*HCI_ERR_CONN_CAUSE_LOCAL_HOST*/ 0x16:
+                return BluetoothStatusCodes.ERROR_DISCONNECT_REASON_LOCAL_REQUEST;
+            case /*HCI_ERR_UNSUPPORTED_REM_FEATURE*/ 0x1A:
+                return BluetoothStatusCodes.ERROR_DISCONNECT_REASON_REMOTE;
+            case /*HCI_ERR_UNACCEPT_CONN_INTERVAL*/ 0x3B:
+                return BluetoothStatusCodes.ERROR_DISCONNECT_REASON_BAD_PARAMETERS;
+            default:
+                Log.e(TAG, "Invalid HCI disconnect reason: " + hciReason);
+                return BluetoothStatusCodes.ERROR_UNKNOWN;
+        }
+    }
+
     void logUserBondResponse(BluetoothDevice device, boolean accepted, int event) {
-        BluetoothStatsLog.write(BluetoothStatsLog.BLUETOOTH_BOND_STATE_CHANGED,
-                obfuscateAddress(device), 0, device.getType(),
-                BluetoothDevice.BOND_BONDING,
-                event,
-                accepted ? 0 : BluetoothDevice.UNBOND_REASON_AUTH_REJECTED);
+        final long token = Binder.clearCallingIdentity();
+        try {
+            BluetoothStatsLog.write(BluetoothStatsLog.BLUETOOTH_BOND_STATE_CHANGED,
+                    obfuscateAddress(device), 0, device.getType(),
+                    BluetoothDevice.BOND_BONDING,
+                    event,
+                    accepted ? 0 : BluetoothDevice.UNBOND_REASON_AUTH_REJECTED);
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
     }
 
     int getDeviceAccessFromPrefs(BluetoothDevice device, String prefFile) {
@@ -2656,25 +3192,23 @@
         editor.apply();
     }
 
-    void setPhonebookAccessPermission(BluetoothDevice device, int value) {
+    public void setPhonebookAccessPermission(BluetoothDevice device, int value) {
         setDeviceAccessFromPrefs(device, value, PHONEBOOK_ACCESS_PERMISSION_PREFERENCE_FILE);
     }
 
-    void setMessageAccessPermission(BluetoothDevice device, int value) {
+    public void setMessageAccessPermission(BluetoothDevice device, int value) {
         setDeviceAccessFromPrefs(device, value, MESSAGE_ACCESS_PERMISSION_PREFERENCE_FILE);
     }
 
-    void setSimAccessPermission(BluetoothDevice device, int value) {
+    public void setSimAccessPermission(BluetoothDevice device, int value) {
         setDeviceAccessFromPrefs(device, value, SIM_ACCESS_PERMISSION_PREFERENCE_FILE);
     }
 
     public boolean isRpaOffloadSupported() {
-        enforceBluetoothPermission(this);
         return mAdapterProperties.isRpaOffloadSupported();
     }
 
     public int getNumOfOffloadedIrkSupported() {
-        enforceBluetoothPermission(this);
         return mAdapterProperties.getNumOfOffloadedIrkSupported();
     }
 
@@ -2772,7 +3306,6 @@
     }
 
     public int getTotalNumOfTrackableAdvertisements() {
-        enforceBluetoothPermission(this);
         return mAdapterProperties.getTotalNumOfTrackableAdvertisements();
     }
 
@@ -2816,8 +3349,11 @@
                     : AlarmManager.ELAPSED_REALTIME;
 
             Intent intent = new Intent(ACTION_ALARM_WAKEUP);
+            // TODO(b/171825892) Please replace FLAG_MUTABLE_UNAUDITED below
+            // with either FLAG_IMMUTABLE (recommended) or FLAG_MUTABLE.
             mPendingAlarm =
-                    PendingIntent.getBroadcast(this, 0, intent, PendingIntent.FLAG_ONE_SHOT);
+                    PendingIntent.getBroadcast(this, 0, intent, PendingIntent.FLAG_ONE_SHOT
+                            | PendingIntent.FLAG_IMMUTABLE);
             mAlarmManager.setExact(type, wakeupTime, mPendingAlarm);
             return true;
         }
@@ -2965,6 +3501,15 @@
             return;
         }
 
+        if ("set-test-mode".equals(args[0])) {
+            final boolean testModeEnabled = "enabled".equalsIgnoreCase(args[1]);
+            for (ProfileService profile : mRunningProfiles) {
+                profile.setTestModeEnabled(testModeEnabled);
+            }
+            mTestModeEnabled = testModeEnabled;
+            return;
+        }
+
         verboseLog("dumpsys arguments, check for protobuf output: " + TextUtils.join(" ", args));
         if (args[0].equals("--proto-bin")) {
             dumpMetrics(fd);
@@ -3049,11 +3594,223 @@
         return UserManager.get(this).isGuestUser();
     }
 
-    private boolean isNiapMode() {
+    private boolean isCommonCriteriaMode() {
         return ((DevicePolicyManager) getSystemService(Context.DEVICE_POLICY_SERVICE))
                 .isCommonCriteriaModeEnabled(null);
     }
 
+    @SuppressLint("AndroidFrameworkRequiresPermission")
+    private void enforceBluetoothPrivilegedPermissionIfNeeded(OobData remoteP192Data,
+            OobData remoteP256Data) {
+        if (remoteP192Data != null || remoteP256Data != null) {
+            enforceBluetoothPrivilegedPermission(this);
+        }
+    }
+
+    // Boolean flags
+    private static final String GD_CORE_FLAG = "INIT_gd_core";
+    private static final String GD_ADVERTISING_FLAG = "INIT_gd_advertising";
+    private static final String GD_SCANNING_FLAG = "INIT_gd_scanning";
+    private static final String GD_HCI_FLAG = "INIT_gd_hci";
+    private static final String GD_CONTROLLER_FLAG = "INIT_gd_controller";
+    private static final String GD_ACL_FLAG = "INIT_gd_acl";
+    private static final String GD_L2CAP_FLAG = "INIT_gd_l2cap";
+    private static final String GD_RUST_FLAG = "INIT_gd_rust";
+    private static final String GD_LINK_POLICY_FLAG = "INIT_gd_link_policy";
+
+    /**
+     * Logging flags logic (only applies to DEBUG and VERBOSE levels):
+     * if LOG_TAG in LOGGING_DEBUG_DISABLED_FOR_TAGS_FLAG:
+     *   DO NOT LOG
+     * else if LOG_TAG in LOGGING_DEBUG_ENABLED_FOR_TAGS_FLAG:
+     *   DO LOG
+     * else if LOGGING_DEBUG_ENABLED_FOR_ALL_FLAG:
+     *   DO LOG
+     * else:
+     *   DO NOT LOG
+     */
+    private static final String LOGGING_DEBUG_ENABLED_FOR_ALL_FLAG =
+            "INIT_logging_debug_enabled_for_all";
+    // String flags
+    // Comma separated tags
+    private static final String LOGGING_DEBUG_ENABLED_FOR_TAGS_FLAG =
+            "INIT_logging_debug_enabled_for_tags";
+    private static final String LOGGING_DEBUG_DISABLED_FOR_TAGS_FLAG =
+            "INIT_logging_debug_disabled_for_tags";
+    private static final String BTAA_HCI_LOG_FLAG = "INIT_btaa_hci";
+
+    private String[] getInitFlags() {
+        ArrayList<String> initFlags = new ArrayList<>();
+        if (DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_BLUETOOTH, GD_CORE_FLAG, false)) {
+            initFlags.add(String.format("%s=%s", GD_CORE_FLAG, "true"));
+        }
+        if (DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_BLUETOOTH, GD_ADVERTISING_FLAG, false)) {
+            initFlags.add(String.format("%s=%s", GD_ADVERTISING_FLAG, "true"));
+        }
+        if (DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_BLUETOOTH, GD_SCANNING_FLAG,
+                Config.isGdEnabledUpToScanningLayer())) {
+            initFlags.add(String.format("%s=%s", GD_SCANNING_FLAG, "true"));
+        }
+        if (DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_BLUETOOTH, GD_HCI_FLAG, false)) {
+            initFlags.add(String.format("%s=%s", GD_HCI_FLAG, "true"));
+        }
+        if (DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_BLUETOOTH, GD_CONTROLLER_FLAG, false)) {
+            initFlags.add(String.format("%s=%s", GD_CONTROLLER_FLAG, "true"));
+        }
+        if (DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_BLUETOOTH, GD_ACL_FLAG, false)) {
+            initFlags.add(String.format("%s=%s", GD_ACL_FLAG, "true"));
+        }
+        if (DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_BLUETOOTH, GD_L2CAP_FLAG, false)) {
+            initFlags.add(String.format("%s=%s", GD_L2CAP_FLAG, "true"));
+        }
+        if (DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_BLUETOOTH, GD_RUST_FLAG, false)) {
+            initFlags.add(String.format("%s=%s", GD_RUST_FLAG, "true"));
+        }
+        if (DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_BLUETOOTH, GD_LINK_POLICY_FLAG, false)) {
+            initFlags.add(String.format("%s=%s", GD_LINK_POLICY_FLAG, "true"));
+        }
+        if (DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_BLUETOOTH,
+                LOGGING_DEBUG_ENABLED_FOR_ALL_FLAG, false)) {
+            initFlags.add(String.format("%s=%s", LOGGING_DEBUG_ENABLED_FOR_ALL_FLAG, "true"));
+        }
+        String debugLoggingEnabledTags = DeviceConfig.getString(DeviceConfig.NAMESPACE_BLUETOOTH,
+                LOGGING_DEBUG_ENABLED_FOR_TAGS_FLAG, "");
+        if (!debugLoggingEnabledTags.isEmpty()) {
+            initFlags.add(String.format("%s=%s", LOGGING_DEBUG_ENABLED_FOR_TAGS_FLAG,
+                    debugLoggingEnabledTags));
+        }
+        String debugLoggingDisabledTags = DeviceConfig.getString(DeviceConfig.NAMESPACE_BLUETOOTH,
+                LOGGING_DEBUG_DISABLED_FOR_TAGS_FLAG, "");
+        if (!debugLoggingDisabledTags.isEmpty()) {
+            initFlags.add(String.format("%s=%s", LOGGING_DEBUG_DISABLED_FOR_TAGS_FLAG,
+                    debugLoggingDisabledTags));
+        }
+        if (DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_BLUETOOTH, BTAA_HCI_LOG_FLAG, false)) {
+            initFlags.add(String.format("%s=%s", BTAA_HCI_LOG_FLAG, "true"));
+        }
+        return initFlags.toArray(new String[0]);
+    }
+
+    private final Object mDeviceConfigLock = new Object();
+
+    /**
+     * Predicate that can be applied to names to determine if a device is
+     * well-known to be used for physical location.
+     */
+    @GuardedBy("mDeviceConfigLock")
+    private Predicate<String> mLocationDenylistName = (v) -> false;
+
+    /**
+     * Predicate that can be applied to MAC addresses to determine if a device
+     * is well-known to be used for physical location.
+     */
+    @GuardedBy("mDeviceConfigLock")
+    private Predicate<byte[]> mLocationDenylistMac = (v) -> false;
+
+    /**
+     * Predicate that can be applied to Advertising Data payloads to determine
+     * if a device is well-known to be used for physical location.
+     */
+    @GuardedBy("mDeviceConfigLock")
+    private Predicate<byte[]> mLocationDenylistAdvertisingData = (v) -> false;
+
+    @GuardedBy("mDeviceConfigLock")
+    private int mScanQuotaCount = DeviceConfigListener.DEFAULT_SCAN_QUOTA_COUNT;
+    @GuardedBy("mDeviceConfigLock")
+    private long mScanQuotaWindowMillis = DeviceConfigListener.DEFAULT_SCAN_QUOTA_WINDOW_MILLIS;
+    @GuardedBy("mDeviceConfigLock")
+    private long mScanTimeoutMillis = DeviceConfigListener.DEFAULT_SCAN_TIMEOUT_MILLIS;
+
+    public @NonNull Predicate<String> getLocationDenylistName() {
+        synchronized (mDeviceConfigLock) {
+            return mLocationDenylistName;
+        }
+    }
+
+    public @NonNull Predicate<byte[]> getLocationDenylistMac() {
+        synchronized (mDeviceConfigLock) {
+            return mLocationDenylistMac;
+        }
+    }
+
+    public @NonNull Predicate<byte[]> getLocationDenylistAdvertisingData() {
+        synchronized (mDeviceConfigLock) {
+            return mLocationDenylistAdvertisingData;
+        }
+    }
+
+    public int getScanQuotaCount() {
+        synchronized (mDeviceConfigLock) {
+            return mScanQuotaCount;
+        }
+    }
+
+    public long getScanQuotaWindowMillis() {
+        synchronized (mDeviceConfigLock) {
+            return mScanQuotaWindowMillis;
+        }
+    }
+
+    public long getScanTimeoutMillis() {
+        synchronized (mDeviceConfigLock) {
+            return mScanTimeoutMillis;
+        }
+    }
+
+    private final DeviceConfigListener mDeviceConfigListener = new DeviceConfigListener();
+
+    private class DeviceConfigListener implements DeviceConfig.OnPropertiesChangedListener {
+        private static final String LOCATION_DENYLIST_NAME =
+                "location_denylist_name";
+        private static final String LOCATION_DENYLIST_MAC =
+                "location_denylist_mac";
+        private static final String LOCATION_DENYLIST_ADVERTISING_DATA =
+                "location_denylist_advertising_data";
+        private static final String SCAN_QUOTA_COUNT =
+                "scan_quota_count";
+        private static final String SCAN_QUOTA_WINDOW_MILLIS =
+                "scan_quota_window_millis";
+        private static final String SCAN_TIMEOUT_MILLIS =
+                "scan_timeout_millis";
+
+        /**
+         * Default denylist which matches Eddystone and iBeacon payloads.
+         */
+        private static final String DEFAULT_LOCATION_DENYLIST_ADVERTISING_DATA =
+                "⊆0016AAFE/00FFFFFF,⊆00FF4C0002/00FFFFFFFF";
+
+        private static final int DEFAULT_SCAN_QUOTA_COUNT = 5;
+        private static final long DEFAULT_SCAN_QUOTA_WINDOW_MILLIS = 30 * SECOND_IN_MILLIS;
+        private static final long DEFAULT_SCAN_TIMEOUT_MILLIS = 30 * MINUTE_IN_MILLIS;
+
+        public void start() {
+            DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_BLUETOOTH,
+                    BackgroundThread.getExecutor(), this);
+            onPropertiesChanged(DeviceConfig.getProperties(DeviceConfig.NAMESPACE_BLUETOOTH));
+        }
+
+        @Override
+        public void onPropertiesChanged(DeviceConfig.Properties properties) {
+            synchronized (mDeviceConfigLock) {
+                final String name = properties.getString(LOCATION_DENYLIST_NAME, null);
+                mLocationDenylistName = !TextUtils.isEmpty(name)
+                        ? Pattern.compile(name).asPredicate()
+                        : (v) -> false;
+                mLocationDenylistMac = BytesMatcher
+                        .decode(properties.getString(LOCATION_DENYLIST_MAC, null));
+                mLocationDenylistAdvertisingData = BytesMatcher
+                        .decode(properties.getString(LOCATION_DENYLIST_ADVERTISING_DATA,
+                                DEFAULT_LOCATION_DENYLIST_ADVERTISING_DATA));
+                mScanQuotaCount = properties.getInt(SCAN_QUOTA_COUNT,
+                        DEFAULT_SCAN_QUOTA_COUNT);
+                mScanQuotaWindowMillis = properties.getLong(SCAN_QUOTA_WINDOW_MILLIS,
+                        DEFAULT_SCAN_QUOTA_WINDOW_MILLIS);
+                mScanTimeoutMillis = properties.getLong(SCAN_TIMEOUT_MILLIS,
+                        DEFAULT_SCAN_TIMEOUT_MILLIS);
+            }
+        }
+    }
+
     /**
      *  Obfuscate Bluetooth MAC address into a PII free ID string
      *
@@ -3069,6 +3826,38 @@
     }
 
     /**
+     * Get dynamic audio buffer size supported type
+     *
+     * @return support <p>Possible values are
+     * {@link BluetoothA2dp#DYNAMIC_BUFFER_SUPPORT_NONE},
+     * {@link BluetoothA2dp#DYNAMIC_BUFFER_SUPPORT_A2DP_OFFLOAD},
+     * {@link BluetoothA2dp#DYNAMIC_BUFFER_SUPPORT_A2DP_SOFTWARE_ENCODING}.
+     */
+    public int getDynamicBufferSupport() {
+        return mAdapterProperties.getDynamicBufferSupport();
+    }
+
+    /**
+     * Get dynamic audio buffer size
+     *
+     * @return BufferConstraints
+     */
+    public BufferConstraints getBufferConstraints() {
+        return mAdapterProperties.getBufferConstraints();
+    }
+
+    /**
+     * Set dynamic audio buffer size
+     *
+     * @param codec Audio codec
+     * @param value buffer millis
+     * @return true if the settings is successful, false otherwise
+     */
+    public boolean setBufferLengthMillis(int codec, int value) {
+        return mAdapterProperties.setBufferLengthMillis(codec, value);
+    }
+
+    /**
      *  Get an incremental id of Bluetooth metrics and log
      *
      *  @param device Bluetooth device
@@ -3083,8 +3872,8 @@
 
     static native void classInitNative();
 
-    native boolean initNative(boolean startRestricted, boolean isNiapMode,
-            int configCompareResult, boolean isAtvDevice);
+    native boolean initNative(boolean startRestricted, boolean isCommonCriteriaMode,
+            int configCompareResult, String[] initFlags, boolean isAtvDevice);
 
     native void cleanupNative();
 
@@ -3113,18 +3902,22 @@
     native boolean getDevicePropertyNative(byte[] address, int type);
 
     /*package*/
-    native boolean createBondNative(byte[] address, int transport);
+    public native boolean createBondNative(byte[] address, int transport);
 
     /*package*/
-    native boolean createBondOutOfBandNative(byte[] address, int transport, OobData oobData);
+    native boolean createBondOutOfBandNative(byte[] address, int transport,
+            OobData p192Data, OobData p256Data);
 
     /*package*/
-    native boolean removeBondNative(byte[] address);
+    public native boolean removeBondNative(byte[] address);
 
     /*package*/
     native boolean cancelBondNative(byte[] address);
 
     /*package*/
+    native void generateLocalOobDataNative(int transport);
+
+    /*package*/
     native boolean sdpSearchNative(byte[] address, byte[] uuid);
 
     /*package*/
@@ -3161,6 +3954,8 @@
 
     private native byte[] obfuscateAddressNative(byte[] address);
 
+    native boolean setBufferLengthMillisNative(int codec, int value);
+
     private native int getMetricIdNative(byte[] address);
 
     /*package*/ native int connectSocketNative(
diff --git a/src/com/android/bluetooth/btservice/AdapterState.java b/src/com/android/bluetooth/btservice/AdapterState.java
index 8596627..3f153bd 100644
--- a/src/com/android/bluetooth/btservice/AdapterState.java
+++ b/src/com/android/bluetooth/btservice/AdapterState.java
@@ -20,6 +20,8 @@
 import android.os.Message;
 import android.util.Log;
 
+import com.android.bluetooth.R;
+import com.android.bluetooth.telephony.BluetoothInCallService;
 import com.android.bluetooth.statemachine.State;
 import com.android.bluetooth.statemachine.StateMachine;
 
@@ -223,6 +225,21 @@
         }
 
         @Override
+        public void enter() {
+            super.enter();
+            mAdapterService.enableBluetoothInCallService(true);
+        }
+
+        @Override
+        public void exit() {
+            BluetoothInCallService bluetoothInCallService = BluetoothInCallService.getInstance();
+            if (bluetoothInCallService == null) {
+                mAdapterService.enableBluetoothInCallService(false);
+            }
+            super.exit();
+        }
+
+        @Override
         public boolean processMessage(Message msg) {
             switch (msg.what) {
                 case USER_TURN_OFF:
@@ -367,6 +384,7 @@
         @Override
         public void enter() {
             super.enter();
+            mAdapterService.enableBluetoothInCallService(false);
             sendMessageDelayed(BLE_STOP_TIMEOUT, BLE_STOP_TIMEOUT_DELAY);
             mAdapterService.bringDownBle();
         }
diff --git a/src/com/android/bluetooth/btservice/BluetoothSocketManagerBinder.java b/src/com/android/bluetooth/btservice/BluetoothSocketManagerBinder.java
index 42595ab..2187a91 100644
--- a/src/com/android/bluetooth/btservice/BluetoothSocketManagerBinder.java
+++ b/src/com/android/bluetooth/btservice/BluetoothSocketManagerBinder.java
@@ -21,13 +21,13 @@
 import android.os.Binder;
 import android.os.ParcelFileDescriptor;
 import android.os.ParcelUuid;
+
 import com.android.bluetooth.Utils;
 
 class BluetoothSocketManagerBinder extends IBluetoothSocketManager.Stub {
     private static final String TAG = "BluetoothSocketManagerBinder";
 
     private static final int INVALID_FD = -1;
-    private static final String BLUETOOTH_PERM = android.Manifest.permission.BLUETOOTH;
 
     private AdapterService mService;
 
@@ -43,7 +43,11 @@
     public ParcelFileDescriptor connectSocket(
             BluetoothDevice device, int type, ParcelUuid uuid, int port, int flag) {
 
-        enforceBluetoothAndActiveUser();
+        enforceActiveUser();
+
+        if (!Utils.checkConnectPermissionForPreflight(mService)) {
+            return null;
+        }
 
         return marshalFd(mService.connectSocketNative(
             Utils.getBytesFromAddress(device.getAddress()),
@@ -58,7 +62,11 @@
     public ParcelFileDescriptor createSocketChannel(
             int type, String serviceName, ParcelUuid uuid, int port, int flag) {
 
-        enforceBluetoothAndActiveUser();
+        enforceActiveUser();
+
+        if (!Utils.checkConnectPermissionForPreflight(mService)) {
+            return null;
+        }
 
         return marshalFd(mService.createSocketChannelNative(
             type,
@@ -72,16 +80,19 @@
 
     @Override
     public void requestMaximumTxDataLength(BluetoothDevice device) {
-        enforceBluetoothAndActiveUser();
+        enforceActiveUser();
+
+        if (!Utils.checkConnectPermissionForPreflight(mService)) {
+            return;
+        }
 
         mService.requestMaximumTxDataLengthNative(Utils.getBytesFromAddress(device.getAddress()));
     }
 
-    private void enforceBluetoothAndActiveUser() {
-        if (!Utils.checkCallerAllowManagedProfiles(mService)) {
+    private void enforceActiveUser() {
+        if (!Utils.checkCallerIsSystemOrActiveOrManagedUser(mService, TAG)) {
             throw new SecurityException("Not allowed for non-active user");
         }
-        mService.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
     }
 
     private static ParcelFileDescriptor marshalFd(int fd) {
diff --git a/src/com/android/bluetooth/btservice/BondStateMachine.java b/src/com/android/bluetooth/btservice/BondStateMachine.java
index 8b42678..bd2ddd0 100644
--- a/src/com/android/bluetooth/btservice/BondStateMachine.java
+++ b/src/com/android/bluetooth/btservice/BondStateMachine.java
@@ -16,14 +16,24 @@
 
 package com.android.bluetooth.btservice;
 
+import static android.Manifest.permission.BLUETOOTH_CONNECT;
+
+import android.annotation.RequiresPermission;
+import android.annotation.Nullable;
+import android.app.Activity;
+import android.app.ActivityThread;
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothClass;
 import android.bluetooth.BluetoothDevice;
 import android.bluetooth.BluetoothProfile;
 import android.bluetooth.BluetoothProtoEnums;
 import android.bluetooth.OobData;
+import android.content.Attributable;
+import android.content.BroadcastReceiver;
 import android.content.Intent;
 import android.os.Build;
+import android.os.Bundle;
+import android.os.Handler;
 import android.os.Message;
 import android.os.UserHandle;
 import android.util.Log;
@@ -77,7 +87,8 @@
     private PendingCommandState mPendingCommandState = new PendingCommandState();
     private StableState mStableState = new StableState();
 
-    public static final String OOBDATA = "oobdata";
+    public static final String OOBDATAP192 = "oobdatap192";
+    public static final String OOBDATAP256 = "oobdatap256";
 
     @VisibleForTesting Set<BluetoothDevice> mPendingBondedDevices = new HashSet<>();
 
@@ -126,16 +137,17 @@
         public synchronized boolean processMessage(Message msg) {
 
             BluetoothDevice dev = (BluetoothDevice) msg.obj;
+            Attributable.setAttributionSource(dev,
+                    ActivityThread.currentAttributionSource());
 
             switch (msg.what) {
 
                 case CREATE_BOND:
-                    OobData oobData = null;
-                    if (msg.getData() != null) {
-                        oobData = msg.getData().getParcelable(OOBDATA);
-                    }
-
-                    createBond(dev, msg.arg1, oobData, true);
+                    OobData p192Data = (msg.getData() != null)
+                            ? msg.getData().getParcelable(OOBDATAP192) : null;
+                    OobData p256Data = (msg.getData() != null)
+                            ? msg.getData().getParcelable(OOBDATAP256) : null;
+                    createBond(dev, msg.arg1, p192Data, p256Data, true);
                     break;
                 case REMOVE_BOND:
                     removeBond(dev, true);
@@ -175,12 +187,14 @@
         @Override
         public void enter() {
             infoLog("Entering PendingCommandState State");
-            BluetoothDevice dev = (BluetoothDevice) getCurrentMessage().obj;
         }
 
         @Override
         public synchronized boolean processMessage(Message msg) {
             BluetoothDevice dev = (BluetoothDevice) msg.obj;
+            Attributable.setAttributionSource(dev,
+                    ActivityThread.currentAttributionSource());
+
             DeviceProperties devProp = mRemoteDevices.getDeviceProperties(dev);
             boolean result = false;
             if (mDevices.contains(dev) && msg.what != CANCEL_BOND
@@ -192,12 +206,11 @@
 
             switch (msg.what) {
                 case CREATE_BOND:
-                    OobData oobData = null;
-                    if (msg.getData() != null) {
-                        oobData = msg.getData().getParcelable(OOBDATA);
-                    }
-
-                    result = createBond(dev, msg.arg1, oobData, false);
+                    OobData p192Data = (msg.getData() != null)
+                            ? msg.getData().getParcelable(OOBDATAP192) : null;
+                    OobData p256Data = (msg.getData() != null)
+                            ? msg.getData().getParcelable(OOBDATAP256) : null;
+                    result = createBond(dev, msg.arg1, p192Data, p256Data, false);
                     break;
                 case REMOVE_BOND:
                     result = removeBond(dev, false);
@@ -215,14 +228,11 @@
                     }
                     sendIntent(dev, newState, reason);
                     if (newState != BluetoothDevice.BOND_BONDING) {
-                        /* this is either none/bonded, remove and transition */
-                        result = !mDevices.remove(dev);
+                        // This is either none/bonded, remove and transition, and also set
+                        // result=false to avoid adding the device to mDevices.
+                        mDevices.remove(dev);
+                        result = false;
                         if (mDevices.isEmpty()) {
-                            // Whenever mDevices is empty, then we need to
-                            // set result=false. Else, we will end up adding
-                            // the device to the list again. This prevents us
-                            // from pairing with a device that we just unpaired
-                            result = false;
                             transitionTo(mStableState);
                         }
                         if (newState == BluetoothDevice.BOND_NONE) {
@@ -298,7 +308,8 @@
     }
 
     private boolean removeBond(BluetoothDevice dev, boolean transition) {
-        if (dev.getBondState() == BluetoothDevice.BOND_BONDED) {
+        DeviceProperties devProp = mRemoteDevices.getDeviceProperties(dev);
+        if (devProp != null && devProp.getBondState() == BluetoothDevice.BOND_BONDED) {
             byte[] addr = Utils.getBytesFromAddress(dev.getAddress());
             if (!mAdapterService.removeBondNative(addr)) {
                 Log.e(TAG, "Unexpected error while removing bond:");
@@ -313,23 +324,27 @@
         return false;
     }
 
-    private boolean createBond(BluetoothDevice dev, int transport, OobData oobData,
-            boolean transition) {
+    private boolean createBond(BluetoothDevice dev, int transport, OobData remoteP192Data,
+            OobData remoteP256Data, boolean transition) {
         if (dev.getBondState() == BluetoothDevice.BOND_NONE) {
             infoLog("Bond address is:" + dev);
             byte[] addr = Utils.getBytesFromAddress(dev.getAddress());
             boolean result;
-            if (oobData != null) {
-                result = mAdapterService.createBondOutOfBandNative(addr, transport, oobData);
+            // If we have some data
+            if (remoteP192Data != null || remoteP256Data != null) {
+                result = mAdapterService.createBondOutOfBandNative(addr, transport,
+                    remoteP192Data, remoteP256Data);
             } else {
                 result = mAdapterService.createBondNative(addr, transport);
             }
             BluetoothStatsLog.write(BluetoothStatsLog.BLUETOOTH_BOND_STATE_CHANGED,
                     mAdapterService.obfuscateAddress(dev), transport, dev.getType(),
                     BluetoothDevice.BOND_BONDING,
-                    oobData == null ? BluetoothProtoEnums.BOND_SUB_STATE_UNKNOWN
+                    remoteP192Data == null && remoteP256Data == null
+                            ? BluetoothProtoEnums.BOND_SUB_STATE_UNKNOWN
                             : BluetoothProtoEnums.BOND_SUB_STATE_LOCAL_OOB_DATA_PROVIDED,
                     BluetoothProtoEnums.UNBOND_REASON_UNKNOWN);
+
             if (!result) {
                 BluetoothStatsLog.write(BluetoothStatsLog.BLUETOOTH_BOND_STATE_CHANGED,
                         mAdapterService.obfuscateAddress(dev), transport, dev.getType(),
@@ -356,7 +371,10 @@
         intent.setFlags(Intent.FLAG_RECEIVER_FOREGROUND);
         // Workaround for Android Auto until pre-accepting pairing requests is added.
         intent.addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
-        mAdapterService.sendOrderedBroadcast(intent, AdapterService.BLUETOOTH_ADMIN_PERM);
+        mAdapterService.sendOrderedBroadcast(intent, BLUETOOTH_CONNECT,
+                Utils.getTempAllowlistBroadcastOptions(), null/* resultReceiver */,
+                null/* scheduler */, Activity.RESULT_OK/* initialCode */, null/* initialData */,
+                null/* initialExtras */);
     }
 
     @VisibleForTesting
@@ -420,7 +438,8 @@
         if (newState == BluetoothDevice.BOND_NONE) {
             intent.putExtra(BluetoothDevice.EXTRA_REASON, reason);
         }
-        mAdapterService.sendBroadcastAsUser(intent, UserHandle.ALL, AdapterService.BLUETOOTH_PERM);
+        mAdapterService.sendBroadcastAsUser(intent, UserHandle.ALL, BLUETOOTH_CONNECT,
+                Utils.getTempAllowlistBroadcastOptions());
         infoLog("Bond State Change Intent:" + device + " " + state2str(oldState) + " => "
                 + state2str(newState));
     }
@@ -523,7 +542,7 @@
                 BluetoothProtoEnums.BOND_SUB_STATE_LOCAL_PIN_REQUESTED, 0);
 
         infoLog("pinRequestCallback: " + bdDevice.getAddress()
-                + " name:" + bdDevice.getName() + " cod:" + new BluetoothClass(cod));
+                + " name:" + Utils.getName(bdDevice) + " cod:" + new BluetoothClass(cod));
 
         Message msg = obtainMessage(PIN_REQUEST);
         msg.obj = bdDevice;
@@ -532,6 +551,10 @@
         sendMessage(msg);
     }
 
+    @RequiresPermission(allOf = {
+            android.Manifest.permission.BLUETOOTH_PRIVILEGED,
+            android.Manifest.permission.MODIFY_PHONE_STATE,
+    })
     private void clearProfilePriority(BluetoothDevice device) {
         HidHostService hidService = HidHostService.getHidHostService();
         A2dpService a2dpService = A2dpService.getA2dpService();
diff --git a/src/com/android/bluetooth/btservice/Config.java b/src/com/android/bluetooth/btservice/Config.java
index d90e3e6..4153537 100644
--- a/src/com/android/bluetooth/btservice/Config.java
+++ b/src/com/android/bluetooth/btservice/Config.java
@@ -22,10 +22,10 @@
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.res.Resources;
-import android.os.SystemProperties;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.os.ServiceManager;
+import android.os.SystemProperties;
 import android.provider.Settings;
 import android.text.TextUtils;
 import android.util.Log;
@@ -111,6 +111,8 @@
 
     private static Class[] sSupportedProfiles = new Class[0];
 
+    private static boolean sIsGdEnabledUptoScanningLayer = false;
+
     static void init(Context ctx) {
         if (ctx == null) {
             return;
@@ -143,12 +145,17 @@
             }
         }
         sSupportedProfiles = profiles.toArray(new Class[profiles.size()]);
+        sIsGdEnabledUptoScanningLayer = resources.getBoolean(R.bool.enable_gd_up_to_scanning_layer);
     }
 
     static Class[] getSupportedProfiles() {
         return sSupportedProfiles;
     }
 
+    static boolean isGdEnabledUpToScanningLayer() {
+        return sIsGdEnabledUptoScanningLayer;
+    }
+
     private static long getProfileMask(Class profile) {
         for (ProfileConfig config : PROFILE_SERVICES_AND_FLAGS) {
             if (config.mClass == profile) {
diff --git a/src/com/android/bluetooth/btservice/DiscoveringPackage.java b/src/com/android/bluetooth/btservice/DiscoveringPackage.java
index baa2b24..72bf389 100644
--- a/src/com/android/bluetooth/btservice/DiscoveringPackage.java
+++ b/src/com/android/bluetooth/btservice/DiscoveringPackage.java
@@ -16,20 +16,30 @@
 
 package com.android.bluetooth.btservice;
 
-final class DiscoveringPackage {
-    private String mPackageName;
-    private String mPermission;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 
-    DiscoveringPackage(String packageName, String permission) {
+final class DiscoveringPackage {
+    private @NonNull String mPackageName;
+    private @Nullable String mPermission;
+    private boolean mHasDisavowedLocation;
+
+    DiscoveringPackage(@NonNull String packageName, @Nullable String permission,
+            boolean hasDisavowedLocation) {
         mPackageName = packageName;
         mPermission = permission;
+        mHasDisavowedLocation = hasDisavowedLocation;
     }
 
-    public String getPackageName() {
+    public @NonNull String getPackageName() {
         return mPackageName;
     }
 
-    public String getPermission() {
+    public @Nullable String getPermission() {
         return mPermission;
     }
+
+    public boolean hasDisavowedLocation() {
+        return mHasDisavowedLocation;
+    }
 }
diff --git a/src/com/android/bluetooth/btservice/JniCallbacks.java b/src/com/android/bluetooth/btservice/JniCallbacks.java
index c835999..231751e 100644
--- a/src/com/android/bluetooth/btservice/JniCallbacks.java
+++ b/src/com/android/bluetooth/btservice/JniCallbacks.java
@@ -16,6 +16,8 @@
 
 package com.android.bluetooth.btservice;
 
+import android.bluetooth.OobData;
+
 final class JniCallbacks {
 
     private RemoteDevices mRemoteDevices;
@@ -65,8 +67,8 @@
         mBondStateMachine.bondStateChangeCallback(status, address, newState);
     }
 
-    void aclStateChangeCallback(int status, byte[] address, int newState) {
-        mRemoteDevices.aclStateChangeCallback(status, address, newState);
+    void aclStateChangeCallback(int status, byte[] address, int newState, int hciReason) {
+        mRemoteDevices.aclStateChangeCallback(status, address, newState, hciReason);
     }
 
     void stateChangeCallback(int status) {
@@ -81,4 +83,21 @@
         mAdapterProperties.adapterPropertyChangedCallback(types, val);
     }
 
+    void oobDataReceivedCallback(int transport, OobData oobData) {
+        mAdapterService.notifyOobDataCallback(transport, oobData);
+    }
+
+    void linkQualityReportCallback(
+            long timestamp,
+            int report_id,
+            int rssi,
+            int snr,
+            int retransmission_count,
+            int packets_not_receive_count,
+            int negative_acknowledgement_count) {
+        mAdapterService.linkQualityReportCallback(
+                timestamp, report_id, rssi, snr, retransmission_count,
+                packets_not_receive_count, negative_acknowledgement_count);
+    }
+
 }
diff --git a/src/com/android/bluetooth/btservice/PhonePolicy.java b/src/com/android/bluetooth/btservice/PhonePolicy.java
index 05a0183..ae3eed5 100644
--- a/src/com/android/bluetooth/btservice/PhonePolicy.java
+++ b/src/com/android/bluetooth/btservice/PhonePolicy.java
@@ -16,13 +16,17 @@
 
 package com.android.bluetooth.btservice;
 
+import android.annotation.RequiresPermission;
+import android.app.ActivityThread;
 import android.bluetooth.BluetoothA2dp;
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothDevice;
 import android.bluetooth.BluetoothHeadset;
 import android.bluetooth.BluetoothHearingAid;
+import android.bluetooth.BluetoothLeAudio;
 import android.bluetooth.BluetoothProfile;
 import android.bluetooth.BluetoothUuid;
+import android.content.Attributable;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
@@ -35,6 +39,7 @@
 import android.util.Log;
 
 import com.android.bluetooth.a2dp.A2dpService;
+import com.android.bluetooth.btservice.storage.DatabaseManager;
 import com.android.bluetooth.hearingaid.HearingAidService;
 import com.android.bluetooth.hfp.HeadsetService;
 import com.android.bluetooth.hid.HidHostService;
@@ -45,6 +50,7 @@
 
 import java.util.HashSet;
 import java.util.List;
+import java.util.Objects;
 
 // Describes the phone policy
 //
@@ -86,6 +92,7 @@
     // Timeouts
     @VisibleForTesting static int sConnectOtherProfilesTimeoutMillis = 6000; // 6s
 
+    private DatabaseManager mDatabaseManager;
     private final AdapterService mAdapterService;
     private final ServiceFactory mFactory;
     private final Handler mHandler;
@@ -113,6 +120,11 @@
                             BluetoothProfile.A2DP, -1, // No-op argument
                             intent).sendToTarget();
                     break;
+                case BluetoothLeAudio.ACTION_LE_AUDIO_CONNECTION_STATE_CHANGED:
+                    mHandler.obtainMessage(MESSAGE_PROFILE_CONNECTION_STATE_CHANGED,
+                            BluetoothProfile.LE_AUDIO, -1, // No-op argument
+                            intent).sendToTarget();
+                    break;
                 case BluetoothA2dp.ACTION_ACTIVE_DEVICE_CHANGED:
                     mHandler.obtainMessage(MESSAGE_PROFILE_ACTIVE_DEVICE_CHANGED,
                             BluetoothProfile.A2DP, -1, // No-op argument
@@ -128,6 +140,11 @@
                             BluetoothProfile.HEARING_AID, -1, // No-op argument
                             intent).sendToTarget();
                     break;
+                case BluetoothLeAudio.ACTION_LE_AUDIO_ACTIVE_DEVICE_CHANGED:
+                    mHandler.obtainMessage(MESSAGE_PROFILE_ACTIVE_DEVICE_CHANGED,
+                            BluetoothProfile.LE_AUDIO, -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.
@@ -201,6 +218,8 @@
                     // Called when we try connect some profiles in processConnectOtherProfiles but
                     // we send a delayed message to try connecting the remaining profiles
                     BluetoothDevice device = (BluetoothDevice) msg.obj;
+                    Attributable.setAttributionSource(device,
+                            ActivityThread.currentAttributionSource());
                     processConnectOtherProfiles(device);
                     mConnectOtherProfilesDeviceSet.remove(device);
                     break;
@@ -226,12 +245,14 @@
         IntentFilter filter = new IntentFilter();
         filter.addAction(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED);
         filter.addAction(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED);
+        filter.addAction(BluetoothLeAudio.ACTION_LE_AUDIO_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);
+        filter.addAction(BluetoothLeAudio.ACTION_LE_AUDIO_ACTIVE_DEVICE_CHANGED);
         mAdapterService.registerReceiver(mReceiver, filter);
     }
 
@@ -242,11 +263,14 @@
 
     PhonePolicy(AdapterService service, ServiceFactory factory) {
         mAdapterService = service;
+        mDatabaseManager = Objects.requireNonNull(mAdapterService.getDatabase(),
+                "DatabaseManager cannot be null when PhonePolicy starts");
         mFactory = factory;
         mHandler = new PhonePolicyHandler(service.getMainLooper());
     }
 
     // Policy implementation, all functions MUST be private
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED)
     private void processInitProfilePriorities(BluetoothDevice device, ParcelUuid[] uuids) {
         debugLog("processInitProfilePriorities() - device " + device);
         HidHostService hidService = mFactory.getHidHostService();
@@ -300,11 +324,13 @@
         }
     }
 
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED)
     private void processProfileStateChanged(BluetoothDevice device, int profileId, int nextState,
             int prevState) {
         debugLog("processProfileStateChanged, device=" + device + ", profile=" + profileId + ", "
                 + prevState + " -> " + nextState);
-        if (((profileId == BluetoothProfile.A2DP) || (profileId == BluetoothProfile.HEADSET))) {
+        if (((profileId == BluetoothProfile.A2DP) || (profileId == BluetoothProfile.HEADSET)
+                || (profileId == BluetoothProfile.LE_AUDIO))) {
             if (nextState == BluetoothProfile.STATE_CONNECTED) {
                 switch (profileId) {
                     case BluetoothProfile.A2DP:
@@ -318,7 +344,7 @@
             }
             if (nextState == BluetoothProfile.STATE_DISCONNECTED) {
                 if (profileId == BluetoothProfile.A2DP) {
-                    mAdapterService.getDatabase().setDisconnection(device);
+                    mDatabaseManager.setDisconnection(device);
                 }
                 handleAllProfilesDisconnected(device);
             }
@@ -335,15 +361,16 @@
         debugLog("processActiveDeviceChanged, device=" + device + ", profile=" + profileId);
 
         if (device != null) {
-            mAdapterService.getDatabase().setConnection(device, profileId == BluetoothProfile.A2DP);
+            mDatabaseManager.setConnection(device, profileId == BluetoothProfile.A2DP);
         }
     }
 
     private void processDeviceConnected(BluetoothDevice device) {
         debugLog("processDeviceConnected, device=" + device);
-        mAdapterService.getDatabase().setConnection(device, false);
+        mDatabaseManager.setConnection(device, false);
     }
 
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED)
     private boolean handleAllProfilesDisconnected(BluetoothDevice device) {
         boolean atLeastOneProfileConnectedForDevice = false;
         boolean allProfilesEmpty = true;
@@ -388,6 +415,7 @@
         mA2dpRetrySet.clear();
     }
 
+    @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
     private void autoConnect() {
         if (mAdapterService.getState() != BluetoothAdapter.STATE_ON) {
             errorLog("autoConnect: BT is not ON. Exiting autoConnect");
@@ -397,7 +425,7 @@
         if (!mAdapterService.isQuietModeEnabled()) {
             debugLog("autoConnect: Initiate auto connection on BT on...");
             final BluetoothDevice mostRecentlyActiveA2dpDevice =
-                    mAdapterService.getDatabase().getMostRecentlyConnectedA2dpDevice();
+                    mDatabaseManager.getMostRecentlyConnectedA2dpDevice();
             if (mostRecentlyActiveA2dpDevice == null) {
                 errorLog("autoConnect: most recently active a2dp device is null");
                 return;
@@ -427,6 +455,7 @@
         }
     }
 
+    @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
     private void autoConnectHeadset(BluetoothDevice device) {
         final HeadsetService hsService = mFactory.getHeadsetService();
         if (hsService == null) {
@@ -462,6 +491,10 @@
     // profiles which are not already connected or in the process of connecting to attempt to
     // connect to the device that initiated the connection.  In the event that this function is
     // invoked and there are no current bluetooth connections no new profiles will be connected.
+    @RequiresPermission(allOf = {
+            android.Manifest.permission.BLUETOOTH_PRIVILEGED,
+            android.Manifest.permission.MODIFY_PHONE_STATE,
+    })
     private void processConnectOtherProfiles(BluetoothDevice device) {
         debugLog("processConnectOtherProfiles, device=" + device);
         if (mAdapterService.getState() != BluetoothAdapter.STATE_ON) {
diff --git a/src/com/android/bluetooth/btservice/ProfileService.java b/src/com/android/bluetooth/btservice/ProfileService.java
index 9a7186c..f582ae5 100644
--- a/src/com/android/bluetooth/btservice/ProfileService.java
+++ b/src/com/android/bluetooth/btservice/ProfileService.java
@@ -16,10 +16,14 @@
 
 package com.android.bluetooth.btservice;
 
+import static android.Manifest.permission.BLUETOOTH_CONNECT;
+
+import android.annotation.SuppressLint;
 import android.app.ActivityManager;
 import android.app.Service;
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothDevice;
+import android.content.Attributable;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
@@ -39,8 +43,6 @@
 public abstract class ProfileService extends Service {
     private static final boolean DBG = false;
 
-    public static final String BLUETOOTH_ADMIN_PERM = android.Manifest.permission.BLUETOOTH_ADMIN;
-    public static final String BLUETOOTH_PERM = android.Manifest.permission.BLUETOOTH;
     public static final String BLUETOOTH_PRIVILEGED =
             android.Manifest.permission.BLUETOOTH_PRIVILEGED;
 
@@ -60,15 +62,20 @@
     private AdapterService mAdapterService;
     private BroadcastReceiver mUserSwitchedReceiver;
     private boolean mProfileStarted = false;
+    private volatile boolean mTestModeEnabled = false;
 
     public String getName() {
         return getClass().getSimpleName();
     }
 
-    protected boolean isAvailable() {
+    public boolean isAvailable() {
         return mProfileStarted;
     }
 
+    protected boolean isTestModeEnabled() {
+        return mTestModeEnabled;
+    }
+
     /**
      * Called in {@link #onCreate()} to init binder interface for this profile service
      *
@@ -79,6 +86,8 @@
     /**
      * Called in {@link #onCreate()} to init basic stuff in this service
      */
+    // Suppressed since this is called from framework
+    @SuppressLint("AndroidFrameworkRequiresPermission")
     protected void create() {}
 
     /**
@@ -86,6 +95,8 @@
      *
      * @return True in successful condition, False otherwise
      */
+    // Suppressed since this is called from framework
+    @SuppressLint("AndroidFrameworkRequiresPermission")
     protected abstract boolean start();
 
     /**
@@ -93,28 +104,47 @@
      *
      * @return True in successful condition, False otherwise
      */
+    // Suppressed since this is called from framework
+    @SuppressLint("AndroidFrameworkRequiresPermission")
     protected abstract boolean stop();
 
     /**
      * Called in {@link #onDestroy()} when this object is completely discarded
      */
+    // Suppressed since this is called from framework
+    @SuppressLint("AndroidFrameworkRequiresPermission")
     protected void cleanup() {}
 
     /**
      * @param userId is equivalent to the result of ActivityManager.getCurrentUser()
      */
+    // Suppressed since this is called from framework
+    @SuppressLint("AndroidFrameworkRequiresPermission")
     protected void setCurrentUser(int userId) {}
 
     /**
      * @param userId is equivalent to the result of ActivityManager.getCurrentUser()
      */
+    // Suppressed since this is called from framework
+    @SuppressLint("AndroidFrameworkRequiresPermission")
     protected void setUserUnlocked(int userId) {}
 
+    /**
+     * @param testEnabled if the profile should enter or exit a testing mode
+     */
+    // Suppressed since this is called from framework
+    @SuppressLint("AndroidFrameworkRequiresPermission")
+    protected void setTestModeEnabled(boolean testModeEnabled) {
+        mTestModeEnabled = testModeEnabled;
+    }
+
     protected ProfileService() {
         mName = getName();
     }
 
     @Override
+    // Suppressed since this is called from framework
+    @SuppressLint("AndroidFrameworkRequiresPermission")
     public void onCreate() {
         if (DBG) {
             Log.d(mName, "onCreate");
@@ -126,12 +156,14 @@
     }
 
     @Override
+    // Suppressed since this is called from framework
+    @SuppressLint("AndroidFrameworkRequiresPermission")
     public int onStartCommand(Intent intent, int flags, int startId) {
         if (DBG) {
             Log.d(mName, "onStartCommand()");
         }
 
-        if (checkCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM)
+        if (checkCallingOrSelfPermission(BLUETOOTH_CONNECT)
                 != PackageManager.PERMISSION_GRANTED) {
             Log.e(mName, "Permission denied!");
             return PROFILE_SERVICE_MODE;
@@ -155,6 +187,8 @@
     }
 
     @Override
+    // Suppressed since this is called from framework
+    @SuppressLint("AndroidFrameworkRequiresPermission")
     public IBinder onBind(Intent intent) {
         if (DBG) {
             Log.d(mName, "onBind");
@@ -167,6 +201,8 @@
     }
 
     @Override
+    // Suppressed since this is called from framework
+    @SuppressLint("AndroidFrameworkRequiresPermission")
     public boolean onUnbind(Intent intent) {
         if (DBG) {
             Log.d(mName, "onUnbind");
@@ -179,6 +215,8 @@
      *
      * @param sb StringBuilder from the profile.
      */
+    // Suppressed since this is called from framework
+    @SuppressLint("AndroidFrameworkRequiresPermission")
     public void dump(StringBuilder sb) {
         sb.append("\nProfile: ");
         sb.append(mName);
@@ -190,6 +228,8 @@
      *
      * @param builder metrics proto builder
      */
+    // Suppressed since this is called from framework
+    @SuppressLint("AndroidFrameworkRequiresPermission")
     public void dumpProto(BluetoothMetricsProto.BluetoothLog.Builder builder) {
         // Do nothing
     }
@@ -207,6 +247,8 @@
     }
 
     @Override
+    // Suppressed since this is called from framework
+    @SuppressLint("AndroidFrameworkRequiresPermission")
     public void onDestroy() {
         cleanup();
         if (mBinder != null) {
@@ -228,6 +270,10 @@
             Log.w(mName, "Could not add this profile because AdapterService is null.");
             return;
         }
+        if (!mAdapterService.isStartedProfile(mName)) {
+            Log.w(mName, "Unexpectedly do Start, don't start");
+            return;
+        }
         mAdapterService.addProfile(this);
 
         IntentFilter filter = new IntentFilter();
@@ -269,6 +315,10 @@
     }
 
     private void doStop() {
+        if (mAdapterService == null || mAdapterService.isStartedProfile(mName)) {
+            Log.w(mName, "Unexpectedly do Stop, don't stop.");
+            return;
+        }
         if (!mProfileStarted) {
             Log.w(mName, "doStop() called, but the profile is not running.");
         }
@@ -289,10 +339,31 @@
         stopSelf();
     }
 
-    protected BluetoothDevice getDevice(byte[] address) {
-        if (mAdapter != null) {
-            return mAdapter.getRemoteDevice(Utils.getAddressStringFromByte(address));
-        }
-        return null;
+    /**
+     * Returns a {@link BluetoothDevice} instance for the given address, but
+     * with any {@link AttributionSource} details removed, making it "anonymous"
+     * and not suitable for local use within this process.
+     * <p>
+     * The returned object is intended to be returned to a remote caller for
+     * actual use, where {@link Attributable#setAttributionSource} will populate
+     * it accurately.
+     */
+    protected BluetoothDevice getAnonymousDevice(String address) {
+        return Attributable.setAttributionSource(
+                BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address), null);
+    }
+
+    /**
+     * Returns a {@link BluetoothDevice} instance for the given address, but
+     * with any {@link AttributionSource} details removed, making it "anonymous"
+     * and not suitable for local use within this process.
+     * <p>
+     * The returned object is intended to be returned to a remote caller for
+     * actual use, where {@link Attributable#setAttributionSource} will populate
+     * it accurately.
+     */
+    protected BluetoothDevice getAnonymousDevice(byte[] address) {
+        return Attributable.setAttributionSource(
+                BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address), null);
     }
 }
diff --git a/src/com/android/bluetooth/btservice/RemoteDevices.java b/src/com/android/bluetooth/btservice/RemoteDevices.java
index 1dcb599..34cbba7 100644
--- a/src/com/android/bluetooth/btservice/RemoteDevices.java
+++ b/src/com/android/bluetooth/btservice/RemoteDevices.java
@@ -16,20 +16,28 @@
 
 package com.android.bluetooth.btservice;
 
+import static android.Manifest.permission.BLUETOOTH_CONNECT;
+import static android.Manifest.permission.BLUETOOTH_SCAN;
+
+import android.app.ActivityThread;
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothAssignedNumbers;
 import android.bluetooth.BluetoothClass;
 import android.bluetooth.BluetoothDevice;
 import android.bluetooth.BluetoothHeadset;
 import android.bluetooth.BluetoothProfile;
+import android.bluetooth.IBluetoothConnectionCallback;
+import android.content.Attributable;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.net.MacAddress;
 import android.os.Handler;
 import android.os.Looper;
 import android.os.Message;
 import android.os.ParcelUuid;
+import android.os.RemoteException;
 import android.util.Log;
 
 import com.android.bluetooth.BluetoothStatsLog;
@@ -44,6 +52,7 @@
 import java.util.LinkedList;
 import java.util.Queue;
 import java.util.Set;
+import java.util.function.Predicate;
 
 final class RemoteDevices {
     private static final boolean DBG = false;
@@ -79,6 +88,8 @@
             switch (msg.what) {
                 case MESSAGE_UUID_INTENT:
                     BluetoothDevice device = (BluetoothDevice) msg.obj;
+                    Attributable.setAttributionSource(device,
+                            ActivityThread.currentAttributionSource());
                     if (device != null) {
                         DeviceProperties prop = getDeviceProperties(device);
                         sendUuidIntent(device, prop);
@@ -109,6 +120,24 @@
         }
     };
 
+    /**
+     * Predicate that tests if the given {@link BluetoothDevice} is well-known
+     * to be used for physical location.
+     */
+    private final Predicate<BluetoothDevice> mLocationDenylistPredicate = (device) -> {
+        final MacAddress parsedAddress = MacAddress.fromString(device.getAddress());
+        if (sAdapterService.getLocationDenylistMac().test(parsedAddress.toByteArray())) {
+            Log.v(TAG, "Skipping device matching denylist: " + parsedAddress);
+            return true;
+        }
+        final String name = Utils.getName(device);
+        if (sAdapterService.getLocationDenylistName().test(name)) {
+            Log.v(TAG, "Skipping name matching denylist: " + name);
+            return true;
+        }
+        return false;
+    };
+
     RemoteDevices(AdapterService service, Looper looper) {
         sAdapter = BluetoothAdapter.getDefaultAdapter();
         sAdapterService = service;
@@ -304,7 +333,8 @@
                 Intent intent = new Intent(BluetoothDevice.ACTION_ALIAS_CHANGED);
                 intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
                 intent.putExtra(BluetoothDevice.EXTRA_NAME, mAlias);
-                sAdapterService.sendBroadcast(intent, AdapterService.BLUETOOTH_PERM);
+                sAdapterService.sendBroadcast(intent, BLUETOOTH_CONNECT,
+                        Utils.getTempAllowlistBroadcastOptions());
             }
         }
 
@@ -382,7 +412,8 @@
         Intent intent = new Intent(BluetoothDevice.ACTION_UUID);
         intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
         intent.putExtra(BluetoothDevice.EXTRA_UUID, prop == null ? null : prop.mUuids);
-        sAdapterService.sendBroadcast(intent, AdapterService.BLUETOOTH_ADMIN_PERM);
+        sAdapterService.sendBroadcast(intent, BLUETOOTH_CONNECT,
+                Utils.getTempAllowlistBroadcastOptions());
 
         //Remove the outstanding UUID request
         sSdpTracker.remove(device);
@@ -467,7 +498,8 @@
         intent.putExtra(BluetoothDevice.EXTRA_BATTERY_LEVEL, batteryLevel);
         intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
         intent.addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
-        sAdapterService.sendBroadcast(intent, AdapterService.BLUETOOTH_PERM);
+        sAdapterService.sendBroadcast(intent, BLUETOOTH_CONNECT,
+                Utils.getTempAllowlistBroadcastOptions());
     }
 
     private static boolean areUuidsEqual(ParcelUuid[] uuids1, ParcelUuid[] uuids2) {
@@ -523,7 +555,8 @@
                             intent.putExtra(BluetoothDevice.EXTRA_DEVICE, bdDevice);
                             intent.putExtra(BluetoothDevice.EXTRA_NAME, device.mName);
                             intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
-                            sAdapterService.sendBroadcast(intent, sAdapterService.BLUETOOTH_PERM);
+                            sAdapterService.sendBroadcast(intent, BLUETOOTH_CONNECT,
+                                    Utils.getTempAllowlistBroadcastOptions());
                             debugLog("Remote Device name is: " + device.mName);
                             break;
                         case AbstractionLayer.BT_PROPERTY_REMOTE_FRIENDLY_NAME:
@@ -546,7 +579,8 @@
                             intent.putExtra(BluetoothDevice.EXTRA_CLASS,
                                     new BluetoothClass(device.mBluetoothClass));
                             intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
-                            sAdapterService.sendBroadcast(intent, sAdapterService.BLUETOOTH_PERM);
+                            sAdapterService.sendBroadcast(intent, BLUETOOTH_CONNECT,
+                                    Utils.getTempAllowlistBroadcastOptions());
                             debugLog("Remote class is:" + device.mBluetoothClass);
                             break;
                         case AbstractionLayer.BT_PROPERTY_UUIDS:
@@ -598,15 +632,28 @@
         final ArrayList<DiscoveringPackage> packages = sAdapterService.getDiscoveringPackages();
         synchronized (packages) {
             for (DiscoveringPackage pkg : packages) {
+                if (pkg.hasDisavowedLocation()) {
+                    if (mLocationDenylistPredicate.test(device)) {
+                        continue;
+                    }
+                }
+
                 intent.setPackage(pkg.getPackageName());
-                sAdapterService.sendBroadcastMultiplePermissions(intent, new String[]{
-                        AdapterService.BLUETOOTH_PERM, pkg.getPermission()
-                });
+
+                if (pkg.getPermission() != null) {
+                    sAdapterService.sendBroadcastMultiplePermissions(intent,
+                            new String[] { BLUETOOTH_SCAN, pkg.getPermission() },
+                            Utils.getTempAllowlistBroadcastOptions());
+                } else {
+                    sAdapterService.sendBroadcastMultiplePermissions(intent,
+                            new String[] { BLUETOOTH_SCAN },
+                            Utils.getTempAllowlistBroadcastOptions());
+                }
             }
         }
     }
 
-    void aclStateChangeCallback(int status, byte[] address, int newState) {
+    void aclStateChangeCallback(int status, byte[] address, int newState, int hciReason) {
         BluetoothDevice device = getDevice(address);
 
         if (device == null) {
@@ -633,7 +680,8 @@
                 intent = new Intent(BluetoothDevice.ACTION_PAIRING_CANCEL);
                 intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
                 intent.setPackage(sAdapterService.getString(R.string.pairing_ui_package));
-                sAdapterService.sendBroadcast(intent, sAdapterService.BLUETOOTH_PERM);
+                sAdapterService.sendBroadcast(intent, BLUETOOTH_CONNECT,
+                        Utils.getTempAllowlistBroadcastOptions());
             }
             if (state == BluetoothAdapter.STATE_ON || state == BluetoothAdapter.STATE_TURNING_OFF) {
                 intent = new Intent(BluetoothDevice.ACTION_ACL_DISCONNECTED);
@@ -665,7 +713,25 @@
             intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT
                     | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
             intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
-            sAdapterService.sendBroadcast(intent, sAdapterService.BLUETOOTH_PERM);
+            sAdapterService.sendBroadcast(intent, BLUETOOTH_CONNECT,
+                    Utils.getTempAllowlistBroadcastOptions());
+
+            synchronized (sAdapterService.getBluetoothConnectionCallbacks()) {
+                Set<IBluetoothConnectionCallback> bluetoothConnectionCallbacks =
+                        sAdapterService.getBluetoothConnectionCallbacks();
+                for (IBluetoothConnectionCallback callback : bluetoothConnectionCallbacks) {
+                    try {
+                        if (connectionState == BluetoothAdapter.STATE_CONNECTED) {
+                            callback.onDeviceConnected(device);
+                        } else {
+                            callback.onDeviceDisconnected(device,
+                                    AdapterService.hciToAndroidDisconnectReason(hciReason));
+                        }
+                    } catch (RemoteException ex) {
+                        Log.e(TAG, "RemoteException in calling IBluetoothConnectionCallback");
+                    }
+                }
+            }
         } else {
             Log.e(TAG, "aclStateChangeCallback intent is null. deviceBondState: "
                     + device.getBondState());
@@ -677,13 +743,24 @@
         if (sSdpTracker.contains(device)) {
             return;
         }
+
+        // If no UUIDs are cached and the device is bonding, wait for SDP after the device is bonded
+        DeviceProperties deviceProperties = getDeviceProperties(device);
+        if (deviceProperties != null && deviceProperties.isBonding()
+                && getDeviceProperties(device).getUuids() == null) {
+            return;
+        }
+
         sSdpTracker.add(device);
 
         Message message = mHandler.obtainMessage(MESSAGE_UUID_INTENT);
         message.obj = device;
         mHandler.sendMessageDelayed(message, UUID_INTENT_DELAY);
 
-        sAdapterService.getRemoteServicesNative(Utils.getBytesFromAddress(device.getAddress()));
+        // Uses cached UUIDs if we are bonding. If not, we fetch the UUIDs with SDP.
+        if (deviceProperties == null || !deviceProperties.isBonding()) {
+            sAdapterService.getRemoteServicesNative(Utils.getBytesFromAddress(device.getAddress()));
+        }
     }
 
     void updateUuids(BluetoothDevice device) {
diff --git a/src/com/android/bluetooth/btservice/SilenceDeviceManager.java b/src/com/android/bluetooth/btservice/SilenceDeviceManager.java
index e8ec235..0df8119 100644
--- a/src/com/android/bluetooth/btservice/SilenceDeviceManager.java
+++ b/src/com/android/bluetooth/btservice/SilenceDeviceManager.java
@@ -16,10 +16,15 @@
 
 package com.android.bluetooth.btservice;
 
+import static android.Manifest.permission.BLUETOOTH_CONNECT;
+
+import android.annotation.RequiresPermission;
+import android.app.ActivityThread;
 import android.bluetooth.BluetoothA2dp;
 import android.bluetooth.BluetoothDevice;
 import android.bluetooth.BluetoothHeadset;
 import android.bluetooth.BluetoothProfile;
+import android.content.Attributable;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
@@ -30,6 +35,7 @@
 import android.os.UserHandle;
 import android.util.Log;
 
+import com.android.bluetooth.Utils;
 import com.android.bluetooth.a2dp.A2dpService;
 import com.android.bluetooth.hfp.HeadsetService;
 import com.android.internal.annotations.VisibleForTesting;
@@ -123,6 +129,8 @@
             switch (msg.what) {
                 case MSG_SILENCE_DEVICE_STATE_CHANGED: {
                     BluetoothDevice device = (BluetoothDevice) msg.obj;
+                    Attributable.setAttributionSource(device,
+                            ActivityThread.currentAttributionSource());
                     boolean state = (msg.arg1 == ENABLE_SILENCE);
                     handleSilenceDeviceStateChanged(device, state);
                 }
@@ -246,6 +254,7 @@
         return true;
     }
 
+    @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
     void handleSilenceDeviceStateChanged(BluetoothDevice device, boolean state) {
         boolean oldState = getSilenceMode(device);
         if (oldState == state) {
@@ -278,7 +287,8 @@
     void broadcastSilenceStateChange(BluetoothDevice device, boolean state) {
         Intent intent = new Intent(BluetoothDevice.ACTION_SILENCE_MODE_CHANGED);
         intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
-        mAdapterService.sendBroadcastAsUser(intent, UserHandle.ALL, AdapterService.BLUETOOTH_PERM);
+        mAdapterService.sendBroadcastAsUser(intent, UserHandle.ALL, BLUETOOTH_CONNECT,
+                Utils.getTempAllowlistBroadcastOptions());
 
     }
 
diff --git a/src/com/android/bluetooth/btservice/activityAttribution/ActivityAttributionNativeInterface.java b/src/com/android/bluetooth/btservice/activityAttribution/ActivityAttributionNativeInterface.java
new file mode 100644
index 0000000..542398a
--- /dev/null
+++ b/src/com/android/bluetooth/btservice/activityAttribution/ActivityAttributionNativeInterface.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * Defines the native interface that is used by state machine/service to
+ * send or receive messages from the native stack. This file is registered
+ * for the native methods in the corresponding JNI C++ file.
+ */
+package com.android.bluetooth.btservice.activityattribution;
+
+import android.util.Log;
+
+import com.android.internal.annotations.GuardedBy;
+
+/** ActivityAttribution Native Interface to/from JNI. */
+public class ActivityAttributionNativeInterface {
+    private static final boolean DBG = false;
+    private static final String TAG = "ActivityAttributionNativeInterface";
+
+    @GuardedBy("INSTANCE_LOCK")
+    private static ActivityAttributionNativeInterface sInstance;
+
+    private static final Object INSTANCE_LOCK = new Object();
+
+    static {
+        classInitNative();
+    }
+
+    /** Get singleton instance. */
+    public static ActivityAttributionNativeInterface getInstance() {
+        synchronized (INSTANCE_LOCK) {
+            if (sInstance == null) {
+                sInstance = new ActivityAttributionNativeInterface();
+            }
+            return sInstance;
+        }
+    }
+
+    /** Initializes the native interface. */
+    public void init() {
+        initNative();
+    }
+
+    /** Cleanup the native interface. */
+    public void cleanup() {
+        cleanupNative();
+    }
+
+    // Callbacks from the native stack back into the Java framework.
+    // All callbacks are routed via the Service which will disambiguate which
+    // state machine the message should be routed to.
+
+    private void onWakeup(int activity, byte[] address) {
+        Log.i(TAG, "onWakeup() BTAA: " + activity);
+    }
+
+    private void onActivityLogsReady(byte[] logs) {
+        Log.i(TAG, "onActivityLogsReady() BTAA: " + logs);
+    }
+
+    // Native methods that call into the JNI interface
+    private static native void classInitNative();
+
+    private native void initNative();
+
+    private native void cleanupNative();
+}
diff --git a/src/com/android/bluetooth/btservice/activityAttribution/ActivityAttributionService.java b/src/com/android/bluetooth/btservice/activityAttribution/ActivityAttributionService.java
new file mode 100644
index 0000000..e09f482
--- /dev/null
+++ b/src/com/android/bluetooth/btservice/activityAttribution/ActivityAttributionService.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.bluetooth.btservice.activityattribution;
+
+import android.util.Log;
+
+import java.util.Objects;
+
+/**
+ * Service used for attributes wakeup, wakelock and Bluetooth traffic into per-app and per-device
+ * based activities.
+ */
+public class ActivityAttributionService {
+    private boolean mCleaningUp;
+    private static ActivityAttributionService sActivityAttributionService;
+    private static final boolean DBG = false;
+    private static final String TAG = "ActivityAttributionService";
+
+    ActivityAttributionNativeInterface mActivityAttributionNativeInterface;
+
+    /** Start and initialize the Activity Attribution service. */
+    public void start() {
+        debugLog("start()");
+
+        if (sActivityAttributionService != null) {
+            Log.e(TAG, "start() called twice");
+            return;
+        }
+
+        mActivityAttributionNativeInterface =
+                Objects.requireNonNull(
+                        ActivityAttributionNativeInterface.getInstance(),
+                        "ActivityAttributionNativeInterface "
+                                + "cannot be null when ActivityAttributionService starts");
+
+        // Mark service as started
+        setActivityAttributionService(this);
+    }
+
+    /** Cleans up the Activity Attribution service. */
+    public void cleanup() {
+        debugLog("cleanup");
+        if (mCleaningUp) {
+            debugLog("already doing cleanup");
+            return;
+        }
+
+        mCleaningUp = true;
+
+        if (sActivityAttributionService == null) {
+            debugLog("cleanup() called before start()");
+            return;
+        }
+
+        // Mark service as stopped
+        setActivityAttributionService(null);
+
+        // Cleanup native interface
+        mActivityAttributionNativeInterface.cleanup();
+        mActivityAttributionNativeInterface = null;
+    }
+
+    /** Get the ActivityAttributionService instance */
+    public static synchronized ActivityAttributionService getActivityAttributionService() {
+        if (sActivityAttributionService == null) {
+            Log.w(TAG, "getActivityAttributionService(): service is NULL");
+            return null;
+        }
+
+        if (!sActivityAttributionService.isAvailable()) {
+            Log.w(TAG, "getActivityAttributionService(): service is not available");
+            return null;
+        }
+        return sActivityAttributionService;
+    }
+
+    /** Init JNI */
+    public void initJni() {
+        debugLog("initJni()");
+        // Initialize native interface
+        mActivityAttributionNativeInterface.init();
+    }
+
+    private boolean isAvailable() {
+        return !mCleaningUp;
+    }
+
+    private static synchronized void setActivityAttributionService(
+            ActivityAttributionService instance) {
+        debugLog("setActivityAttributionService(): set to: " + instance);
+        sActivityAttributionService = instance;
+    }
+
+    private static void debugLog(String msg) {
+        if (DBG) {
+            Log.d(TAG, msg);
+        }
+    }
+}
diff --git a/src/com/android/bluetooth/btservice/bluetoothKeystore/BluetoothKeystoreService.java b/src/com/android/bluetooth/btservice/bluetoothKeystore/BluetoothKeystoreService.java
index f25f367..df40e35 100644
--- a/src/com/android/bluetooth/btservice/bluetoothKeystore/BluetoothKeystoreService.java
+++ b/src/com/android/bluetooth/btservice/bluetoothKeystore/BluetoothKeystoreService.java
@@ -17,9 +17,7 @@
 package com.android.bluetooth.btservice.bluetoothkeystore;
 
 import android.annotation.Nullable;
-import android.os.Process;
 import android.os.SystemProperties;
-import android.security.keystore.AndroidKeyStoreProvider;
 import android.security.keystore.KeyGenParameterSpec;
 import android.security.keystore.KeyProperties;
 import android.util.Log;
@@ -43,6 +41,7 @@
 import java.security.NoSuchProviderException;
 import java.security.ProviderException;
 import java.security.UnrecoverableEntryException;
+import java.security.cert.CertificateException;
 import java.util.Base64;
 import java.util.HashMap;
 import java.util.LinkedList;
@@ -70,7 +69,7 @@
 
     private static BluetoothKeystoreService sBluetoothKeystoreService;
     private boolean mCleaningUp;
-    private boolean mIsNiapMode;
+    private boolean mIsCommonCriteriaMode;
 
     private static final String CIPHER_ALGORITHM = "AES/GCM/NoPadding";
     private static final int GCM_TAG_LENGTH = 128;
@@ -119,9 +118,9 @@
     private Base64.Decoder mDecoder = Base64.getDecoder();
     private Base64.Encoder mEncoder = Base64.getEncoder();
 
-    public BluetoothKeystoreService(boolean isNiapMode) {
-        debugLog("new BluetoothKeystoreService isNiapMode: " + isNiapMode);
-        mIsNiapMode = isNiapMode;
+    public BluetoothKeystoreService(boolean isCommonCriteriaMode) {
+        debugLog("new BluetoothKeystoreService isCommonCriteriaMode: " + isCommonCriteriaMode);
+        mIsCommonCriteriaMode = isCommonCriteriaMode;
         mCompareResult = CONFIG_COMPARE_INIT;
         startThread();
     }
@@ -140,7 +139,7 @@
 
         keyStore = getKeyStore();
 
-        // Confirm whether to enable NIAP for the first time.
+        // Confirm whether to enable Common Criteria mode for the first time.
         if (keyStore == null) {
             debugLog("cannot find the keystore.");
             return;
@@ -154,8 +153,8 @@
         setBluetoothKeystoreService(this);
 
         try {
-            if (!keyStore.containsAlias(KEYALIAS) && mIsNiapMode) {
-                infoLog("Enable NIAP mode for the first time, pass hash check.");
+            if (!keyStore.containsAlias(KEYALIAS) && mIsCommonCriteriaMode) {
+                infoLog("Enable Common Criteria mode for the first time, pass hash check.");
                 mCompareResult = 0b11;
                 return;
             }
@@ -201,19 +200,20 @@
         mBluetoothKeystoreNativeInterface.cleanup();
         mBluetoothKeystoreNativeInterface = null;
 
-        if (mIsNiapMode) {
-            cleanupForNiapModeEnable();
+        if (mIsCommonCriteriaMode) {
+            cleanupForCommonCriteriaModeEnable();
         } else {
-            cleanupForNiapModeDisable();
+            cleanupForCommonCriteriaModeDisable();
         }
     }
 
     /**
-     * Clean up if NIAP mode is enabled.
+     * Clean up if Common Criteria mode is enabled.
      */
     @VisibleForTesting
-    public void cleanupForNiapModeEnable() {
+    public void cleanupForCommonCriteriaModeEnable() {
         try {
+            Thread.sleep(100);
             setEncryptKeyOrRemoveKey(CONFIG_FILE_PREFIX, CONFIG_FILE_HASH);
         } catch (InterruptedException e) {
             reportBluetoothKeystoreException(e, "Interrupted while operating.");
@@ -227,10 +227,10 @@
     }
 
     /**
-     * Clean up if NIAP mode is disabled.
+     * Clean up if Common Criteria mode is disabled.
      */
     @VisibleForTesting
-    public void cleanupForNiapModeDisable() {
+    public void cleanupForCommonCriteriaModeDisable() {
         mNameDecryptKey.clear();
         mNameEncryptKey.clear();
     }
@@ -268,16 +268,16 @@
                     mNameEncryptKey.remove(CONFIG_FILE_PREFIX);
                     loadEncryptionFile(CONFIG_BACKUP_ENCRYPTION_PATH, true);
                 } else {
-                    // if the NIAP mode is disable, don't show the log.
-                    if (mIsNiapMode) {
+                    // if the Common Criteria mode is disable, don't show the log.
+                    if (mIsCommonCriteriaMode) {
                         debugLog("Config file conf and bak checksum check fail.");
                     }
                     cleanupAll();
                     return;
                 }
             }
-            // keep memory data for get decrypted key if NIAP mode disable.
-            if (!mIsNiapMode) {
+            // keep memory data for get decrypted key if Common Criteria mode disable.
+            if (!mIsCommonCriteriaMode) {
                 stopThread();
                 cleanupFile();
             }
@@ -299,8 +299,13 @@
      */
     public void initJni() {
         debugLog("initJni()");
+        // Need to make sure all keys are decrypted.
+        stopThread();
+        startThread();
         // Initialize native interface
-        mBluetoothKeystoreNativeInterface.init();
+        if (mBluetoothKeystoreNativeInterface != null) {
+            mBluetoothKeystoreNativeInterface.init();
+        }
     }
 
     private boolean isAvailable() {
@@ -521,7 +526,7 @@
      */
     @VisibleForTesting
     public boolean compareFileHash(String hashFilePathString)
-            throws IOException, NoSuchAlgorithmException {
+            throws InterruptedException, IOException, NoSuchAlgorithmException {
         if (!Files.exists(Paths.get(hashFilePathString))) {
             infoLog("compareFileHash: File does not exist, path: " + hashFilePathString);
             return false;
@@ -556,25 +561,37 @@
     }
 
     private void readHashFile(String filePathString, String prefixString)
-            throws IOException, NoSuchAlgorithmException {
+            throws InterruptedException, NoSuchAlgorithmException {
         byte[] dataBuffer = new byte[BUFFER_SIZE];
         int bytesRead  = 0;
+        boolean successful = false;
+        int counter = 0;
+        while (!successful && counter < TRY_MAX) {
+            try {
+                MessageDigest messageDigest = MessageDigest.getInstance("SHA-256");
+                InputStream fileStream = Files.newInputStream(Paths.get(filePathString));
+                while ((bytesRead = fileStream.read(dataBuffer)) != -1) {
+                    messageDigest.update(dataBuffer, 0, bytesRead);
+                }
 
-        MessageDigest messageDigest = MessageDigest.getInstance("SHA-256");
-        InputStream fileStream = Files.newInputStream(Paths.get(filePathString));
+                byte[] messageDigestBytes = messageDigest.digest();
+                StringBuffer hashString = new StringBuffer();
+                for (int index = 0; index < messageDigestBytes.length; index++) {
+                    hashString.append(Integer.toString((
+                            messageDigestBytes[index] & 0xff) + 0x100, 16).substring(1));
+                }
 
-        while ((bytesRead = fileStream.read(dataBuffer)) != -1) {
-            messageDigest.update(dataBuffer, 0, bytesRead);
+                mNameDecryptKey.put(prefixString, hashString.toString());
+                successful = true;
+            } catch (IOException e) {
+                infoLog("Fail to open file, try again. counter: " + counter);
+                Thread.sleep(50);
+                counter++;
+            }
         }
-
-        byte[] messageDigestBytes = messageDigest.digest();
-        StringBuffer hashString = new StringBuffer();
-        for (int index = 0; index < messageDigestBytes.length; index++) {
-            hashString.append(Integer.toString((
-                    messageDigestBytes[index] & 0xff) + 0x100, 16).substring(1));
+        if (counter > 3) {
+            errorLog("Fail to open file");
         }
-
-        mNameDecryptKey.put(prefixString, hashString.toString());
     }
 
     private void readChecksumFile(String filePathString, String prefixString) throws IOException {
@@ -786,18 +803,19 @@
 
         while ((counter <= TRY_MAX) && (keyStore == null)) {
             try {
-                keyStore = AndroidKeyStoreProvider.getKeyStoreForUid(Process.BLUETOOTH_UID);
-            } catch (NoSuchProviderException e) {
-                reportKeystoreException(e, "cannot find crypto provider");
-            } catch (KeyStoreException e) {
-                reportKeystoreException(e, "cannot find the keystore");
+                keyStore = KeyStore.getInstance("AndroidKeyStore");
+                keyStore.load(null);
+            } catch (KeyStoreException | CertificateException | NoSuchAlgorithmException
+                    | IOException e) {
+                reportKeystoreException(e, "cannot open keystore");
             }
             counter++;
         }
         return keyStore;
     }
 
-    private SecretKey getOrCreateSecretKey() {
+    // The getOrGenerate semantic on keystore is not thread safe, need to synchronized it.
+    private synchronized SecretKey getOrCreateSecretKey() {
         SecretKey secretKey = null;
         try {
             KeyStore keyStore = getKeyStore();
@@ -820,7 +838,6 @@
                         .setBlockModes(KeyProperties.BLOCK_MODE_GCM)
                         .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
                         .setKeySize(KEY_LENGTH)
-                        .setUid(Process.BLUETOOTH_UID)
                         .build();
 
                 keyGenerator.init(keyGenParameterSpec);
diff --git a/src/com/android/bluetooth/btservice/storage/CustomizedMetadataEntity.java b/src/com/android/bluetooth/btservice/storage/CustomizedMetadataEntity.java
index e1033b9..2bb71ef 100644
--- a/src/com/android/bluetooth/btservice/storage/CustomizedMetadataEntity.java
+++ b/src/com/android/bluetooth/btservice/storage/CustomizedMetadataEntity.java
@@ -37,6 +37,13 @@
     public byte[] untethered_right_charging;
     public byte[] untethered_case_charging;
     public byte[] enhanced_settings_ui_uri;
+    public byte[] device_type;
+    public byte[] main_battery;
+    public byte[] main_charging;
+    public byte[] main_low_battery_threshold;
+    public byte[] untethered_left_low_battery_threshold;
+    public byte[] untethered_right_low_battery_threshold;
+    public byte[] untethered_case_low_battery_threshold;
 
     public String toString() {
         StringBuilder builder = new StringBuilder();
@@ -73,7 +80,21 @@
                 .append("|untethered_case_charging=")
                 .append(metadataToString(untethered_case_charging))
                 .append("|enhanced_settings_ui_uri=")
-                .append(metadataToString(enhanced_settings_ui_uri));
+                .append(metadataToString(enhanced_settings_ui_uri))
+                .append("|device_type=")
+                .append(metadataToString(device_type))
+                .append("|main_battery=")
+                .append(metadataToString(main_battery))
+                .append("|main_charging=")
+                .append(metadataToString(main_charging))
+                .append("|main_low_battery_threshold=")
+                .append(metadataToString(main_low_battery_threshold))
+                .append("|untethered_left_low_battery_threshold=")
+                .append(metadataToString(untethered_left_low_battery_threshold))
+                .append("|untethered_right_low_battery_threshold=")
+                .append(metadataToString(untethered_right_low_battery_threshold))
+                .append("|untethered_case_low_battery_threshold=")
+                .append(metadataToString(untethered_case_low_battery_threshold));
 
         return builder.toString();
     }
diff --git a/src/com/android/bluetooth/btservice/storage/DatabaseManager.java b/src/com/android/bluetooth/btservice/storage/DatabaseManager.java
index 68dc7a3..f644371 100644
--- a/src/com/android/bluetooth/btservice/storage/DatabaseManager.java
+++ b/src/com/android/bluetooth/btservice/storage/DatabaseManager.java
@@ -23,6 +23,7 @@
 import android.bluetooth.BluetoothDevice;
 import android.bluetooth.BluetoothProfile;
 import android.bluetooth.BluetoothProtoEnums;
+import android.content.AttributionSource;
 import android.content.BroadcastReceiver;
 import android.content.ContentResolver;
 import android.content.Context;
@@ -214,25 +215,8 @@
     }
 
     boolean isValidMetaKey(int key) {
-        switch (key) {
-            case BluetoothDevice.METADATA_MANUFACTURER_NAME:
-            case BluetoothDevice.METADATA_MODEL_NAME:
-            case BluetoothDevice.METADATA_SOFTWARE_VERSION:
-            case BluetoothDevice.METADATA_HARDWARE_VERSION:
-            case BluetoothDevice.METADATA_COMPANION_APP:
-            case BluetoothDevice.METADATA_MAIN_ICON:
-            case BluetoothDevice.METADATA_IS_UNTETHERED_HEADSET:
-            case BluetoothDevice.METADATA_UNTETHERED_LEFT_ICON:
-            case BluetoothDevice.METADATA_UNTETHERED_RIGHT_ICON:
-            case BluetoothDevice.METADATA_UNTETHERED_CASE_ICON:
-            case BluetoothDevice.METADATA_UNTETHERED_LEFT_BATTERY:
-            case BluetoothDevice.METADATA_UNTETHERED_RIGHT_BATTERY:
-            case BluetoothDevice.METADATA_UNTETHERED_CASE_BATTERY:
-            case BluetoothDevice.METADATA_UNTETHERED_LEFT_CHARGING:
-            case BluetoothDevice.METADATA_UNTETHERED_RIGHT_CHARGING:
-            case BluetoothDevice.METADATA_UNTETHERED_CASE_CHARGING:
-            case BluetoothDevice.METADATA_ENHANCED_SETTINGS_UI_URI:
-                return true;
+        if (key >= 0 && key <= BluetoothDevice.getMaxMetadataKey()) {
+            return true;
         }
         Log.w(TAG, "Invalid metadata key " + key);
         return false;
@@ -310,7 +294,7 @@
      * {@link BluetoothProfile#PAN}, {@link BluetoothProfile#PBAP},
      * {@link BluetoothProfile#PBAP_CLIENT}, {@link BluetoothProfile#MAP},
      * {@link BluetoothProfile#MAP_CLIENT}, {@link BluetoothProfile#SAP},
-     * {@link BluetoothProfile#HEARING_AID}
+     * {@link BluetoothProfile#HEARING_AID}, {@link BluetoothProfile#LE_AUDIO}
      * @param newConnectionPolicy the connectionPolicy to set; one of
      * {@link BluetoothProfile.CONNECTION_POLICY_UNKNOWN},
      * {@link BluetoothProfile.CONNECTION_POLICY_FORBIDDEN},
@@ -366,7 +350,7 @@
      * {@link BluetoothProfile#PAN}, {@link BluetoothProfile#PBAP},
      * {@link BluetoothProfile#PBAP_CLIENT}, {@link BluetoothProfile#MAP},
      * {@link BluetoothProfile#MAP_CLIENT}, {@link BluetoothProfile#SAP},
-     * {@link BluetoothProfile#HEARING_AID}
+     * {@link BluetoothProfile#HEARING_AID}, {@link BluetoothProfile#LE_AUDIO}
      * @return the profile connection policy of the device; one of
      * {@link BluetoothProfile.CONNECTION_POLICY_UNKNOWN},
      * {@link BluetoothProfile.CONNECTION_POLICY_FORBIDDEN},
@@ -889,6 +873,8 @@
             data.setProfileConnectionPolicy(BluetoothProfile.SAP, sapConnectionPolicy);
             data.setProfileConnectionPolicy(BluetoothProfile.HEARING_AID,
                     hearingaidConnectionPolicy);
+            data.setProfileConnectionPolicy(BluetoothProfile.LE_AUDIO,
+                    BluetoothProfile.CONNECTION_POLICY_UNKNOWN);
             data.a2dpSupportsOptionalCodecs = a2dpSupportsOptionalCodec;
             data.a2dpOptionalCodecsEnabled = a2dpOptionalCodecEnabled;
             mMetadataCache.put(address, data);
diff --git a/src/com/android/bluetooth/btservice/storage/Metadata.java b/src/com/android/bluetooth/btservice/storage/Metadata.java
index 43f618a..85c2266 100644
--- a/src/com/android/bluetooth/btservice/storage/Metadata.java
+++ b/src/com/android/bluetooth/btservice/storage/Metadata.java
@@ -110,6 +110,9 @@
             case BluetoothProfile.HEARING_AID:
                 profileConnectionPolicies.hearing_aid_connection_policy = connectionPolicy;
                 break;
+            case BluetoothProfile.LE_AUDIO:
+                profileConnectionPolicies.le_audio_connection_policy = connectionPolicy;
+                break;
             default:
                 throw new IllegalArgumentException("invalid profile " + profile);
         }
@@ -141,6 +144,8 @@
                 return profileConnectionPolicies.sap_connection_policy;
             case BluetoothProfile.HEARING_AID:
                 return profileConnectionPolicies.hearing_aid_connection_policy;
+            case BluetoothProfile.LE_AUDIO:
+                return profileConnectionPolicies.le_audio_connection_policy;
         }
         return BluetoothProfile.CONNECTION_POLICY_UNKNOWN;
     }
@@ -198,6 +203,27 @@
             case BluetoothDevice.METADATA_ENHANCED_SETTINGS_UI_URI:
                 publicMetadata.enhanced_settings_ui_uri = value;
                 break;
+            case BluetoothDevice.METADATA_DEVICE_TYPE:
+                publicMetadata.device_type = value;
+                break;
+            case BluetoothDevice.METADATA_MAIN_BATTERY:
+                publicMetadata.main_battery = value;
+                break;
+            case BluetoothDevice.METADATA_MAIN_CHARGING:
+                publicMetadata.main_charging = value;
+                break;
+            case BluetoothDevice.METADATA_MAIN_LOW_BATTERY_THRESHOLD:
+                publicMetadata.main_low_battery_threshold = value;
+                break;
+            case BluetoothDevice.METADATA_UNTETHERED_LEFT_LOW_BATTERY_THRESHOLD:
+                publicMetadata.untethered_left_low_battery_threshold = value;
+                break;
+            case BluetoothDevice.METADATA_UNTETHERED_RIGHT_LOW_BATTERY_THRESHOLD:
+                publicMetadata.untethered_right_low_battery_threshold = value;
+                break;
+            case BluetoothDevice.METADATA_UNTETHERED_CASE_LOW_BATTERY_THRESHOLD:
+                publicMetadata.untethered_case_low_battery_threshold = value;
+                break;
         }
     }
 
@@ -255,62 +281,37 @@
             case BluetoothDevice.METADATA_ENHANCED_SETTINGS_UI_URI:
                 value = publicMetadata.enhanced_settings_ui_uri;
                 break;
+            case BluetoothDevice.METADATA_DEVICE_TYPE:
+                value = publicMetadata.device_type;
+                break;
+            case BluetoothDevice.METADATA_MAIN_BATTERY:
+                value = publicMetadata.main_battery;
+                break;
+            case BluetoothDevice.METADATA_MAIN_CHARGING:
+                value = publicMetadata.main_charging;
+                break;
+            case BluetoothDevice.METADATA_MAIN_LOW_BATTERY_THRESHOLD:
+                value = publicMetadata.main_low_battery_threshold;
+                break;
+            case BluetoothDevice.METADATA_UNTETHERED_LEFT_LOW_BATTERY_THRESHOLD:
+                value = publicMetadata.untethered_left_low_battery_threshold;
+                break;
+            case BluetoothDevice.METADATA_UNTETHERED_RIGHT_LOW_BATTERY_THRESHOLD:
+                value = publicMetadata.untethered_right_low_battery_threshold;
+                break;
+            case BluetoothDevice.METADATA_UNTETHERED_CASE_LOW_BATTERY_THRESHOLD:
+                value = publicMetadata.untethered_case_low_battery_threshold;
+                break;
         }
         return value;
     }
 
     List<Integer> getChangedCustomizedMeta() {
         List<Integer> list = new ArrayList<>();
-        if (publicMetadata.manufacturer_name != null) {
-            list.add(BluetoothDevice.METADATA_MANUFACTURER_NAME);
-        }
-        if (publicMetadata.model_name != null) {
-            list.add(BluetoothDevice.METADATA_MODEL_NAME);
-        }
-        if (publicMetadata.software_version != null) {
-            list.add(BluetoothDevice.METADATA_SOFTWARE_VERSION);
-        }
-        if (publicMetadata.hardware_version != null) {
-            list.add(BluetoothDevice.METADATA_HARDWARE_VERSION);
-        }
-        if (publicMetadata.companion_app != null) {
-            list.add(BluetoothDevice.METADATA_COMPANION_APP);
-        }
-        if (publicMetadata.main_icon != null) {
-            list.add(BluetoothDevice.METADATA_MAIN_ICON);
-        }
-        if (publicMetadata.is_untethered_headset != null) {
-            list.add(BluetoothDevice.METADATA_IS_UNTETHERED_HEADSET);
-        }
-        if (publicMetadata.untethered_left_icon != null) {
-            list.add(BluetoothDevice.METADATA_UNTETHERED_LEFT_ICON);
-        }
-        if (publicMetadata.untethered_right_icon != null) {
-            list.add(BluetoothDevice.METADATA_UNTETHERED_RIGHT_ICON);
-        }
-        if (publicMetadata.untethered_case_icon != null) {
-            list.add(BluetoothDevice.METADATA_UNTETHERED_CASE_ICON);
-        }
-        if (publicMetadata.untethered_left_battery != null) {
-            list.add(BluetoothDevice.METADATA_UNTETHERED_LEFT_BATTERY);
-        }
-        if (publicMetadata.untethered_right_battery != null) {
-            list.add(BluetoothDevice.METADATA_UNTETHERED_RIGHT_BATTERY);
-        }
-        if (publicMetadata.untethered_case_battery != null) {
-            list.add(BluetoothDevice.METADATA_UNTETHERED_CASE_BATTERY);
-        }
-        if (publicMetadata.untethered_left_charging != null) {
-            list.add(BluetoothDevice.METADATA_UNTETHERED_LEFT_CHARGING);
-        }
-        if (publicMetadata.untethered_right_charging != null) {
-            list.add(BluetoothDevice.METADATA_UNTETHERED_RIGHT_CHARGING);
-        }
-        if (publicMetadata.untethered_case_charging != null) {
-            list.add(BluetoothDevice.METADATA_UNTETHERED_CASE_CHARGING);
-        }
-        if (publicMetadata.enhanced_settings_ui_uri != null) {
-            list.add(BluetoothDevice.METADATA_ENHANCED_SETTINGS_UI_URI);
+        for (int key = 0; key <= BluetoothDevice.getMaxMetadataKey(); key++) {
+            if (getCustomizedMeta(key) != null) {
+                list.add(key);
+            }
         }
         return list;
     }
diff --git a/src/com/android/bluetooth/btservice/storage/MetadataDatabase.java b/src/com/android/bluetooth/btservice/storage/MetadataDatabase.java
index 888f0b0..2aacb61 100644
--- a/src/com/android/bluetooth/btservice/storage/MetadataDatabase.java
+++ b/src/com/android/bluetooth/btservice/storage/MetadataDatabase.java
@@ -33,7 +33,7 @@
 /**
  * MetadataDatabase is a Room database stores Bluetooth persistence data
  */
-@Database(entities = {Metadata.class}, version = 104)
+@Database(entities = {Metadata.class}, version = 106)
 public abstract class MetadataDatabase extends RoomDatabase {
     /**
      * The metadata database file name
@@ -57,6 +57,8 @@
                 .addMigrations(MIGRATION_101_102)
                 .addMigrations(MIGRATION_102_103)
                 .addMigrations(MIGRATION_103_104)
+                .addMigrations(MIGRATION_104_105)
+                .addMigrations(MIGRATION_105_106)
                 .allowMainThreadQueries()
                 .build();
     }
@@ -303,4 +305,47 @@
             }
         }
     };
+
+    @VisibleForTesting
+    static final Migration MIGRATION_104_105 = new Migration(104, 105) {
+        @Override
+        public void migrate(SupportSQLiteDatabase database) {
+            try {
+                database.execSQL("ALTER TABLE metadata ADD COLUMN `device_type` BLOB");
+                database.execSQL("ALTER TABLE metadata ADD COLUMN `main_battery` BLOB");
+                database.execSQL("ALTER TABLE metadata ADD COLUMN `main_charging` BLOB");
+                database.execSQL("ALTER TABLE metadata ADD COLUMN "
+                        + "`main_low_battery_threshold` BLOB");
+                database.execSQL("ALTER TABLE metadata ADD COLUMN "
+                        + "`untethered_left_low_battery_threshold` BLOB");
+                database.execSQL("ALTER TABLE metadata ADD COLUMN "
+                        + "`untethered_right_low_battery_threshold` BLOB");
+                database.execSQL("ALTER TABLE metadata ADD COLUMN "
+                        + "`untethered_case_low_battery_threshold` BLOB");
+            } 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("device_type") == -1) {
+                    throw ex;
+                }
+            }
+        }
+    };
+
+    @VisibleForTesting
+    static final Migration MIGRATION_105_106 = new Migration(105, 106) {
+        @Override
+        public void migrate(SupportSQLiteDatabase database) {
+            try {
+                database.execSQL("ALTER TABLE metadata ADD COLUMN `le_audio_connection_policy` "
+                        + "INTEGER DEFAULT 100");
+            } 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("le_audio_connection_policy") == -1) {
+                    throw ex;
+                }
+            }
+        }
+    };
 }
diff --git a/src/com/android/bluetooth/btservice/storage/ProfilePrioritiesEntity.java b/src/com/android/bluetooth/btservice/storage/ProfilePrioritiesEntity.java
index 480a859..e578a87 100644
--- a/src/com/android/bluetooth/btservice/storage/ProfilePrioritiesEntity.java
+++ b/src/com/android/bluetooth/btservice/storage/ProfilePrioritiesEntity.java
@@ -35,6 +35,7 @@
     public int sap_connection_policy;
     public int hearing_aid_connection_policy;
     public int map_client_connection_policy;
+    public int le_audio_connection_policy;
 
     ProfilePrioritiesEntity() {
         a2dp_connection_policy = BluetoothProfile.CONNECTION_POLICY_UNKNOWN;
@@ -49,6 +50,7 @@
         sap_connection_policy = BluetoothProfile.CONNECTION_POLICY_UNKNOWN;
         hearing_aid_connection_policy = BluetoothProfile.CONNECTION_POLICY_UNKNOWN;
         map_client_connection_policy = BluetoothProfile.CONNECTION_POLICY_UNKNOWN;
+        le_audio_connection_policy = BluetoothProfile.CONNECTION_POLICY_UNKNOWN;
     }
 
     public String toString() {
@@ -64,7 +66,8 @@
                 .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);
+                .append("|HEARING_AID=").append(hearing_aid_connection_policy)
+                .append("|LE_AUDIO=").append(le_audio_connection_policy);
 
         return builder.toString();
     }
diff --git a/src/com/android/bluetooth/gatt/AdvertiseHelper.java b/src/com/android/bluetooth/gatt/AdvertiseHelper.java
index 1c1b4dc..e971abb 100644
--- a/src/com/android/bluetooth/gatt/AdvertiseHelper.java
+++ b/src/com/android/bluetooth/gatt/AdvertiseHelper.java
@@ -35,7 +35,10 @@
     private static final int SHORTENED_LOCAL_NAME = 0X08;
     private static final int COMPLETE_LOCAL_NAME = 0X09;
     private static final int TX_POWER_LEVEL = 0x0A;
+    private static final int LIST_16_BIT_SERVICE_SOLICITATION_UUIDS = 0X14;
+    private static final int LIST_128_BIT_SERVICE_SOLICITATION_UUIDS = 0X15;
     private static final int SERVICE_DATA_16_BIT_UUID = 0X16;
+    private static final int LIST_32_BIT_SERVICE_SOLICITATION_UUIDS = 0x1F;
     private static final int SERVICE_DATA_32_BIT_UUID = 0X20;
     private static final int SERVICE_DATA_128_BIT_UUID = 0X21;
     private static final int MANUFACTURER_SPECIFIC_DATA = 0XFF;
@@ -166,6 +169,42 @@
             }
         }
 
+
+        if (data.getServiceSolicitationUuids() != null) {
+            ByteArrayOutputStream serviceUuids16 = new ByteArrayOutputStream();
+            ByteArrayOutputStream serviceUuids32 = new ByteArrayOutputStream();
+            ByteArrayOutputStream serviceUuids128 = new ByteArrayOutputStream();
+
+            for (ParcelUuid parcelUuid : data.getServiceSolicitationUuids()) {
+                byte[] uuid = BluetoothUuid.uuidToBytes(parcelUuid);
+
+                if (uuid.length == BluetoothUuid.UUID_BYTES_16_BIT) {
+                    serviceUuids16.write(uuid, 0, uuid.length);
+                } else if (uuid.length == BluetoothUuid.UUID_BYTES_32_BIT) {
+                    serviceUuids32.write(uuid, 0, uuid.length);
+                } else /*if (uuid.length == BluetoothUuid.UUID_BYTES_128_BIT)*/ {
+                    serviceUuids128.write(uuid, 0, uuid.length);
+                }
+            }
+
+            if (serviceUuids16.size() != 0) {
+                ret.write(serviceUuids16.size() + 1);
+                ret.write(LIST_16_BIT_SERVICE_SOLICITATION_UUIDS);
+                ret.write(serviceUuids16.toByteArray(), 0, serviceUuids16.size());
+            }
+
+            if (serviceUuids32.size() != 0) {
+                ret.write(serviceUuids32.size() + 1);
+                ret.write(LIST_32_BIT_SERVICE_SOLICITATION_UUIDS);
+                ret.write(serviceUuids32.toByteArray(), 0, serviceUuids32.size());
+            }
+
+            if (serviceUuids128.size() != 0) {
+                ret.write(serviceUuids128.size() + 1);
+                ret.write(LIST_128_BIT_SERVICE_SOLICITATION_UUIDS);
+                ret.write(serviceUuids128.toByteArray(), 0, serviceUuids128.size());
+            }
+        }
         return ret.toByteArray();
     }
 }
diff --git a/src/com/android/bluetooth/gatt/AppScanStats.java b/src/com/android/bluetooth/gatt/AppScanStats.java
index 4cab832..975d7b6 100644
--- a/src/com/android/bluetooth/gatt/AppScanStats.java
+++ b/src/com/android/bluetooth/gatt/AppScanStats.java
@@ -25,6 +25,7 @@
 
 import com.android.bluetooth.BluetoothMetricsProto;
 import com.android.bluetooth.BluetoothStatsLog;
+import com.android.bluetooth.btservice.AdapterService;
 import com.android.internal.app.IBatteryStats;
 
 import java.text.DateFormat;
@@ -47,8 +48,10 @@
 
     static final DateFormat DATE_FORMAT = new SimpleDateFormat("MM-dd HH:mm:ss");
 
+    // Weight is the duty cycle of the scan mode
     static final int OPPORTUNISTIC_WEIGHT = 0;
     static final int LOW_POWER_WEIGHT = 10;
+    static final int AMBIENT_DISCOVERY_WEIGHT = 20;
     static final int BALANCED_WEIGHT = 25;
     static final int LOW_LATENCY_WEIGHT = 100;
 
@@ -99,16 +102,22 @@
         }
     }
 
-    static final int NUM_SCAN_DURATIONS_KEPT = 5;
+    static int getNumScanDurationsKept() {
+        return AdapterService.getAdapterService().getScanQuotaCount();
+    }
 
     // This constant defines the time window an app can scan multiple times.
     // Any single app can scan up to |NUM_SCAN_DURATIONS_KEPT| times during
     // this window. Once they reach this limit, they must wait until their
     // earliest recorded scan exits this window.
-    static final long EXCESSIVE_SCANNING_PERIOD_MS = 30 * 1000;
+    static long getExcessiveScanningPeriodMillis() {
+        return AdapterService.getAdapterService().getScanQuotaWindowMillis();
+    }
 
     // Maximum msec before scan gets downgraded to opportunistic
-    static final int SCAN_TIMEOUT_MS = 30 * 60 * 1000;
+    static long getScanTimeoutMillis() {
+        return AdapterService.getAdapterService().getScanTimeoutMillis();
+    }
 
     public String appName;
     public WorkSource mWorkSource; // Used for BatteryStats and BluetoothStatsLog
@@ -123,11 +132,13 @@
     private long mLowPowerScanTime = 0;
     private long mBalancedScanTime = 0;
     private long mLowLantencyScanTime = 0;
+    private long mAmbientDiscoveryScanTime = 0;
     private int mOppScan = 0;
     private int mLowPowerScan = 0;
     private int mBalancedScan = 0;
     private int mLowLantencyScan = 0;
-    private List<LastScan> mLastScans = new ArrayList<LastScan>(NUM_SCAN_DURATIONS_KEPT);
+    private int mAmbientDiscoveryScan = 0;
+    private List<LastScan> mLastScans = new ArrayList<LastScan>();
     private HashMap<Integer, LastScan> mOngoingScans = new HashMap<Integer, LastScan>();
     public long startTime = 0;
     public long stopTime = 0;
@@ -206,6 +217,9 @@
                 case ScanSettings.SCAN_MODE_LOW_LATENCY:
                     mLowLantencyScan++;
                     break;
+                case ScanSettings.SCAN_MODE_AMBIENT_DISCOVERY:
+                    mAmbientDiscoveryScan++;
+                    break;
             }
         }
 
@@ -256,7 +270,7 @@
             mTotalSuspendTime += suspendDuration;
         }
         mOngoingScans.remove(scannerId);
-        if (mLastScans.size() >= NUM_SCAN_DURATIONS_KEPT) {
+        if (mLastScans.size() >= getNumScanDurationsKept()) {
             mLastScans.remove(0);
         }
         mLastScans.add(scan);
@@ -287,6 +301,9 @@
             case ScanSettings.SCAN_MODE_LOW_LATENCY:
                 mLowLantencyScanTime += activeDuration;
                 break;
+            case ScanSettings.SCAN_MODE_AMBIENT_DISCOVERY:
+                mAmbientDiscoveryScanTime += activeDuration;
+                break;
         }
 
         try {
@@ -339,19 +356,19 @@
     }
 
     synchronized boolean isScanningTooFrequently() {
-        if (mLastScans.size() < NUM_SCAN_DURATIONS_KEPT) {
+        if (mLastScans.size() < getNumScanDurationsKept()) {
             return false;
         }
 
         return (SystemClock.elapsedRealtime() - mLastScans.get(0).timestamp)
-                < EXCESSIVE_SCANNING_PERIOD_MS;
+                < getExcessiveScanningPeriodMillis();
     }
 
     synchronized boolean isScanningTooLong() {
         if (!isScanning()) {
             return false;
         }
-        return (SystemClock.elapsedRealtime() - mScanStartTime) > SCAN_TIMEOUT_MS;
+        return (SystemClock.elapsedRealtime() - mScanStartTime) > getScanTimeoutMillis();
     }
 
     // This function truncates the app name for privacy reasons. Apps with
@@ -427,6 +444,8 @@
                 return "BALANCED";
             case ScanSettings.SCAN_MODE_LOW_POWER:
                 return "LOW_POWER";
+            case ScanSettings.SCAN_MODE_AMBIENT_DISCOVERY:
+                return "AMBIENT_DISCOVERY";
             default:
                 return "UNKNOWN(" + scanMode + ")";
         }
@@ -461,10 +480,12 @@
         long lowPowerScanTime = mLowPowerScanTime;
         long balancedScanTime = mBalancedScanTime;
         long lowLatencyScanTime = mLowLantencyScanTime;
+        long ambientDiscoveryScanTime = mAmbientDiscoveryScanTime;
         int oppScan = mOppScan;
         int lowPowerScan = mLowPowerScan;
         int balancedScan = mBalancedScan;
         int lowLatencyScan = mLowLantencyScan;
+        int ambientDiscoveryScan = mAmbientDiscoveryScan;
 
         if (!mOngoingScans.isEmpty()) {
             for (Integer key : mOngoingScans.keySet()) {
@@ -493,11 +514,15 @@
                     case ScanSettings.SCAN_MODE_LOW_LATENCY:
                         lowLatencyScanTime += activeDuration;
                         break;
+                    case ScanSettings.SCAN_MODE_AMBIENT_DISCOVERY:
+                        ambientDiscoveryScan += activeDuration;
+                        break;
                 }
             }
         }
         Score = (oppScanTime * OPPORTUNISTIC_WEIGHT + lowPowerScanTime * LOW_POWER_WEIGHT
-              + balancedScanTime * BALANCED_WEIGHT + lowLatencyScanTime * LOW_LATENCY_WEIGHT) / 100;
+              + balancedScanTime * BALANCED_WEIGHT + lowLatencyScanTime * LOW_LATENCY_WEIGHT
+              + ambientDiscoveryScanTime * AMBIENT_DISCOVERY_WEIGHT) / 100;
 
         sb.append("  " + appName);
         if (isRegistered) {
@@ -508,11 +533,13 @@
                 + mScansStarted + " / " + mScansStopped);
         sb.append("\n  Scan time in ms (active/suspend/total)                      : "
                 + totalActiveTime + " / " + totalSuspendTime + " / " + totalScanTime);
-        sb.append("\n  Scan time with mode in ms (Opp/LowPower/Balanced/LowLatency): "
+        sb.append("\n  Scan time with mode in ms "
+                + "(Opp/LowPower/Balanced/LowLatency/AmbientDiscovery):"
                 + oppScanTime + " / " + lowPowerScanTime + " / " + balancedScanTime + " / "
-                + lowLatencyScanTime);
-        sb.append("\n  Scan mode counter (Opp/LowPower/Balanced/LowLatency)        : " + oppScan
-                + " / " + lowPowerScan + " / " + balancedScan + " / " + lowLatencyScan);
+                + lowLatencyScanTime + " / " + ambientDiscoveryScanTime);
+        sb.append("\n  Scan mode counter (Opp/LowPower/Balanced/LowLatency/AmbientDiscovery):"
+                + oppScan + " / " + lowPowerScan + " / " + balancedScan + " / " + lowLatencyScan
+                + " / " + ambientDiscoveryScan);
         sb.append("\n  Score                                                       : " + Score);
         sb.append("\n  Total number of results                                     : " + results);
 
diff --git a/src/com/android/bluetooth/gatt/ContextMap.java b/src/com/android/bluetooth/gatt/ContextMap.java
index 0d82eba..d452532 100644
--- a/src/com/android/bluetooth/gatt/ContextMap.java
+++ b/src/com/android/bluetooth/gatt/ContextMap.java
@@ -107,6 +107,9 @@
         /** Whether the calling app has the network setup wizard permission */
         boolean mHasScanWithoutLocationPermission;
 
+        /** Whether the calling app has disavowed the use of bluetooth for location */
+        boolean mHasDisavowedLocation;
+
         boolean mEligibleForSanitizedExposureNotification;
 
         public List<String> mAssociatedDevices;
@@ -172,10 +175,10 @@
     private List<App> mApps = new ArrayList<App>();
 
     /** Internal map to keep track of logging information by app name */
-    HashMap<Integer, AppScanStats> mAppScanStats = new HashMap<Integer, AppScanStats>();
+    private HashMap<Integer, AppScanStats> mAppScanStats = new HashMap<Integer, AppScanStats>();
 
     /** Internal list of connected devices **/
-    Set<Connection> mConnections = new HashSet<Connection>();
+    private Set<Connection> mConnections = new HashSet<Connection>();
 
     /**
      * Add an entry to the application context list.
@@ -222,12 +225,13 @@
      * Remove the context for a given application ID.
      */
     void remove(int id) {
+        boolean find = false;
         synchronized (mApps) {
             Iterator<App> i = mApps.iterator();
             while (i.hasNext()) {
                 App entry = i.next();
                 if (entry.id == id) {
-                    removeConnectionsByAppId(id);
+                    find = true;
                     entry.unlinkToDeath();
                     entry.appScanStats.isRegistered = false;
                     i.remove();
@@ -235,6 +239,9 @@
                 }
             }
         }
+        if (find) {
+            removeConnectionsByAppId(id);
+        }
     }
 
     List<Integer> getAllAppsIds() {
@@ -281,11 +288,13 @@
      * Remove all connections for a given application ID.
      */
     void removeConnectionsByAppId(int appId) {
-        Iterator<Connection> i = mConnections.iterator();
-        while (i.hasNext()) {
-            Connection connection = i.next();
-            if (connection.appId == appId) {
-                i.remove();
+        synchronized (mConnections) {
+            Iterator<Connection> i = mConnections.iterator();
+            while (i.hasNext()) {
+                Connection connection = i.next();
+                if (connection.appId == appId) {
+                    i.remove();
+                }
             }
         }
     }
@@ -381,10 +390,12 @@
      */
     Set<String> getConnectedDevices() {
         Set<String> addresses = new HashSet<String>();
-        Iterator<Connection> i = mConnections.iterator();
-        while (i.hasNext()) {
-            Connection connection = i.next();
-            addresses.add(connection.address);
+        synchronized (mConnections) {
+            Iterator<Connection> i = mConnections.iterator();
+            while (i.hasNext()) {
+                Connection connection = i.next();
+                addresses.add(connection.address);
+            }
         }
         return addresses;
     }
@@ -393,13 +404,20 @@
      * Get an application context by a connection ID.
      */
     App getByConnId(int connId) {
-        Iterator<Connection> ii = mConnections.iterator();
-        while (ii.hasNext()) {
-            Connection connection = ii.next();
-            if (connection.connId == connId) {
-                return getById(connection.appId);
+        int appId = -1;
+        synchronized (mConnections) {
+            Iterator<Connection> ii = mConnections.iterator();
+            while (ii.hasNext()) {
+                Connection connection = ii.next();
+                if (connection.connId == connId) {
+                    appId = connection.appId;
+                    break;
+                }
             }
         }
+        if (appId >= 0) {
+            return getById(appId);
+        }
         return null;
     }
 
@@ -411,12 +429,13 @@
         if (entry == null) {
             return null;
         }
-
-        Iterator<Connection> i = mConnections.iterator();
-        while (i.hasNext()) {
-            Connection connection = i.next();
-            if (connection.address.equalsIgnoreCase(address) && connection.appId == id) {
-                return connection.connId;
+        synchronized (mConnections) {
+            Iterator<Connection> i = mConnections.iterator();
+            while (i.hasNext()) {
+                Connection connection = i.next();
+                if (connection.address.equalsIgnoreCase(address) && connection.appId == id) {
+                    return connection.connId;
+                }
             }
         }
         return null;
@@ -426,11 +445,13 @@
      * Returns the device address for a given connection ID.
      */
     String addressByConnId(int connId) {
-        Iterator<Connection> i = mConnections.iterator();
-        while (i.hasNext()) {
-            Connection connection = i.next();
-            if (connection.connId == connId) {
-                return connection.address;
+        synchronized (mConnections) {
+            Iterator<Connection> i = mConnections.iterator();
+            while (i.hasNext()) {
+                Connection connection = i.next();
+                if (connection.connId == connId) {
+                    return connection.address;
+                }
             }
         }
         return null;
@@ -438,11 +459,13 @@
 
     List<Connection> getConnectionByApp(int appId) {
         List<Connection> currentConnections = new ArrayList<Connection>();
-        Iterator<Connection> i = mConnections.iterator();
-        while (i.hasNext()) {
-            Connection connection = i.next();
-            if (connection.appId == appId) {
-                currentConnections.add(connection);
+        synchronized (mConnections) {
+            Iterator<Connection> i = mConnections.iterator();
+            while (i.hasNext()) {
+                Connection connection = i.next();
+                if (connection.appId == appId) {
+                    currentConnections.add(connection);
+                }
             }
         }
         return currentConnections;
@@ -472,8 +495,10 @@
      */
     Map<Integer, String> getConnectedMap() {
         Map<Integer, String> connectedmap = new HashMap<Integer, String>();
-        for (Connection conn : mConnections) {
-            connectedmap.put(conn.appId, conn.address);
+        synchronized (mConnections) {
+            for (Connection conn : mConnections) {
+                connectedmap.put(conn.appId, conn.address);
+            }
         }
         return connectedmap;
     }
diff --git a/src/com/android/bluetooth/gatt/GattService.java b/src/com/android/bluetooth/gatt/GattService.java
index 8a792fe..0aaf7f5 100644
--- a/src/com/android/bluetooth/gatt/GattService.java
+++ b/src/com/android/bluetooth/gatt/GattService.java
@@ -18,7 +18,8 @@
 
 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
 
-import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
+import android.annotation.SuppressLint;
 import android.app.AppOpsManager;
 import android.app.PendingIntent;
 import android.app.Service;
@@ -46,17 +47,23 @@
 import android.bluetooth.le.ScanResult;
 import android.bluetooth.le.ScanSettings;
 import android.companion.ICompanionDeviceManager;
+import android.content.AttributionSource;
 import android.content.Context;
 import android.content.Intent;
+import android.net.MacAddress;
 import android.os.Binder;
+import android.os.Handler;
 import android.os.IBinder;
+import android.os.Message;
 import android.os.ParcelUuid;
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.SystemClock;
 import android.os.UserHandle;
 import android.os.WorkSource;
+import android.provider.DeviceConfig;
 import android.provider.Settings;
+import android.text.format.DateUtils;
 import android.util.Log;
 
 import com.android.bluetooth.BluetoothMetricsProto;
@@ -67,6 +74,7 @@
 import com.android.bluetooth.btservice.ProfileService;
 import com.android.bluetooth.util.NumberUtils;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.HexDump;
 
 import java.util.ArrayDeque;
 import java.util.ArrayList;
@@ -79,6 +87,7 @@
 import java.util.Set;
 import java.util.UUID;
 import java.util.concurrent.TimeUnit;
+import java.util.function.Predicate;
 
 /**
  * Provides Bluetooth Gatt profile, as a service in
@@ -89,6 +98,8 @@
     private static final boolean DBG = GattServiceConfig.DBG;
     private static final boolean VDBG = GattServiceConfig.VDBG;
     private static final String TAG = GattServiceConfig.TAG_PREFIX + "GattService";
+    private static final String UUID_SUFFIX = "-0000-1000-8000-00805f9b34fb";
+    private static final String UUID_ZERO_PAD = "00000000";
 
     static final int SCAN_FILTER_ENABLED = 1;
     static final int SCAN_FILTER_MODIFIED = 2;
@@ -98,6 +109,11 @@
     private static final int TRUNCATED_RESULT_SIZE = 11;
     private static final int TIME_STAMP_LENGTH = 2;
 
+    /**
+     * The default floor value for LE batch scan report delays greater than 0
+     */
+    private static final long DEFAULT_REPORT_DELAY_FLOOR = 5000;
+
     // onFoundLost related constants
     private static final int ADVT_STATE_ONFOUND = 0;
     private static final int ADVT_STATE_ONLOST = 1;
@@ -121,6 +137,17 @@
             UUID.fromString("0000FFFD-0000-1000-8000-00805F9B34FB"); // U2F
 
     /**
+     * Example raw beacons captured from a Blue Charm BC011
+     */
+    private static final String[] TEST_MODE_BEACONS = new String[] {
+            "020106",
+            "0201060303AAFE1716AAFE10EE01626C7565636861726D626561636F6E730009168020691E0EFE13551109426C7565436861726D5F313639363835000000",
+            "0201060303AAFE1716AAFE00EE626C7565636861726D31000000000001000009168020691E0EFE13551109426C7565436861726D5F313639363835000000",
+            "0201060303AAFE1116AAFE20000BF017000008874803FB93540916802069080EFE13551109426C7565436861726D5F313639363835000000000000000000",
+            "0201061AFF4C000215426C7565436861726D426561636F6E730EFE1355C509168020691E0EFE13551109426C7565436861726D5F31363936383500000000",
+    };
+
+    /**
      * Keep the arguments passed in for the PendingIntent.
      */
     class PendingIntentInfo {
@@ -180,13 +207,32 @@
      */
     private final Map<Integer, Set<Integer>> mRestrictedHandles = new HashMap<>();
 
-    private BluetoothAdapter mAdapter;
+    private AdapterService mAdapterService;
     private AdvertiseManager mAdvertiseManager;
     private PeriodicScanManager mPeriodicScanManager;
     private ScanManager mScanManager;
     private AppOpsManager mAppOps;
     private ICompanionDeviceManager mCompanionManager;
     private String mExposureNotificationPackage;
+    private Handler mTestModeHandler;
+    private final Object mTestModeLock = new Object();
+
+    /**
+     */
+    private final Predicate<ScanResult> mLocationDenylistPredicate = (scanResult) -> {
+        final MacAddress parsedAddress = MacAddress
+                .fromString(scanResult.getDevice().getAddress());
+        if (mAdapterService.getLocationDenylistMac().test(parsedAddress.toByteArray())) {
+            Log.v(TAG, "Skipping device matching denylist: " + parsedAddress);
+            return true;
+        }
+        final ScanRecord scanRecord = scanResult.getScanRecord();
+        if (scanRecord.matchesAnyField(mAdapterService.getLocationDenylistAdvertisingData())) {
+            Log.v(TAG, "Skipping data matching denylist: " + scanRecord);
+            return true;
+        }
+        return false;
+    };
 
     private static GattService sGattService;
 
@@ -212,18 +258,19 @@
         mExposureNotificationPackage = getString(R.string.exposure_notification_package);
         Settings.Global.putInt(
                 getContentResolver(), "bluetooth_sanitized_exposure_notification_supported", 1);
+
         initializeNative();
-        mAdapter = BluetoothAdapter.getDefaultAdapter();
+        mAdapterService = AdapterService.getAdapterService();
         mCompanionManager = ICompanionDeviceManager.Stub.asInterface(
                 ServiceManager.getService(Context.COMPANION_DEVICE_SERVICE));
         mAppOps = getSystemService(AppOpsManager.class);
-        mAdvertiseManager = new AdvertiseManager(this, AdapterService.getAdapterService());
+        mAdvertiseManager = new AdvertiseManager(this, mAdapterService);
         mAdvertiseManager.start();
 
         mScanManager = new ScanManager(this);
         mScanManager.start();
 
-        mPeriodicScanManager = new PeriodicScanManager(AdapterService.getAdapterService());
+        mPeriodicScanManager = new PeriodicScanManager(mAdapterService);
         mPeriodicScanManager.start();
 
         setGattService(this);
@@ -270,6 +317,38 @@
         }
     }
 
+    // While test mode is enabled, pretend as if the underlying stack
+    // discovered a specific set of well-known beacons every second
+    @Override
+    protected void setTestModeEnabled(boolean enableTestMode) {
+        synchronized (mTestModeLock) {
+            if (mTestModeHandler == null) {
+                mTestModeHandler = new Handler(getMainLooper()) {
+                    public void handleMessage(Message msg) {
+                        synchronized (mTestModeLock) {
+                            if (!GattService.this.isTestModeEnabled()) {
+                                return;
+                            }
+                            for (String test : TEST_MODE_BEACONS) {
+                                onScanResultInternal(0x1b, 0x1, "DD:34:02:05:5C:4D", 1, 0, 0xff,
+                                        127, -54, 0x0, HexDump.hexStringToByteArray(test));
+                            }
+                            sendEmptyMessageDelayed(0, DateUtils.SECOND_IN_MILLIS);
+                        }
+                    }
+                };
+            }
+            if (enableTestMode && !isTestModeEnabled()) {
+                super.setTestModeEnabled(true);
+                mTestModeHandler.removeMessages(0);
+                mTestModeHandler.sendEmptyMessageDelayed(0, DateUtils.SECOND_IN_MILLIS);
+            } else if (!enableTestMode && isTestModeEnabled()) {
+                super.setTestModeEnabled(false);
+                mTestModeHandler.removeMessages(0);
+                mTestModeHandler.sendEmptyMessage(0);
+            }
+        }
+    }
 
     /**
      * Get the current instance of {@link GattService}
@@ -296,12 +375,16 @@
         sGattService = instance;
     }
 
+    // Suppressed since we're not actually enforcing here
+    @SuppressLint("AndroidFrameworkRequiresPermission")
     private boolean permissionCheck(UUID characteristicUuid) {
         return !isHidCharUuid(characteristicUuid)
                 || (checkCallingOrSelfPermission(BLUETOOTH_PRIVILEGED)
                         == PERMISSION_GRANTED);
     }
 
+    // Suppressed since we're not actually enforcing here
+    @SuppressLint("AndroidFrameworkRequiresPermission")
     private boolean permissionCheck(int connId, int handle) {
         Set<Integer> restrictedHandles = mRestrictedHandles.get(connId);
         if (restrictedHandles == null || !restrictedHandles.contains(handle)) {
@@ -312,6 +395,8 @@
                 == PERMISSION_GRANTED);
     }
 
+    // Suppressed since we're not actually enforcing here
+    @SuppressLint("AndroidFrameworkRequiresPermission")
     private boolean permissionCheck(ClientMap.App app, int connId, int handle) {
         Set<Integer> restrictedHandles = mRestrictedHandles.get(connId);
         if (restrictedHandles == null || !restrictedHandles.contains(handle)) {
@@ -355,7 +440,7 @@
             ScanClient client = getScanClient(mScannerId);
             if (client != null) {
                 client.appDied = true;
-                stopScan(client.scannerId);
+                stopScan(client.scannerId, getAttributionSource());
             }
         }
 
@@ -386,7 +471,7 @@
             if (DBG) {
                 Log.d(TAG, "Binder is dead - unregistering server (" + mAppIf + ")!");
             }
-            unregisterServer(mAppIf);
+            unregisterServer(mAppIf, getAttributionSource());
         }
     }
 
@@ -402,7 +487,7 @@
             if (DBG) {
                 Log.d(TAG, "Binder is dead - unregistering client (" + mAppIf + ")!");
             }
-            unregisterClient(mAppIf);
+            unregisterClient(mAppIf, getAttributionSource());
         }
     }
 
@@ -431,531 +516,562 @@
         }
 
         @Override
-        public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
+        public List<BluetoothDevice> getDevicesMatchingConnectionStates(
+                int[] states, AttributionSource attributionSource) {
             GattService service = getService();
             if (service == null) {
                 return new ArrayList<BluetoothDevice>();
             }
-            return service.getDevicesMatchingConnectionStates(states);
+            return service.getDevicesMatchingConnectionStates(states, attributionSource);
         }
 
         @Override
-        public void registerClient(ParcelUuid uuid, IBluetoothGattCallback callback) {
+        public void registerClient(ParcelUuid uuid, IBluetoothGattCallback callback,
+                boolean eatt_support, AttributionSource attributionSource) {
             GattService service = getService();
             if (service == null) {
                 return;
             }
-            service.registerClient(uuid.getUuid(), callback);
+            service.registerClient(uuid.getUuid(), callback, eatt_support, attributionSource);
         }
 
         @Override
-        public void unregisterClient(int clientIf) {
+        public void unregisterClient(int clientIf, AttributionSource attributionSource) {
             GattService service = getService();
             if (service == null) {
                 return;
             }
-            service.unregisterClient(clientIf);
+            service.unregisterClient(clientIf, attributionSource);
         }
 
         @Override
-        public void registerScanner(IScannerCallback callback, WorkSource workSource)
-                throws RemoteException {
+        public void registerScanner(IScannerCallback callback, WorkSource workSource,
+                AttributionSource attributionSource) throws RemoteException {
             GattService service = getService();
             if (service == null) {
                 return;
             }
-            service.registerScanner(callback, workSource);
+            service.registerScanner(callback, workSource, attributionSource);
         }
 
         @Override
-        public void unregisterScanner(int scannerId) {
+        public void unregisterScanner(int scannerId, AttributionSource attributionSource) {
             GattService service = getService();
             if (service == null) {
                 return;
             }
-            service.unregisterScanner(scannerId);
+            service.unregisterScanner(scannerId, attributionSource);
         }
 
         @Override
         public void startScan(int scannerId, ScanSettings settings, List<ScanFilter> filters,
-                List storages, String callingPackage, String callingFeatureId) {
+                List storages, AttributionSource attributionSource) {
             GattService service = getService();
             if (service == null) {
                 return;
             }
-            service.startScan(scannerId, settings, filters, storages, callingPackage,
-                    callingFeatureId);
+            service.startScan(scannerId, settings, filters, storages, attributionSource);
         }
 
         @Override
         public void startScanForIntent(PendingIntent intent, ScanSettings settings,
-                List<ScanFilter> filters, String callingPackage, String callingFeatureId)
+                List<ScanFilter> filters, AttributionSource attributionSource)
                 throws RemoteException {
             GattService service = getService();
             if (service == null) {
                 return;
             }
-            service.registerPiAndStartScan(intent, settings, filters, callingPackage,
-                    callingFeatureId);
+            service.registerPiAndStartScan(intent, settings, filters, attributionSource);
         }
 
         @Override
-        public void stopScanForIntent(PendingIntent intent, String callingPackage)
+        public void stopScanForIntent(PendingIntent intent, AttributionSource attributionSource)
                 throws RemoteException {
             GattService service = getService();
             if (service == null) {
                 return;
             }
-            service.stopScan(intent, callingPackage);
+            service.stopScan(intent, attributionSource);
         }
 
         @Override
-        public void stopScan(int scannerId) {
+        public void stopScan(int scannerId, AttributionSource attributionSource) {
             GattService service = getService();
             if (service == null) {
                 return;
             }
-            service.stopScan(scannerId);
+            service.stopScan(scannerId, attributionSource);
         }
 
         @Override
-        public void flushPendingBatchResults(int scannerId) {
+        public void flushPendingBatchResults(int scannerId, AttributionSource attributionSource) {
             GattService service = getService();
             if (service == null) {
                 return;
             }
-            service.flushPendingBatchResults(scannerId);
+            service.flushPendingBatchResults(scannerId, attributionSource);
         }
 
         @Override
         public void clientConnect(int clientIf, String address, boolean isDirect, int transport,
-                boolean opportunistic, int phy) {
+                boolean opportunistic, int phy, AttributionSource attributionSource) {
             GattService service = getService();
             if (service == null) {
                 return;
             }
-            service.clientConnect(clientIf, address, isDirect, transport, opportunistic, phy);
+            service.clientConnect(clientIf, address, isDirect, transport, opportunistic, phy,
+                    attributionSource);
         }
 
         @Override
-        public void clientDisconnect(int clientIf, String address) {
+        public void clientDisconnect(
+                int clientIf, String address, AttributionSource attributionSource) {
             GattService service = getService();
             if (service == null) {
                 return;
             }
-            service.clientDisconnect(clientIf, address);
+            service.clientDisconnect(clientIf, address, attributionSource);
         }
 
         @Override
         public void clientSetPreferredPhy(int clientIf, String address, int txPhy, int rxPhy,
-                int phyOptions) {
+                int phyOptions, AttributionSource attributionSource) {
             GattService service = getService();
             if (service == null) {
                 return;
             }
-            service.clientSetPreferredPhy(clientIf, address, txPhy, rxPhy, phyOptions);
+            service.clientSetPreferredPhy(clientIf, address, txPhy, rxPhy, phyOptions,
+                    attributionSource);
         }
 
         @Override
-        public void clientReadPhy(int clientIf, String address) {
+        public void clientReadPhy(
+                int clientIf, String address, AttributionSource attributionSource) {
             GattService service = getService();
             if (service == null) {
                 return;
             }
-            service.clientReadPhy(clientIf, address);
+            service.clientReadPhy(clientIf, address, attributionSource);
         }
 
         @Override
-        public void refreshDevice(int clientIf, String address) {
+        public void refreshDevice(
+                int clientIf, String address, AttributionSource attributionSource) {
             GattService service = getService();
             if (service == null) {
                 return;
             }
-            service.refreshDevice(clientIf, address);
+            service.refreshDevice(clientIf, address, attributionSource);
         }
 
         @Override
-        public void discoverServices(int clientIf, String address) {
+        public void discoverServices(
+                int clientIf, String address, AttributionSource attributionSource) {
             GattService service = getService();
             if (service == null) {
                 return;
             }
-            service.discoverServices(clientIf, address);
+            service.discoverServices(clientIf, address, attributionSource);
         }
 
         @Override
-        public void discoverServiceByUuid(int clientIf, String address, ParcelUuid uuid) {
+        public void discoverServiceByUuid(int clientIf, String address, ParcelUuid uuid,
+                AttributionSource attributionSource) {
             GattService service = getService();
             if (service == null) {
                 return;
             }
-            service.discoverServiceByUuid(clientIf, address, uuid.getUuid());
+            service.discoverServiceByUuid(clientIf, address, uuid.getUuid(), attributionSource);
         }
 
         @Override
-        public void readCharacteristic(int clientIf, String address, int handle, int authReq) {
+        public void readCharacteristic(int clientIf, String address, int handle, int authReq,
+                AttributionSource attributionSource) {
             GattService service = getService();
             if (service == null) {
                 return;
             }
-            service.readCharacteristic(clientIf, address, handle, authReq);
+            service.readCharacteristic(clientIf, address, handle, authReq, attributionSource);
         }
 
         @Override
         public void readUsingCharacteristicUuid(int clientIf, String address, ParcelUuid uuid,
-                int startHandle, int endHandle, int authReq) {
+                int startHandle, int endHandle, int authReq, AttributionSource attributionSource) {
             GattService service = getService();
             if (service == null) {
                 return;
             }
             service.readUsingCharacteristicUuid(clientIf, address, uuid.getUuid(), startHandle,
-                    endHandle, authReq);
+                    endHandle, authReq, attributionSource);
         }
 
         @Override
         public void writeCharacteristic(int clientIf, String address, int handle, int writeType,
-                int authReq, byte[] value) {
+                int authReq, byte[] value, AttributionSource attributionSource) {
             GattService service = getService();
             if (service == null) {
                 return;
             }
-            service.writeCharacteristic(clientIf, address, handle, writeType, authReq, value);
+            service.writeCharacteristic(clientIf, address, handle, writeType, authReq, value,
+                    attributionSource);
         }
 
         @Override
-        public void readDescriptor(int clientIf, String address, int handle, int authReq) {
+        public void readDescriptor(int clientIf, String address, int handle, int authReq,
+                AttributionSource attributionSource) {
             GattService service = getService();
             if (service == null) {
                 return;
             }
-            service.readDescriptor(clientIf, address, handle, authReq);
+            service.readDescriptor(clientIf, address, handle, authReq, attributionSource);
         }
 
         @Override
         public void writeDescriptor(int clientIf, String address, int handle, int authReq,
-                byte[] value) {
+                byte[] value, AttributionSource attributionSource) {
             GattService service = getService();
             if (service == null) {
                 return;
             }
-            service.writeDescriptor(clientIf, address, handle, authReq, value);
+            service.writeDescriptor(clientIf, address, handle, authReq, value, attributionSource);
         }
 
         @Override
-        public void beginReliableWrite(int clientIf, String address) {
+        public void beginReliableWrite(
+                int clientIf, String address, AttributionSource attributionSource) {
             GattService service = getService();
             if (service == null) {
                 return;
             }
-            service.beginReliableWrite(clientIf, address);
+            service.beginReliableWrite(clientIf, address, attributionSource);
         }
 
         @Override
-        public void endReliableWrite(int clientIf, String address, boolean execute) {
+        public void endReliableWrite(int clientIf, String address, boolean execute,
+                AttributionSource attributionSource) {
             GattService service = getService();
             if (service == null) {
                 return;
             }
-            service.endReliableWrite(clientIf, address, execute);
+            service.endReliableWrite(clientIf, address, execute, attributionSource);
         }
 
         @Override
         public void registerForNotification(int clientIf, String address, int handle,
-                boolean enable) {
+                boolean enable, AttributionSource attributionSource) {
             GattService service = getService();
             if (service == null) {
                 return;
             }
-            service.registerForNotification(clientIf, address, handle, enable);
+            service.registerForNotification(clientIf, address, handle, enable, attributionSource);
         }
 
         @Override
-        public void readRemoteRssi(int clientIf, String address) {
+        public void readRemoteRssi(
+                int clientIf, String address, AttributionSource attributionSource) {
             GattService service = getService();
             if (service == null) {
                 return;
             }
-            service.readRemoteRssi(clientIf, address);
+            service.readRemoteRssi(clientIf, address, attributionSource);
         }
 
         @Override
-        public void configureMTU(int clientIf, String address, int mtu) {
+        public void configureMTU(
+                int clientIf, String address, int mtu, AttributionSource attributionSource) {
             GattService service = getService();
             if (service == null) {
                 return;
             }
-            service.configureMTU(clientIf, address, mtu);
+            service.configureMTU(clientIf, address, mtu, attributionSource);
         }
 
         @Override
         public void connectionParameterUpdate(int clientIf, String address,
-                int connectionPriority) {
+                int connectionPriority, AttributionSource attributionSource) {
             GattService service = getService();
             if (service == null) {
                 return;
             }
-            service.connectionParameterUpdate(clientIf, address, connectionPriority);
+            service.connectionParameterUpdate(
+                    clientIf, address, connectionPriority, attributionSource);
         }
 
         @Override
         public void leConnectionUpdate(int clientIf, String address,
                 int minConnectionInterval, int maxConnectionInterval,
-                int slaveLatency, int supervisionTimeout,
-                int minConnectionEventLen, int maxConnectionEventLen) {
+                int peripheralLatency, int supervisionTimeout,
+                int minConnectionEventLen, int maxConnectionEventLen,
+                AttributionSource attributionSource) {
             GattService service = getService();
             if (service == null) {
                 return;
             }
             service.leConnectionUpdate(clientIf, address, minConnectionInterval,
-                                       maxConnectionInterval, slaveLatency,
+                                       maxConnectionInterval, peripheralLatency,
                                        supervisionTimeout, minConnectionEventLen,
-                                       maxConnectionEventLen);
+                                       maxConnectionEventLen, attributionSource);
         }
 
         @Override
-        public void registerServer(ParcelUuid uuid, IBluetoothGattServerCallback callback) {
+        public void registerServer(ParcelUuid uuid, IBluetoothGattServerCallback callback,
+                boolean eatt_support, AttributionSource attributionSource) {
             GattService service = getService();
             if (service == null) {
                 return;
             }
-            service.registerServer(uuid.getUuid(), callback);
+            service.registerServer(uuid.getUuid(), callback, eatt_support, attributionSource);
         }
 
         @Override
-        public void unregisterServer(int serverIf) {
+        public void unregisterServer(int serverIf, AttributionSource attributionSource) {
             GattService service = getService();
             if (service == null) {
                 return;
             }
-            service.unregisterServer(serverIf);
+            service.unregisterServer(serverIf, attributionSource);
         }
 
         @Override
-        public void serverConnect(int serverIf, String address, boolean isDirect, int transport) {
+        public void serverConnect(int serverIf, String address, boolean isDirect, int transport,
+                AttributionSource attributionSource) {
             GattService service = getService();
             if (service == null) {
                 return;
             }
-            service.serverConnect(serverIf, address, isDirect, transport);
+            service.serverConnect(serverIf, address, isDirect, transport, attributionSource);
         }
 
         @Override
-        public void serverDisconnect(int serverIf, String address) {
+        public void serverDisconnect(
+                int serverIf, String address, AttributionSource attributionSource) {
             GattService service = getService();
             if (service == null) {
                 return;
             }
-            service.serverDisconnect(serverIf, address);
+            service.serverDisconnect(serverIf, address, attributionSource);
         }
 
         @Override
         public void serverSetPreferredPhy(int serverIf, String address, int txPhy, int rxPhy,
-                int phyOptions) {
+                int phyOptions, AttributionSource attributionSource) {
             GattService service = getService();
             if (service == null) {
                 return;
             }
-            service.serverSetPreferredPhy(serverIf, address, txPhy, rxPhy, phyOptions);
+            service.serverSetPreferredPhy(
+                    serverIf, address, txPhy, rxPhy, phyOptions, attributionSource);
         }
 
         @Override
-        public void serverReadPhy(int clientIf, String address) {
+        public void serverReadPhy(
+                int clientIf, String address, AttributionSource attributionSource) {
             GattService service = getService();
             if (service == null) {
                 return;
             }
-            service.serverReadPhy(clientIf, address);
+            service.serverReadPhy(clientIf, address, attributionSource);
         }
 
         @Override
-        public void addService(int serverIf, BluetoothGattService svc) {
+        public void addService(
+                int serverIf, BluetoothGattService svc, AttributionSource attributionSource) {
             GattService service = getService();
             if (service == null) {
                 return;
             }
 
-            service.addService(serverIf, svc);
+            service.addService(serverIf, svc, attributionSource);
         }
 
         @Override
-        public void removeService(int serverIf, int handle) {
+        public void removeService(int serverIf, int handle, AttributionSource attributionSource) {
             GattService service = getService();
             if (service == null) {
                 return;
             }
-            service.removeService(serverIf, handle);
+            service.removeService(serverIf, handle, attributionSource);
         }
 
         @Override
-        public void clearServices(int serverIf) {
+        public void clearServices(int serverIf, AttributionSource attributionSource) {
             GattService service = getService();
             if (service == null) {
                 return;
             }
-            service.clearServices(serverIf);
+            service.clearServices(serverIf, attributionSource);
         }
 
         @Override
         public void sendResponse(int serverIf, String address, int requestId, int status,
-                int offset, byte[] value) {
+                int offset, byte[] value, AttributionSource attributionSource) {
             GattService service = getService();
             if (service == null) {
                 return;
             }
-            service.sendResponse(serverIf, address, requestId, status, offset, value);
+            service.sendResponse(
+                    serverIf, address, requestId, status, offset, value, attributionSource);
         }
 
         @Override
         public void sendNotification(int serverIf, String address, int handle, boolean confirm,
-                byte[] value) {
+                byte[] value, AttributionSource attributionSource) {
             GattService service = getService();
             if (service == null) {
                 return;
             }
-            service.sendNotification(serverIf, address, handle, confirm, value);
+            service.sendNotification(serverIf, address, handle, confirm, value, attributionSource);
         }
 
         @Override
         public void startAdvertisingSet(AdvertisingSetParameters parameters,
                 AdvertiseData advertiseData, AdvertiseData scanResponse,
                 PeriodicAdvertisingParameters periodicParameters, AdvertiseData periodicData,
-                int duration, int maxExtAdvEvents, IAdvertisingSetCallback callback) {
+                int duration, int maxExtAdvEvents, IAdvertisingSetCallback callback,
+                AttributionSource attributionSource) {
             GattService service = getService();
             if (service == null) {
                 return;
             }
             service.startAdvertisingSet(parameters, advertiseData, scanResponse, periodicParameters,
-                    periodicData, duration, maxExtAdvEvents, callback);
+                    periodicData, duration, maxExtAdvEvents, callback, attributionSource);
         }
 
         @Override
-        public void stopAdvertisingSet(IAdvertisingSetCallback callback) {
+        public void stopAdvertisingSet(
+                IAdvertisingSetCallback callback, AttributionSource attributionSource) {
             GattService service = getService();
             if (service == null) {
                 return;
             }
-            service.stopAdvertisingSet(callback);
+            service.stopAdvertisingSet(callback, attributionSource);
         }
 
         @Override
-        public void getOwnAddress(int advertiserId) {
+        public void getOwnAddress(int advertiserId, AttributionSource attributionSource) {
             GattService service = getService();
             if (service == null) {
                 return;
             }
-            service.getOwnAddress(advertiserId);
+            service.getOwnAddress(advertiserId, attributionSource);
         }
 
         @Override
         public void enableAdvertisingSet(int advertiserId, boolean enable, int duration,
-                int maxExtAdvEvents) {
+                int maxExtAdvEvents, AttributionSource attributionSource) {
             GattService service = getService();
             if (service == null) {
                 return;
             }
-            service.enableAdvertisingSet(advertiserId, enable, duration, maxExtAdvEvents);
+            service.enableAdvertisingSet(
+                    advertiserId, enable, duration, maxExtAdvEvents, attributionSource);
         }
 
         @Override
-        public void setAdvertisingData(int advertiserId, AdvertiseData data) {
+        public void setAdvertisingData(
+                int advertiserId, AdvertiseData data, AttributionSource attributionSource) {
             GattService service = getService();
             if (service == null) {
                 return;
             }
-            service.setAdvertisingData(advertiserId, data);
+            service.setAdvertisingData(advertiserId, data, attributionSource);
         }
 
         @Override
-        public void setScanResponseData(int advertiserId, AdvertiseData data) {
+        public void setScanResponseData(
+                int advertiserId, AdvertiseData data, AttributionSource attributionSource) {
             GattService service = getService();
             if (service == null) {
                 return;
             }
-            service.setScanResponseData(advertiserId, data);
+            service.setScanResponseData(advertiserId, data, attributionSource);
         }
 
         @Override
         public void setAdvertisingParameters(int advertiserId,
-                AdvertisingSetParameters parameters) {
+                AdvertisingSetParameters parameters, AttributionSource attributionSource) {
             GattService service = getService();
             if (service == null) {
                 return;
             }
-            service.setAdvertisingParameters(advertiserId, parameters);
+            service.setAdvertisingParameters(advertiserId, parameters, attributionSource);
         }
 
         @Override
         public void setPeriodicAdvertisingParameters(int advertiserId,
-                PeriodicAdvertisingParameters parameters) {
+                PeriodicAdvertisingParameters parameters, AttributionSource attributionSource) {
             GattService service = getService();
             if (service == null) {
                 return;
             }
-            service.setPeriodicAdvertisingParameters(advertiserId, parameters);
+            service.setPeriodicAdvertisingParameters(advertiserId, parameters, attributionSource);
         }
 
         @Override
-        public void setPeriodicAdvertisingData(int advertiserId, AdvertiseData data) {
+        public void setPeriodicAdvertisingData(int advertiserId, AdvertiseData data,
+                AttributionSource attributionSource) {
             GattService service = getService();
             if (service == null) {
                 return;
             }
-            service.setPeriodicAdvertisingData(advertiserId, data);
+            service.setPeriodicAdvertisingData(advertiserId, data, attributionSource);
         }
 
         @Override
-        public void setPeriodicAdvertisingEnable(int advertiserId, boolean enable) {
+        public void setPeriodicAdvertisingEnable(
+                int advertiserId, boolean enable, AttributionSource attributionSource) {
             GattService service = getService();
             if (service == null) {
                 return;
             }
-            service.setPeriodicAdvertisingEnable(advertiserId, enable);
+            service.setPeriodicAdvertisingEnable(advertiserId, enable, attributionSource);
         }
 
         @Override
         public void registerSync(ScanResult scanResult, int skip, int timeout,
-                IPeriodicAdvertisingCallback callback) {
+                IPeriodicAdvertisingCallback callback, AttributionSource attributionSource) {
             GattService service = getService();
             if (service == null) {
                 return;
             }
-            service.registerSync(scanResult, skip, timeout, callback);
+            service.registerSync(scanResult, skip, timeout, callback, attributionSource);
         }
 
         @Override
-        public void unregisterSync(IPeriodicAdvertisingCallback callback) {
+        public void unregisterSync(
+                IPeriodicAdvertisingCallback callback, AttributionSource attributionSource) {
             GattService service = getService();
             if (service == null) {
                 return;
             }
-            service.unregisterSync(callback);
+            service.unregisterSync(callback, attributionSource);
         }
 
         @Override
-        public void disconnectAll() {
+        public void disconnectAll(AttributionSource attributionSource) {
             GattService service = getService();
             if (service == null) {
                 return;
             }
-            service.disconnectAll();
+            service.disconnectAll(attributionSource);
         }
 
         @Override
-        public void unregAll() {
+        public void unregAll(AttributionSource attributionSource) {
             GattService service = getService();
             if (service == null) {
                 return;
             }
-            service.unregAll();
+            service.unregAll(attributionSource);
         }
 
         @Override
-        public int numHwTrackFiltersAvailable() {
+        public int numHwTrackFiltersAvailable(AttributionSource attributionSource) {
             GattService service = getService();
             if (service == null) {
                 return 0;
             }
-            return service.numHwTrackFiltersAvailable();
+            return service.numHwTrackFiltersAvailable(attributionSource);
         }
     }
 
@@ -1018,6 +1134,16 @@
     void onScanResult(int eventType, int addressType, String address, int primaryPhy,
             int secondaryPhy, int advertisingSid, int txPower, int rssi, int periodicAdvInt,
             byte[] advData) {
+        // When in testing mode, ignore all real-world events
+        if (isTestModeEnabled()) return;
+
+        onScanResultInternal(eventType, addressType, address, primaryPhy, secondaryPhy,
+                advertisingSid, txPower, rssi, periodicAdvInt, advData);
+    }
+
+    void onScanResultInternal(int eventType, int addressType, String address, int primaryPhy,
+            int secondaryPhy, int advertisingSid, int txPower, int rssi, int periodicAdvInt,
+            byte[] advData) {
         if (VDBG) {
             Log.d(TAG, "onScanResult() - eventType=0x" + Integer.toHexString(eventType)
                     + ", addressType=" + addressType + ", address=" + address + ", primaryPhy="
@@ -1025,33 +1151,16 @@
                     + Integer.toHexString(advertisingSid) + ", txPower=" + txPower + ", rssi="
                     + rssi + ", periodicAdvInt=0x" + Integer.toHexString(periodicAdvInt));
         }
-        List<UUID> remoteUuids = parseUuids(advData);
 
         byte[] legacyAdvData = Arrays.copyOfRange(advData, 0, 62);
 
         for (ScanClient client : mScanManager.getRegularScanQueue()) {
-            if (client.uuids.length > 0) {
-                int matches = 0;
-                for (UUID search : client.uuids) {
-                    for (UUID remote : remoteUuids) {
-                        if (remote.equals(search)) {
-                            ++matches;
-                            break; // Only count 1st match in case of duplicates
-                        }
-                    }
-                }
-
-                if (matches < client.uuids.length) {
-                    continue;
-                }
-            }
-
             ScannerMap.App app = mScannerMap.getById(client.scannerId);
             if (app == null) {
                 continue;
             }
 
-            BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address);
+            BluetoothDevice device = getAnonymousDevice(address);
 
             ScanSettings settings = client.settings;
             byte[] scanRecordData;
@@ -1068,11 +1177,18 @@
                 scanRecordData = advData;
             }
 
+            ScanRecord scanRecord = ScanRecord.parseFromBytes(scanRecordData);
             ScanResult result =
                     new ScanResult(device, eventType, primaryPhy, secondaryPhy, advertisingSid,
-                            txPower, rssi, periodicAdvInt,
-                            ScanRecord.parseFromBytes(scanRecordData),
+                            txPower, rssi, periodicAdvInt, scanRecord,
                             SystemClock.elapsedRealtimeNanos());
+
+            if (client.hasDisavowedLocation) {
+                if (mLocationDenylistPredicate.test(result)) {
+                    continue;
+                }
+            }
+
             boolean hasPermission = hasScanResultPermission(client);
             if (!hasPermission) {
                 for (String associatedDevice : client.associatedDevices) {
@@ -1123,8 +1239,13 @@
         try {
             sendResultsByPendingIntent(pii, results, callbackType);
         } catch (PendingIntent.CanceledException e) {
-            stopScan(client.scannerId);
-            unregisterScanner(client.scannerId);
+            final long token = Binder.clearCallingIdentity();
+            try {
+                stopScan(client.scannerId, getAttributionSource());
+                unregisterScanner(client.scannerId, getAttributionSource());
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
         }
     }
 
@@ -1180,6 +1301,9 @@
                 || client.hasScanWithoutLocationPermission) {
             return true;
         }
+        if (client.hasDisavowedLocation) {
+            return true;
+        }
         return client.hasLocationPermission && !Utils.blockedByLocationOff(this, client.userHandle);
     }
 
@@ -1304,6 +1428,24 @@
         app.callback.onConnectionUpdated(address, interval, latency, timeout, status);
     }
 
+    void onServiceChanged(int connId) throws RemoteException {
+        if (DBG) {
+            Log.d(TAG, "onServiceChanged - connId=" + connId);
+        }
+
+        String address = mClientMap.addressByConnId(connId);
+        if (address == null) {
+            return;
+        }
+
+        ClientMap.App app = mClientMap.getByConnId(connId);
+        if (app == null) {
+            return;
+        }
+
+        app.callback.onServiceChanged(address);
+    }
+
     void onServerPhyUpdate(int connId, int txPhy, int rxPhy, int status) throws RemoteException {
         if (DBG) {
             Log.d(TAG, "onServerPhyUpdate() - connId=" + connId + ", status=" + status);
@@ -1644,6 +1786,14 @@
 
     void onBatchScanReports(int status, int scannerId, int reportType, int numRecords,
             byte[] recordData) throws RemoteException {
+        // When in testing mode, ignore all real-world events
+        if (isTestModeEnabled()) return;
+
+        onBatchScanReportsInternal(status, scannerId, reportType, numRecords, recordData);
+    }
+
+    void onBatchScanReportsInternal(int status, int scannerId, int reportType, int numRecords,
+            byte[] recordData) throws RemoteException {
         if (DBG) {
             Log.d(TAG, "onBatchScanReports() - scannerId=" + scannerId + ", status=" + status
                     + ", reportType=" + reportType + ", numRecords=" + numRecords);
@@ -1680,6 +1830,10 @@
                 }
             }
 
+            if (client.hasDisavowedLocation) {
+                permittedResults.removeIf(mLocationDenylistPredicate);
+            }
+
             if (app.callback != null) {
                 app.callback.onBatchScanResults(permittedResults);
             } else {
@@ -1779,7 +1933,7 @@
                     extractBytes(batchRecord, i * TRUNCATED_RESULT_SIZE, TRUNCATED_RESULT_SIZE);
             byte[] address = extractBytes(record, 0, 6);
             reverse(address);
-            BluetoothDevice device = mAdapter.getRemoteDevice(address);
+            BluetoothDevice device = getAnonymousDevice(address);
             int rssi = record[8];
             long timestampNanos = now - parseTimestampNanos(extractBytes(record, 9, 2));
             results.add(new ScanResult(device, ScanRecord.parseFromBytes(new byte[0]), rssi,
@@ -1806,7 +1960,7 @@
             byte[] address = extractBytes(batchRecord, position, 6);
             // TODO: remove temp hack.
             reverse(address);
-            BluetoothDevice device = mAdapter.getRemoteDevice(address);
+            BluetoothDevice device = getAnonymousDevice(address);
             position += 6;
             // Skip address type.
             position++;
@@ -1853,11 +2007,12 @@
         return bytes;
     }
 
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN)
     void onBatchScanThresholdCrossed(int clientIf) {
         if (DBG) {
             Log.d(TAG, "onBatchScanThresholdCrossed() - clientIf=" + clientIf);
         }
-        flushPendingBatchResults(clientIf);
+        flushPendingBatchResults(clientIf, getAttributionSource());
     }
 
     AdvtFilterOnFoundOnLostInfo createOnTrackAdvFoundLostObject(int clientIf, int advPktLen,
@@ -1883,8 +2038,7 @@
             return;
         }
 
-        BluetoothDevice device =
-                BluetoothAdapter.getDefaultAdapter().getRemoteDevice(trackingInfo.getAddress());
+        BluetoothDevice device = getAnonymousDevice(trackingInfo.getAddress());
         int advertiserState = trackingInfo.getAdvState();
         ScanResult result =
                 new ScanResult(device, ScanRecord.parseFromBytes(trackingInfo.getResult()),
@@ -1989,14 +2143,20 @@
      * GATT Service functions - Shared CLIENT/SERVER
      *************************************************************************/
 
-    List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
-        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
+    List<BluetoothDevice> getDevicesMatchingConnectionStates(
+            int[] states, AttributionSource attributionSource) {
+        if (!Utils.checkConnectPermissionForDataDelivery(
+                this, attributionSource,
+                "GattService getDevicesMatchingConnectionStates")) {
+            return new ArrayList<>(0);
+        }
 
         Map<BluetoothDevice, Integer> deviceStates = new HashMap<BluetoothDevice, Integer>();
 
         // Add paired LE devices
 
-        Set<BluetoothDevice> bondedDevices = mAdapter.getBondedDevices();
+        BluetoothDevice[] bondedDevices = mAdapterService.getBondedDevices();
         for (BluetoothDevice device : bondedDevices) {
             if (getDeviceType(device) != AbstractionLayer.BT_DEVICE_TYPE_BREDR) {
                 deviceStates.put(device, BluetoothProfile.STATE_DISCONNECTED);
@@ -2010,7 +2170,7 @@
         connectedDevices.addAll(mServerMap.getConnectedDevices());
 
         for (String address : connectedDevices) {
-            BluetoothDevice device = mAdapter.getRemoteDevice(address);
+            BluetoothDevice device = getAnonymousDevice(address);
             if (device != null) {
                 deviceStates.put(device, BluetoothProfile.STATE_CONNECTED);
             }
@@ -2031,21 +2191,24 @@
         return deviceList;
     }
 
-    void registerScanner(IScannerCallback callback, WorkSource workSource) throws RemoteException {
-        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN)
+    void registerScanner(IScannerCallback callback, WorkSource workSource,
+            AttributionSource attributionSource) throws RemoteException {
+        if (!Utils.checkScanPermissionForDataDelivery(
+                this, attributionSource, "GattService registerScanner")) {
+            return;
+        }
 
         UUID uuid = UUID.randomUUID();
         if (DBG) {
             Log.d(TAG, "registerScanner() - UUID=" + uuid);
         }
 
-        if (workSource != null) {
-            enforceImpersonatationPermission();
-        }
+        enforceImpersonatationPermissionIfNeeded(workSource);
 
         AppScanStats app = mScannerMap.getAppScanStatsByUid(Binder.getCallingUid());
         if (app != null && app.isScanningTooFrequently()
-                && checkCallingOrSelfPermission(BLUETOOTH_PRIVILEGED) != PERMISSION_GRANTED) {
+                && !Utils.checkCallerHasPrivilegedPermission(this)) {
             Log.e(TAG, "App '" + app.appName + "' is scanning too frequently");
             callback.onScannerRegistered(ScanCallback.SCAN_FAILED_SCANNING_TOO_FREQUENTLY, -1);
             return;
@@ -2055,8 +2218,12 @@
         mScanManager.registerScanner(uuid);
     }
 
-    void unregisterScanner(int scannerId) {
-        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN)
+    void unregisterScanner(int scannerId, AttributionSource attributionSource) {
+        if (!Utils.checkScanPermissionForDataDelivery(
+                this, attributionSource, "GattService unregisterScanner")) {
+            return;
+        }
 
         if (DBG) {
             Log.d(TAG, "unregisterScanner() - scannerId=" + scannerId);
@@ -2085,29 +2252,40 @@
         return new ArrayList<String>();
     }
 
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN)
     void startScan(int scannerId, ScanSettings settings, List<ScanFilter> filters,
-            List<List<ResultStorageDescriptor>> storages, String callingPackage,
-            @Nullable String callingFeatureId) {
+            List<List<ResultStorageDescriptor>> storages, AttributionSource attributionSource) {
         if (DBG) {
             Log.d(TAG, "start scan with filters");
         }
-        UserHandle callingUser = UserHandle.of(UserHandle.getCallingUserId());
-        enforceAdminPermission();
-        if (needsPrivilegedPermissionForScan(settings)) {
-            enforcePrivilegedPermission();
+
+        if (!Utils.checkScanPermissionForDataDelivery(
+                this, attributionSource, "Starting GATT scan.")) {
+            return;
         }
+
+        enforcePrivilegedPermissionIfNeeded(settings);
+        String callingPackage = attributionSource.getPackageName();
+        settings = enforceReportDelayFloor(settings);
+        enforcePrivilegedPermissionIfNeeded(filters);
         final ScanClient scanClient = new ScanClient(scannerId, settings, filters, storages);
         scanClient.userHandle = UserHandle.of(UserHandle.getCallingUserId());
         mAppOps.checkPackage(Binder.getCallingUid(), callingPackage);
         scanClient.eligibleForSanitizedExposureNotification =
                 callingPackage.equals(mExposureNotificationPackage);
+
+        scanClient.hasDisavowedLocation =
+                Utils.hasDisavowedLocationForScan(this, attributionSource, isTestModeEnabled());
+
         scanClient.isQApp = Utils.isQApp(this, callingPackage);
-        if (scanClient.isQApp) {
-            scanClient.hasLocationPermission = Utils.checkCallerHasFineLocation(this, mAppOps,
-                    callingPackage, callingFeatureId, scanClient.userHandle);
-        } else {
-            scanClient.hasLocationPermission = Utils.checkCallerHasCoarseOrFineLocation(this,
-                    mAppOps, callingPackage, callingFeatureId, scanClient.userHandle);
+        if (!scanClient.hasDisavowedLocation) {
+            if (scanClient.isQApp) {
+                scanClient.hasLocationPermission = Utils.checkCallerHasFineLocation(
+                        this, attributionSource, scanClient.userHandle);
+            } else {
+                scanClient.hasLocationPermission = Utils.checkCallerHasCoarseOrFineLocation(
+                        this, attributionSource, scanClient.userHandle);
+            }
         }
         scanClient.hasNetworkSettingsPermission =
                 Utils.checkCallerHasNetworkSettingsPermission(this);
@@ -2132,20 +2310,25 @@
         mScanManager.startScan(scanClient);
     }
 
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN)
     void registerPiAndStartScan(PendingIntent pendingIntent, ScanSettings settings,
-            List<ScanFilter> filters, String callingPackage, @Nullable String callingFeatureId) {
+            List<ScanFilter> filters, AttributionSource attributionSource) {
         if (DBG) {
             Log.d(TAG, "start scan with filters, for PendingIntent");
         }
-        enforceAdminPermission();
-        if (needsPrivilegedPermissionForScan(settings)) {
-            enforcePrivilegedPermission();
-        }
 
+        if (!Utils.checkScanPermissionForDataDelivery(
+                this, attributionSource, "Starting GATT scan.")) {
+            return;
+        }
+        enforcePrivilegedPermissionIfNeeded(settings);
+        settings = enforceReportDelayFloor(settings);
+        enforcePrivilegedPermissionIfNeeded(filters);
         UUID uuid = UUID.randomUUID();
         if (DBG) {
             Log.d(TAG, "startScan(PI) - UUID=" + uuid);
         }
+        String callingPackage = attributionSource.getPackageName();
         PendingIntentInfo piInfo = new PendingIntentInfo();
         piInfo.intent = pendingIntent;
         piInfo.settings = settings;
@@ -2163,18 +2346,24 @@
         mAppOps.checkPackage(Binder.getCallingUid(), callingPackage);
         app.mEligibleForSanitizedExposureNotification =
                 callingPackage.equals(mExposureNotificationPackage);
+
+        app.mHasDisavowedLocation =
+                Utils.hasDisavowedLocationForScan(this, attributionSource, isTestModeEnabled());
+
         app.mIsQApp = Utils.isQApp(this, callingPackage);
-        try {
-            if (app.mIsQApp) {
-                app.hasLocationPermission = Utils.checkCallerHasFineLocation(
-                      this, mAppOps, callingPackage, callingFeatureId, app.mUserHandle);
-            } else {
-                app.hasLocationPermission = Utils.checkCallerHasCoarseOrFineLocation(
-                      this, mAppOps, callingPackage, callingFeatureId, app.mUserHandle);
+        if (!app.mHasDisavowedLocation) {
+            try {
+                if (app.mIsQApp) {
+                    app.hasLocationPermission = Utils.checkCallerHasFineLocation(
+                            this, attributionSource, app.mUserHandle);
+                } else {
+                    app.hasLocationPermission = Utils.checkCallerHasCoarseOrFineLocation(
+                            this, attributionSource, app.mUserHandle);
+                }
+            } catch (SecurityException se) {
+                // No need to throw here. Just mark as not granted.
+                app.hasLocationPermission = false;
             }
-        } catch (SecurityException se) {
-            // No need to throw here. Just mark as not granted.
-            app.hasLocationPermission = false;
         }
         app.mHasNetworkSettingsPermission =
                 Utils.checkCallerHasNetworkSettingsPermission(this);
@@ -2199,6 +2388,7 @@
         scanClient.hasNetworkSetupWizardPermission = app.mHasNetworkSetupWizardPermission;
         scanClient.hasScanWithoutLocationPermission = app.mHasScanWithoutLocationPermission;
         scanClient.associatedDevices = app.mAssociatedDevices;
+        scanClient.hasDisavowedLocation = app.mHasDisavowedLocation;
 
         AppScanStats scanStats = mScannerMap.getAppScanStatsById(scannerId);
         if (scanStats != null) {
@@ -2211,15 +2401,24 @@
         mScanManager.startScan(scanClient);
     }
 
-    void flushPendingBatchResults(int scannerId) {
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN)
+    void flushPendingBatchResults(int scannerId, AttributionSource attributionSource) {
+        if (!Utils.checkScanPermissionForDataDelivery(
+                this, attributionSource, "GattService flushPendingBatchResults")) {
+            return;
+        }
         if (DBG) {
             Log.d(TAG, "flushPendingBatchResults - scannerId=" + scannerId);
         }
         mScanManager.flushBatchScanResults(new ScanClient(scannerId));
     }
 
-    void stopScan(int scannerId) {
-        enforceAdminPermission();
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN)
+    void stopScan(int scannerId, AttributionSource attributionSource) {
+        if (!Utils.checkScanPermissionForDataDelivery(
+                this, attributionSource, "GattService stopScan")) {
+            return;
+        }
         int scanQueueSize =
                 mScanManager.getBatchScanQueue().size() + mScanManager.getRegularScanQueue().size();
         if (DBG) {
@@ -2235,8 +2434,12 @@
         mScanManager.stopScan(scannerId);
     }
 
-    void stopScan(PendingIntent intent, String callingPackage) {
-        enforceAdminPermission();
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN)
+    void stopScan(PendingIntent intent, AttributionSource attributionSource) {
+        if (!Utils.checkScanPermissionForDataDelivery(
+                this, attributionSource, "GattService stopScan")) {
+            return;
+        }
         PendingIntentInfo pii = new PendingIntentInfo();
         pii.intent = intent;
         ScannerMap.App app = mScannerMap.getByContextInfo(pii);
@@ -2245,13 +2448,14 @@
         }
         if (app != null) {
             final int scannerId = app.id;
-            stopScan(scannerId);
+            stopScan(scannerId, attributionSource);
             // Also unregister the scanner
-            unregisterScanner(scannerId);
+            unregisterScanner(scannerId, attributionSource);
         }
     }
 
-    void disconnectAll() {
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
+    void disconnectAll(AttributionSource attributionSource) {
         if (DBG) {
             Log.d(TAG, "disconnectAll()");
         }
@@ -2260,89 +2464,149 @@
             if (DBG) {
                 Log.d(TAG, "disconnecting addr:" + entry.getValue());
             }
-            clientDisconnect(entry.getKey(), entry.getValue());
+            clientDisconnect(entry.getKey(), entry.getValue(), attributionSource);
             //clientDisconnect(int clientIf, String address)
         }
     }
 
-    void unregAll() {
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
+    void unregAll(AttributionSource attributionSource) {
         for (Integer appId : mClientMap.getAllAppsIds()) {
             if (DBG) {
                 Log.d(TAG, "unreg:" + appId);
             }
-            unregisterClient(appId);
+            unregisterClient(appId, attributionSource);
         }
     }
 
     /**************************************************************************
      * PERIODIC SCANNING
      *************************************************************************/
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN)
     void registerSync(ScanResult scanResult, int skip, int timeout,
-            IPeriodicAdvertisingCallback callback) {
-        enforceAdminPermission();
+            IPeriodicAdvertisingCallback callback, AttributionSource attributionSource) {
+        if (!Utils.checkScanPermissionForDataDelivery(
+                this, attributionSource, "GattService registerSync")) {
+            return;
+        }
         mPeriodicScanManager.startSync(scanResult, skip, timeout, callback);
     }
 
-    void unregisterSync(IPeriodicAdvertisingCallback callback) {
-        enforceAdminPermission();
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN)
+    void unregisterSync(
+            IPeriodicAdvertisingCallback callback, AttributionSource attributionSource) {
+        if (!Utils.checkScanPermissionForDataDelivery(
+                this, attributionSource, "GattService unregisterSync")) {
+            return;
+        }
         mPeriodicScanManager.stopSync(callback);
     }
 
     /**************************************************************************
      * ADVERTISING SET
      *************************************************************************/
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADVERTISE)
     void startAdvertisingSet(AdvertisingSetParameters parameters, AdvertiseData advertiseData,
             AdvertiseData scanResponse, PeriodicAdvertisingParameters periodicParameters,
             AdvertiseData periodicData, int duration, int maxExtAdvEvents,
-            IAdvertisingSetCallback callback) {
-        enforceAdminPermission();
+            IAdvertisingSetCallback callback, AttributionSource attributionSource) {
+        if (!Utils.checkAdvertisePermissionForDataDelivery(
+                this, attributionSource, "GattService startAdvertisingSet")) {
+            return;
+        }
         mAdvertiseManager.startAdvertisingSet(parameters, advertiseData, scanResponse,
                 periodicParameters, periodicData, duration, maxExtAdvEvents, callback);
     }
 
-    void stopAdvertisingSet(IAdvertisingSetCallback callback) {
-        enforceAdminPermission();
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADVERTISE)
+    void stopAdvertisingSet(IAdvertisingSetCallback callback, AttributionSource attributionSource) {
+        if (!Utils.checkAdvertisePermissionForDataDelivery(
+                this, attributionSource, "GattService stopAdvertisingSet")) {
+            return;
+        }
         mAdvertiseManager.stopAdvertisingSet(callback);
     }
 
-    void getOwnAddress(int advertiserId) {
+    @RequiresPermission(allOf = {
+            android.Manifest.permission.BLUETOOTH_ADVERTISE,
+            android.Manifest.permission.BLUETOOTH_PRIVILEGED,
+    })
+    void getOwnAddress(int advertiserId, AttributionSource attributionSource) {
+        if (!Utils.checkAdvertisePermissionForDataDelivery(
+                this, attributionSource, "GattService getOwnAddress")) {
+            return;
+        }
         enforcePrivilegedPermission();
         mAdvertiseManager.getOwnAddress(advertiserId);
     }
 
-    void enableAdvertisingSet(int advertiserId, boolean enable, int duration, int maxExtAdvEvents) {
-        enforceAdminPermission();
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADVERTISE)
+    void enableAdvertisingSet(int advertiserId, boolean enable, int duration, int maxExtAdvEvents,
+            AttributionSource attributionSource) {
+        if (!Utils.checkAdvertisePermissionForDataDelivery(
+                this, attributionSource, "GattService enableAdvertisingSet")) {
+            return;
+        }
         mAdvertiseManager.enableAdvertisingSet(advertiserId, enable, duration, maxExtAdvEvents);
     }
 
-    void setAdvertisingData(int advertiserId, AdvertiseData data) {
-        enforceAdminPermission();
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADVERTISE)
+    void setAdvertisingData(
+            int advertiserId, AdvertiseData data, AttributionSource attributionSource) {
+        if (!Utils.checkAdvertisePermissionForDataDelivery(
+                this, attributionSource, "GattService setAdvertisingData")) {
+            return;
+        }
         mAdvertiseManager.setAdvertisingData(advertiserId, data);
     }
 
-    void setScanResponseData(int advertiserId, AdvertiseData data) {
-        enforceAdminPermission();
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADVERTISE)
+    void setScanResponseData(
+            int advertiserId, AdvertiseData data, AttributionSource attributionSource) {
+        if (!Utils.checkAdvertisePermissionForDataDelivery(
+                this, attributionSource, "GattService setScanResponseData")) {
+            return;
+        }
         mAdvertiseManager.setScanResponseData(advertiserId, data);
     }
 
-    void setAdvertisingParameters(int advertiserId, AdvertisingSetParameters parameters) {
-        enforceAdminPermission();
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADVERTISE)
+    void setAdvertisingParameters(int advertiserId, AdvertisingSetParameters parameters,
+            AttributionSource attributionSource) {
+        if (!Utils.checkAdvertisePermissionForDataDelivery(
+                this, attributionSource, "GattService setAdvertisingParameters")) {
+            return;
+        }
         mAdvertiseManager.setAdvertisingParameters(advertiserId, parameters);
     }
 
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADVERTISE)
     void setPeriodicAdvertisingParameters(int advertiserId,
-            PeriodicAdvertisingParameters parameters) {
-        enforceAdminPermission();
+            PeriodicAdvertisingParameters parameters, AttributionSource attributionSource) {
+        if (!Utils.checkAdvertisePermissionForDataDelivery(
+                this, attributionSource, "GattService setPeriodicAdvertisingParameters")) {
+            return;
+        }
         mAdvertiseManager.setPeriodicAdvertisingParameters(advertiserId, parameters);
     }
 
-    void setPeriodicAdvertisingData(int advertiserId, AdvertiseData data) {
-        enforceAdminPermission();
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADVERTISE)
+    void setPeriodicAdvertisingData(
+            int advertiserId, AdvertiseData data, AttributionSource attributionSource) {
+        if (!Utils.checkAdvertisePermissionForDataDelivery(
+                this, attributionSource, "GattService setPeriodicAdvertisingData")) {
+            return;
+        }
         mAdvertiseManager.setPeriodicAdvertisingData(advertiserId, data);
     }
 
-    void setPeriodicAdvertisingEnable(int advertiserId, boolean enable) {
-        enforceAdminPermission();
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADVERTISE)
+    void setPeriodicAdvertisingEnable(
+            int advertiserId, boolean enable, AttributionSource attributionSource) {
+        if (!Utils.checkAdvertisePermissionForDataDelivery(
+                this, attributionSource, "GattService setPeriodicAdvertisingEnable")) {
+            return;
+        }
         mAdvertiseManager.setPeriodicAdvertisingEnable(advertiserId, enable);
     }
 
@@ -2350,18 +2614,27 @@
      * GATT Service functions - CLIENT
      *************************************************************************/
 
-    void registerClient(UUID uuid, IBluetoothGattCallback callback) {
-        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
+    void registerClient(UUID uuid, IBluetoothGattCallback callback, boolean eatt_support,
+            AttributionSource attributionSource) {
+        if (!Utils.checkConnectPermissionForDataDelivery(
+                this, attributionSource, "GattService registerClient")) {
+            return;
+        }
 
         if (DBG) {
             Log.d(TAG, "registerClient() - UUID=" + uuid);
         }
         mClientMap.add(uuid, null, callback, null, this);
-        gattClientRegisterAppNative(uuid.getLeastSignificantBits(), uuid.getMostSignificantBits());
+        gattClientRegisterAppNative(uuid.getLeastSignificantBits(), uuid.getMostSignificantBits(), eatt_support);
     }
 
-    void unregisterClient(int clientIf) {
-        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
+    void unregisterClient(int clientIf, AttributionSource attributionSource) {
+        if (!Utils.checkConnectPermissionForDataDelivery(
+                this, attributionSource, "GattService unregisterClient")) {
+            return;
+        }
 
         if (DBG) {
             Log.d(TAG, "unregisterClient() - clientIf=" + clientIf);
@@ -2370,9 +2643,13 @@
         gattClientUnregisterAppNative(clientIf);
     }
 
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     void clientConnect(int clientIf, String address, boolean isDirect, int transport,
-            boolean opportunistic, int phy) {
-        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+            boolean opportunistic, int phy, AttributionSource attributionSource) {
+        if (!Utils.checkConnectPermissionForDataDelivery(
+                this, attributionSource, "GattService clientConnect")) {
+            return;
+        }
 
         if (DBG) {
             Log.d(TAG, "clientConnect() - address=" + address + ", isDirect=" + isDirect
@@ -2381,8 +2658,12 @@
         gattClientConnectNative(clientIf, address, isDirect, transport, opportunistic, phy);
     }
 
-    void clientDisconnect(int clientIf, String address) {
-        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
+    void clientDisconnect(int clientIf, String address, AttributionSource attributionSource) {
+        if (!Utils.checkConnectPermissionForDataDelivery(
+                this, attributionSource, "GattService clientDisconnect")) {
+            return;
+        }
 
         Integer connId = mClientMap.connIdByAddress(clientIf, address);
         if (DBG) {
@@ -2392,8 +2673,13 @@
         gattClientDisconnectNative(clientIf, address, connId != null ? connId : 0);
     }
 
-    void clientSetPreferredPhy(int clientIf, String address, int txPhy, int rxPhy, int phyOptions) {
-        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
+    void clientSetPreferredPhy(int clientIf, String address, int txPhy, int rxPhy, int phyOptions,
+            AttributionSource attributionSource) {
+        if (!Utils.checkConnectPermissionForDataDelivery(
+                this, attributionSource, "GattService clientSetPreferredPhy")) {
+            return;
+        }
 
         Integer connId = mClientMap.connIdByAddress(clientIf, address);
         if (connId == null) {
@@ -2409,8 +2695,12 @@
         gattClientSetPreferredPhyNative(clientIf, address, txPhy, rxPhy, phyOptions);
     }
 
-    void clientReadPhy(int clientIf, String address) {
-        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
+    void clientReadPhy(int clientIf, String address, AttributionSource attributionSource) {
+        if (!Utils.checkConnectPermissionForDataDelivery(
+                this, attributionSource, "GattService clientReadPhy")) {
+            return;
+        }
 
         Integer connId = mClientMap.connIdByAddress(clientIf, address);
         if (connId == null) {
@@ -2426,13 +2716,22 @@
         gattClientReadPhyNative(clientIf, address);
     }
 
-    int numHwTrackFiltersAvailable() {
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
+    int numHwTrackFiltersAvailable(AttributionSource attributionSource) {
+        if (!Utils.checkConnectPermissionForDataDelivery(
+                this, attributionSource, "GattService numHwTrackFiltersAvailable")) {
+            return 0;
+        }
         return (AdapterService.getAdapterService().getTotalNumOfTrackableAdvertisements()
                 - mScanManager.getCurrentUsedTrackingAdvertisement());
     }
 
-    synchronized List<ParcelUuid> getRegisteredServiceUuids() {
-        Utils.enforceAdminPermission(this);
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
+    synchronized List<ParcelUuid> getRegisteredServiceUuids(AttributionSource attributionSource) {
+        if (!Utils.checkConnectPermissionForDataDelivery(
+                this, attributionSource, "GattService getRegisteredServiceUuids")) {
+            return new ArrayList<>(0);
+        }
         List<ParcelUuid> serviceUuids = new ArrayList<ParcelUuid>();
         for (HandleMap.Entry entry : mHandleMap.mEntries) {
             serviceUuids.add(new ParcelUuid(entry.uuid));
@@ -2440,8 +2739,12 @@
         return serviceUuids;
     }
 
-    List<String> getConnectedDevices() {
-        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
+    List<String> getConnectedDevices(AttributionSource attributionSource) {
+        if (!Utils.checkConnectPermissionForDataDelivery(
+                this, attributionSource, "GattService getConnectedDevices")) {
+            return new ArrayList<>(0);
+        }
 
         Set<String> connectedDevAddress = new HashSet<String>();
         connectedDevAddress.addAll(mClientMap.getConnectedDevices());
@@ -2450,8 +2753,12 @@
         return connectedDeviceList;
     }
 
-    void refreshDevice(int clientIf, String address) {
-        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
+    void refreshDevice(int clientIf, String address, AttributionSource attributionSource) {
+        if (!Utils.checkConnectPermissionForDataDelivery(
+                this, attributionSource, "GattService refreshDevice")) {
+            return;
+        }
 
         if (DBG) {
             Log.d(TAG, "refreshDevice() - address=" + address);
@@ -2459,8 +2766,12 @@
         gattClientRefreshNative(clientIf, address);
     }
 
-    void discoverServices(int clientIf, String address) {
-        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
+    void discoverServices(int clientIf, String address, AttributionSource attributionSource) {
+        if (!Utils.checkConnectPermissionForDataDelivery(
+                this, attributionSource, "GattService discoverServices")) {
+            return;
+        }
 
         Integer connId = mClientMap.connIdByAddress(clientIf, address);
         if (DBG) {
@@ -2474,8 +2785,13 @@
         }
     }
 
-    void discoverServiceByUuid(int clientIf, String address, UUID uuid) {
-        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
+    void discoverServiceByUuid(
+            int clientIf, String address, UUID uuid, AttributionSource attributionSource) {
+        if (!Utils.checkConnectPermissionForDataDelivery(
+                this, attributionSource, "GattService discoverServiceByUuid")) {
+            return;
+        }
 
         Integer connId = mClientMap.connIdByAddress(clientIf, address);
         if (connId != null) {
@@ -2486,8 +2802,13 @@
         }
     }
 
-    void readCharacteristic(int clientIf, String address, int handle, int authReq) {
-        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
+    void readCharacteristic(int clientIf, String address, int handle, int authReq,
+            AttributionSource attributionSource) {
+        if (!Utils.checkConnectPermissionForDataDelivery(
+                this, attributionSource, "GattService readCharacteristic")) {
+            return;
+        }
 
         if (VDBG) {
             Log.d(TAG, "readCharacteristic() - address=" + address);
@@ -2507,9 +2828,13 @@
         gattClientReadCharacteristicNative(connId, handle, authReq);
     }
 
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     void readUsingCharacteristicUuid(int clientIf, String address, UUID uuid, int startHandle,
-            int endHandle, int authReq) {
-        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+            int endHandle, int authReq, AttributionSource attributionSource) {
+        if (!Utils.checkConnectPermissionForDataDelivery(
+                this, attributionSource, "GattService readUsingCharacteristicUuid")) {
+            return;
+        }
 
         if (VDBG) {
             Log.d(TAG, "readUsingCharacteristicUuid() - address=" + address);
@@ -2530,9 +2855,13 @@
                 uuid.getMostSignificantBits(), startHandle, endHandle, authReq);
     }
 
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     void writeCharacteristic(int clientIf, String address, int handle, int writeType, int authReq,
-            byte[] value) {
-        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+            byte[] value, AttributionSource attributionSource) {
+        if (!Utils.checkConnectPermissionForDataDelivery(
+                this, attributionSource, "GattService writeCharacteristic")) {
+            return;
+        }
 
         if (VDBG) {
             Log.d(TAG, "writeCharacteristic() - address=" + address);
@@ -2556,8 +2885,13 @@
         gattClientWriteCharacteristicNative(connId, handle, writeType, authReq, value);
     }
 
-    void readDescriptor(int clientIf, String address, int handle, int authReq) {
-        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
+    void readDescriptor(int clientIf, String address, int handle, int authReq,
+            AttributionSource attributionSource) {
+        if (!Utils.checkConnectPermissionForDataDelivery(
+                this, attributionSource, "GattService readDescriptor")) {
+            return;
+        }
 
         if (VDBG) {
             Log.d(TAG, "readDescriptor() - address=" + address);
@@ -2577,10 +2911,13 @@
         gattClientReadDescriptorNative(connId, handle, authReq);
     }
 
-    ;
-
-    void writeDescriptor(int clientIf, String address, int handle, int authReq, byte[] value) {
-        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
+    void writeDescriptor(int clientIf, String address, int handle, int authReq, byte[] value,
+            AttributionSource attributionSource) {
+        if (!Utils.checkConnectPermissionForDataDelivery(
+                this, attributionSource, "GattService writeDescriptor")) {
+            return;
+        }
         if (VDBG) {
             Log.d(TAG, "writeDescriptor() - address=" + address);
         }
@@ -2599,8 +2936,12 @@
         gattClientWriteDescriptorNative(connId, handle, authReq, value);
     }
 
-    void beginReliableWrite(int clientIf, String address) {
-        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
+    void beginReliableWrite(int clientIf, String address, AttributionSource attributionSource) {
+        if (!Utils.checkConnectPermissionForDataDelivery(
+                this, attributionSource, "GattService beginReliableWrite")) {
+            return;
+        }
 
         if (DBG) {
             Log.d(TAG, "beginReliableWrite() - address=" + address);
@@ -2608,8 +2949,13 @@
         mReliableQueue.add(address);
     }
 
-    void endReliableWrite(int clientIf, String address, boolean execute) {
-        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
+    void endReliableWrite(
+            int clientIf, String address, boolean execute, AttributionSource attributionSource) {
+        if (!Utils.checkConnectPermissionForDataDelivery(
+                this, attributionSource, "GattService endReliableWrite")) {
+            return;
+        }
 
         if (DBG) {
             Log.d(TAG, "endReliableWrite() - address=" + address + " execute: " + execute);
@@ -2622,8 +2968,13 @@
         }
     }
 
-    void registerForNotification(int clientIf, String address, int handle, boolean enable) {
-        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
+    void registerForNotification(int clientIf, String address, int handle, boolean enable,
+            AttributionSource attributionSource) {
+        if (!Utils.checkConnectPermissionForDataDelivery(
+                this, attributionSource, "GattService registerForNotification")) {
+            return;
+        }
 
         if (DBG) {
             Log.d(TAG, "registerForNotification() - address=" + address + " enable: " + enable);
@@ -2643,8 +2994,12 @@
         gattClientRegisterForNotificationsNative(clientIf, address, handle, enable);
     }
 
-    void readRemoteRssi(int clientIf, String address) {
-        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
+    void readRemoteRssi(int clientIf, String address, AttributionSource attributionSource) {
+        if (!Utils.checkConnectPermissionForDataDelivery(
+                this, attributionSource, "GattService readRemoteRssi")) {
+            return;
+        }
 
         if (DBG) {
             Log.d(TAG, "readRemoteRssi() - address=" + address);
@@ -2652,8 +3007,12 @@
         gattClientReadRemoteRssiNative(clientIf, address);
     }
 
-    void configureMTU(int clientIf, String address, int mtu) {
-        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
+    void configureMTU(int clientIf, String address, int mtu, AttributionSource attributionSource) {
+        if (!Utils.checkConnectPermissionForDataDelivery(
+                this, attributionSource, "GattService configureMTU")) {
+            return;
+        }
 
         if (DBG) {
             Log.d(TAG, "configureMTU() - address=" + address + " mtu=" + mtu);
@@ -2666,13 +3025,18 @@
         }
     }
 
-    void connectionParameterUpdate(int clientIf, String address, int connectionPriority) {
-        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
+    void connectionParameterUpdate(int clientIf, String address, int connectionPriority,
+            AttributionSource attributionSource) {
+        if (!Utils.checkConnectPermissionForDataDelivery(
+                this, attributionSource, "GattService connectionParameterUpdate")) {
+            return;
+        }
 
         int minInterval;
         int maxInterval;
 
-        // Slave latency
+        // Peripheral latency
         int latency;
 
         // Link supervision timeout is measured in N * 10ms
@@ -2709,22 +3073,26 @@
                 timeout, 0, 0);
     }
 
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     void leConnectionUpdate(int clientIf, String address, int minInterval,
-                            int maxInterval, int slaveLatency,
+                            int maxInterval, int peripheralLatency,
                             int supervisionTimeout, int minConnectionEventLen,
-                            int maxConnectionEventLen) {
-        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+                            int maxConnectionEventLen, AttributionSource attributionSource) {
+        if (!Utils.checkConnectPermissionForDataDelivery(
+                this, attributionSource, "GattService leConnectionUpdate")) {
+            return;
+        }
 
         if (DBG) {
             Log.d(TAG, "leConnectionUpdate() - address=" + address + ", intervals="
-                        + minInterval + "/" + maxInterval + ", latency=" + slaveLatency
+                        + minInterval + "/" + maxInterval + ", latency=" + peripheralLatency
                         + ", timeout=" + supervisionTimeout + "msec" + ", min_ce="
                         + minConnectionEventLen + ", max_ce=" + maxConnectionEventLen);
 
 
         }
         gattConnectionParameterUpdateNative(clientIf, address, minInterval, maxInterval,
-                                            slaveLatency, supervisionTimeout,
+                                            peripheralLatency, supervisionTimeout,
                                             minConnectionEventLen, maxConnectionEventLen);
     }
 
@@ -3015,18 +3383,27 @@
      * GATT Service functions - SERVER
      *************************************************************************/
 
-    void registerServer(UUID uuid, IBluetoothGattServerCallback callback) {
-        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
+    void registerServer(UUID uuid, IBluetoothGattServerCallback callback, boolean eatt_support,
+            AttributionSource attributionSource) {
+        if (!Utils.checkConnectPermissionForDataDelivery(
+                this, attributionSource, "GattService registerServer")) {
+            return;
+        }
 
         if (DBG) {
             Log.d(TAG, "registerServer() - UUID=" + uuid);
         }
         mServerMap.add(uuid, null, callback, null, this);
-        gattServerRegisterAppNative(uuid.getLeastSignificantBits(), uuid.getMostSignificantBits());
+        gattServerRegisterAppNative(uuid.getLeastSignificantBits(), uuid.getMostSignificantBits(), eatt_support);
     }
 
-    void unregisterServer(int serverIf) {
-        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
+    void unregisterServer(int serverIf, AttributionSource attributionSource) {
+        if (!Utils.checkConnectPermissionForDataDelivery(
+                this, attributionSource, "GattService unregisterServer")) {
+            return;
+        }
 
         if (DBG) {
             Log.d(TAG, "unregisterServer() - serverIf=" + serverIf);
@@ -3038,8 +3415,13 @@
         gattServerUnregisterAppNative(serverIf);
     }
 
-    void serverConnect(int serverIf, String address, boolean isDirect, int transport) {
-        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
+    void serverConnect(int serverIf, String address, boolean isDirect, int transport,
+            AttributionSource attributionSource) {
+        if (!Utils.checkConnectPermissionForDataDelivery(
+                this, attributionSource, "GattService serverConnect")) {
+            return;
+        }
 
         if (DBG) {
             Log.d(TAG, "serverConnect() - address=" + address);
@@ -3047,8 +3429,12 @@
         gattServerConnectNative(serverIf, address, isDirect, transport);
     }
 
-    void serverDisconnect(int serverIf, String address) {
-        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
+    void serverDisconnect(int serverIf, String address, AttributionSource attributionSource) {
+        if (!Utils.checkConnectPermissionForDataDelivery(
+                this, attributionSource, "GattService serverDisconnect")) {
+            return;
+        }
 
         Integer connId = mServerMap.connIdByAddress(serverIf, address);
         if (DBG) {
@@ -3058,8 +3444,13 @@
         gattServerDisconnectNative(serverIf, address, connId != null ? connId : 0);
     }
 
-    void serverSetPreferredPhy(int serverIf, String address, int txPhy, int rxPhy, int phyOptions) {
-        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
+    void serverSetPreferredPhy(int serverIf, String address, int txPhy, int rxPhy, int phyOptions,
+            AttributionSource attributionSource) {
+        if (!Utils.checkConnectPermissionForDataDelivery(
+                this, attributionSource, "GattService serverSetPreferredPhy")) {
+            return;
+        }
 
         Integer connId = mServerMap.connIdByAddress(serverIf, address);
         if (connId == null) {
@@ -3075,8 +3466,12 @@
         gattServerSetPreferredPhyNative(serverIf, address, txPhy, rxPhy, phyOptions);
     }
 
-    void serverReadPhy(int serverIf, String address) {
-        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
+    void serverReadPhy(int serverIf, String address, AttributionSource attributionSource) {
+        if (!Utils.checkConnectPermissionForDataDelivery(
+                this, attributionSource, "GattService serverReadPhy")) {
+            return;
+        }
 
         Integer connId = mServerMap.connIdByAddress(serverIf, address);
         if (connId == null) {
@@ -3092,8 +3487,13 @@
         gattServerReadPhyNative(serverIf, address);
     }
 
-    void addService(int serverIf, BluetoothGattService service) {
-        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
+    void addService(
+            int serverIf, BluetoothGattService service, AttributionSource attributionSource) {
+        if (!Utils.checkConnectPermissionForDataDelivery(
+                this, attributionSource, "GattService addService")) {
+            return;
+        }
 
         if (DBG) {
             Log.d(TAG, "addService() - uuid=" + service.getUuid());
@@ -3134,8 +3534,12 @@
         gattServerAddServiceNative(serverIf, db);
     }
 
-    void removeService(int serverIf, int handle) {
-        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
+    void removeService(int serverIf, int handle, AttributionSource attributionSource) {
+        if (!Utils.checkConnectPermissionForDataDelivery(
+                this, attributionSource, "GattService removeService")) {
+            return;
+        }
 
         if (DBG) {
             Log.d(TAG, "removeService() - handle=" + handle);
@@ -3144,8 +3548,12 @@
         gattServerDeleteServiceNative(serverIf, handle);
     }
 
-    void clearServices(int serverIf) {
-        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
+    void clearServices(int serverIf, AttributionSource attributionSource) {
+        if (!Utils.checkConnectPermissionForDataDelivery(
+                this, attributionSource, "GattService clearServices")) {
+            return;
+        }
 
         if (DBG) {
             Log.d(TAG, "clearServices()");
@@ -3153,9 +3561,13 @@
         deleteServices(serverIf);
     }
 
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     void sendResponse(int serverIf, String address, int requestId, int status, int offset,
-            byte[] value) {
-        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+            byte[] value, AttributionSource attributionSource) {
+        if (!Utils.checkConnectPermissionForDataDelivery(
+                this, attributionSource, "GattService sendResponse")) {
+            return;
+        }
 
         if (VDBG) {
             Log.d(TAG, "sendResponse() - address=" + address);
@@ -3173,8 +3585,13 @@
         mHandleMap.deleteRequest(requestId);
     }
 
-    void sendNotification(int serverIf, String address, int handle, boolean confirm, byte[] value) {
-        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
+    void sendNotification(int serverIf, String address, int handle, boolean confirm, byte[] value,
+            AttributionSource attributionSource) {
+        if (!Utils.checkConnectPermissionForDataDelivery(
+                this, attributionSource, "GattService sendNotification")) {
+            return;
+        }
 
         if (VDBG) {
             Log.d(TAG, "sendNotification() - address=" + address + " handle=" + handle);
@@ -3226,14 +3643,9 @@
         return type;
     }
 
-    private void enforceAdminPermission() {
-        enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission");
-    }
-
     private boolean needsPrivilegedPermissionForScan(ScanSettings settings) {
-        BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
         // BLE scan only mode needs special permission.
-        if (adapter.getState() != BluetoothAdapter.STATE_ON) {
+        if (mAdapterService.getState() != BluetoothAdapter.STATE_ON) {
             return true;
         }
 
@@ -3242,6 +3654,11 @@
             return false;
         }
 
+        // Ambient discovery mode, needs privileged permission.
+        if (settings.getScanMode() == ScanSettings.SCAN_MODE_AMBIENT_DISCOVERY) {
+            return true;
+        }
+
         // Regular scan, no special permission.
         if (settings.getReportDelayMillis() == 0) {
             return false;
@@ -3251,21 +3668,105 @@
         return settings.getScanResultType() == ScanSettings.SCAN_RESULT_TYPE_ABBREVIATED;
     }
 
+    /*
+     * The {@link ScanFilter#setDeviceAddress} API overloads are @SystemApi access methods.  This
+     * requires that the permissions be BLUETOOTH_PRIVILEGED.
+     */
+    @SuppressLint("AndroidFrameworkRequiresPermission")
+    private void enforcePrivilegedPermissionIfNeeded(List<ScanFilter> filters) {
+        if (DBG) {
+            Log.d(TAG, "enforcePrivilegedPermissionIfNeeded(" + filters + ")");
+        }
+        // Some 3p API cases may have null filters, need to allow
+        if (filters != null) {
+            for (ScanFilter filter : filters) {
+                // The only case to enforce here is if there is an address
+                // If there is an address, enforce if the correct combination criteria is met.
+                if (filter.getDeviceAddress() != null) {
+                    // At this point we have an address, that means a caller used the
+                    // setDeviceAddress(address) public API for the ScanFilter
+                    // We don't want to enforce if the type is PUBLIC and the IRK is null
+                    // However, if we have a different type that means the caller used a new
+                    // @SystemApi such as setDeviceAddress(address, type) or
+                    // setDeviceAddress(address, type, irk) which are both @SystemApi and require
+                    // permissions to be enforced
+                    if (filter.getAddressType()
+                            == BluetoothDevice.ADDRESS_TYPE_PUBLIC && filter.getIrk() == null) {
+                        // Do not enforce
+                    } else {
+                        enforcePrivilegedPermission();
+                    }
+                }
+            }
+        }
+    }
+
     // Enforce caller has BLUETOOTH_PRIVILEGED permission. A {@link SecurityException} will be
     // thrown if the caller app does not have BLUETOOTH_PRIVILEGED permission.
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED)
     private void enforcePrivilegedPermission() {
         enforceCallingOrSelfPermission(BLUETOOTH_PRIVILEGED,
                 "Need BLUETOOTH_PRIVILEGED permission");
     }
 
+    @SuppressLint("AndroidFrameworkRequiresPermission")
+    private void enforcePrivilegedPermissionIfNeeded(ScanSettings settings) {
+        if (needsPrivilegedPermissionForScan(settings)) {
+            enforcePrivilegedPermission();
+        }
+    }
+
     // Enforce caller has UPDATE_DEVICE_STATS permission, which allows the caller to blame other
     // apps for Bluetooth usage. A {@link SecurityException} will be thrown if the caller app does
     // not have UPDATE_DEVICE_STATS permission.
+    @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_STATS)
     private void enforceImpersonatationPermission() {
         enforceCallingOrSelfPermission(android.Manifest.permission.UPDATE_DEVICE_STATS,
                 "Need UPDATE_DEVICE_STATS permission");
     }
 
+    @SuppressLint("AndroidFrameworkRequiresPermission")
+    private void enforceImpersonatationPermissionIfNeeded(WorkSource workSource) {
+        if (workSource != null) {
+            enforceImpersonatationPermission();
+        }
+    }
+
+    /**
+     * Ensures the report delay is either 0 or at least the floor value (5000ms)
+     *
+     * @param  settings are the scan settings passed into a request to start le scanning
+     * @return the passed in ScanSettings object if the report delay is 0 or above the floor value;
+     *         a new ScanSettings object with the report delay being the floor value if the original
+     *         report delay was between 0 and the floor value (exclusive of both)
+     */
+    private ScanSettings enforceReportDelayFloor(ScanSettings settings) {
+        if (settings.getReportDelayMillis() == 0) {
+            return settings;
+        }
+
+        // Need to clear identity to pass device config permission check
+        long callerToken = Binder.clearCallingIdentity();
+        long floor = DeviceConfig.getLong(DeviceConfig.NAMESPACE_BLUETOOTH, "report_delay",
+                DEFAULT_REPORT_DELAY_FLOOR);
+        Binder.restoreCallingIdentity(callerToken);
+
+        if (settings.getReportDelayMillis() > floor) {
+            return settings;
+        } else {
+            return new ScanSettings.Builder()
+                    .setCallbackType(settings.getCallbackType())
+                    .setLegacy(settings.getLegacy())
+                    .setMatchMode(settings.getMatchMode())
+                    .setNumOfMatches(settings.getNumOfMatches())
+                    .setPhy(settings.getPhy())
+                    .setReportDelay(floor)
+                    .setScanMode(settings.getScanMode())
+                    .setScanResultType(settings.getScanResultType())
+                    .build();
+        }
+    }
+
     private void stopNextService(int serverIf, int status) throws RemoteException {
         if (DBG) {
             Log.d(TAG, "stopNextService() - serverIf=" + serverIf + ", status=" + status);
@@ -3309,38 +3810,6 @@
         }
     }
 
-    private List<UUID> parseUuids(byte[] advData) {
-        List<UUID> uuids = new ArrayList<UUID>();
-
-        int offset = 0;
-        while (offset < (advData.length - 2)) {
-            int len = Byte.toUnsignedInt(advData[offset++]);
-            if (len == 0) {
-                break;
-            }
-
-            int type = advData[offset++];
-            switch (type) {
-                case 0x02: // Partial list of 16-bit UUIDs
-                case 0x03: // Complete list of 16-bit UUIDs
-                    while (len > 1) {
-                        int uuid16 = advData[offset++];
-                        uuid16 += (advData[offset++] << 8);
-                        len -= 2;
-                        uuids.add(UUID.fromString(
-                                String.format("%08x-0000-1000-8000-00805f9b34fb", uuid16)));
-                    }
-                    break;
-
-                default:
-                    offset += (len - 1);
-                    break;
-            }
-        }
-
-        return uuids;
-    }
-
     void dumpRegisterId(StringBuilder sb) {
         sb.append("  Scanner:\n");
         for (Integer appId : mScannerMap.getAllAppsIds()) {
@@ -3431,7 +3900,7 @@
 
     private native int gattClientGetDeviceTypeNative(String address);
 
-    private native void gattClientRegisterAppNative(long appUuidLsb, long appUuidMsb);
+    private native void gattClientRegisterAppNative(long appUuidLsb, long appUuidMsb, boolean eatt_support);
 
     private native void gattClientUnregisterAppNative(int clientIf);
 
@@ -3481,7 +3950,7 @@
             int minInterval, int maxInterval, int latency, int timeout, int minConnectionEventLen,
             int maxConnectionEventLen);
 
-    private native void gattServerRegisterAppNative(long appUuidLsb, long appUuidMsb);
+    private native void gattServerRegisterAppNative(long appUuidLsb, long appUuidMsb, boolean eatt_support);
 
     private native void gattServerUnregisterAppNative(int serverIf);
 
diff --git a/src/com/android/bluetooth/gatt/ScanClient.java b/src/com/android/bluetooth/gatt/ScanClient.java
index 9f17cc0..5ed3f2b 100644
--- a/src/com/android/bluetooth/gatt/ScanClient.java
+++ b/src/com/android/bluetooth/gatt/ScanClient.java
@@ -24,7 +24,6 @@
 
 import java.util.List;
 import java.util.Objects;
-import java.util.UUID;
 
 /**
  * Helper class identifying a client that has requested LE scan results.
@@ -33,7 +32,6 @@
  */
 /* package */class ScanClient {
     public int scannerId;
-    public UUID[] uuids;
     public ScanSettings settings;
     public ScanSettings passiveSettings;
     public int appUid;
@@ -48,6 +46,7 @@
     public boolean hasNetworkSettingsPermission;
     public boolean hasNetworkSetupWizardPermission;
     public boolean hasScanWithoutLocationPermission;
+    public boolean hasDisavowedLocation;
     public List<String> associatedDevices;
 
     public AppScanStats stats = null;
@@ -56,26 +55,16 @@
             new ScanSettings.Builder().setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY).build();
 
     ScanClient(int scannerId) {
-        this(scannerId, new UUID[0], DEFAULT_SCAN_SETTINGS, null, null);
-    }
-
-    ScanClient(int scannerId, UUID[] uuids) {
-        this(scannerId, uuids, DEFAULT_SCAN_SETTINGS, null, null);
+        this(scannerId, DEFAULT_SCAN_SETTINGS, null, null);
     }
 
     ScanClient(int scannerId, ScanSettings settings, List<ScanFilter> filters) {
-        this(scannerId, new UUID[0], settings, filters, null);
+        this(scannerId, settings, filters, null);
     }
 
     ScanClient(int scannerId, ScanSettings settings, List<ScanFilter> filters,
             List<List<ResultStorageDescriptor>> storages) {
-        this(scannerId, new UUID[0], settings, filters, storages);
-    }
-
-    private ScanClient(int scannerId, UUID[] uuids, ScanSettings settings, List<ScanFilter> filters,
-            List<List<ResultStorageDescriptor>> storages) {
         this.scannerId = scannerId;
-        this.uuids = uuids;
         this.settings = settings;
         this.passiveSettings = null;
         this.filters = filters;
diff --git a/src/com/android/bluetooth/gatt/ScanFilterQueue.java b/src/com/android/bluetooth/gatt/ScanFilterQueue.java
index 6c47711..4572c89 100644
--- a/src/com/android/bluetooth/gatt/ScanFilterQueue.java
+++ b/src/com/android/bluetooth/gatt/ScanFilterQueue.java
@@ -50,6 +50,7 @@
         public byte type;
         public String address;
         public byte addr_type;
+        public byte[] irk;
         public UUID uuid;
         public UUID uuid_mask;
         public String name;
@@ -61,11 +62,12 @@
 
     private Set<Entry> mEntries = new HashSet<Entry>();
 
-    void addDeviceAddress(String address, byte type) {
+    void addDeviceAddress(String address, byte type, byte[] irk) {
         Entry entry = new Entry();
         entry.type = TYPE_DEVICE_ADDRESS;
         entry.address = address;
         entry.addr_type = type;
+        entry.irk = irk;
         mEntries.add(entry);
     }
 
@@ -179,7 +181,13 @@
             addName(filter.getDeviceName());
         }
         if (filter.getDeviceAddress() != null) {
-            addDeviceAddress(filter.getDeviceAddress(), DEVICE_TYPE_ALL);
+            byte addressType = (byte) filter.getAddressType();
+            // If addressType == iADDRESS_TYPE_PUBLIC (0) then this is the original
+            // setDeviceAddress(address) API path which provided DEVICE_TYPE_ALL (2) which might map
+            // to the stack value for address type of BTM_BLE_STATIC (2)
+            // Additionally, we shouldn't confuse device type with address type.
+            addDeviceAddress(filter.getDeviceAddress(),
+                    ((addressType == 0) ? DEVICE_TYPE_ALL : addressType), filter.getIrk());
         }
         if (filter.getServiceUuid() != null) {
             if (filter.getServiceUuidMask() == null) {
diff --git a/src/com/android/bluetooth/gatt/ScanManager.java b/src/com/android/bluetooth/gatt/ScanManager.java
index 274bbfe..6688184 100644
--- a/src/com/android/bluetooth/gatt/ScanManager.java
+++ b/src/com/android/bluetooth/gatt/ScanManager.java
@@ -16,6 +16,7 @@
 
 package com.android.bluetooth.gatt;
 
+import android.annotation.RequiresPermission;
 import android.app.ActivityManager;
 import android.app.AlarmManager;
 import android.app.PendingIntent;
@@ -97,6 +98,7 @@
     private Set<ScanClient> mRegularScanClients;
     private Set<ScanClient> mBatchClients;
     private Set<ScanClient> mSuspendedScanClients;
+    private HashMap<Integer, Integer> mPriorityMap = new HashMap<Integer, Integer>();
 
     private CountDownLatch mLatch;
 
@@ -129,6 +131,12 @@
         mDm = (DisplayManager) mService.getSystemService(Context.DISPLAY_SERVICE);
         mActivityManager = (ActivityManager) mService.getSystemService(Context.ACTIVITY_SERVICE);
         mLocationManager = (LocationManager) mService.getSystemService(Context.LOCATION_SERVICE);
+
+        mPriorityMap.put(ScanSettings.SCAN_MODE_OPPORTUNISTIC, 0);
+        mPriorityMap.put(ScanSettings.SCAN_MODE_LOW_POWER, 1);
+        mPriorityMap.put(ScanSettings.SCAN_MODE_BALANCED, 2);
+        mPriorityMap.put(ScanSettings.SCAN_MODE_AMBIENT_DISCOVERY, 3);
+        mPriorityMap.put(ScanSettings.SCAN_MODE_LOW_LATENCY, 4);
     }
 
     void start() {
@@ -300,8 +308,6 @@
         }
 
         void handleStartScan(ScanClient client) {
-            Utils.enforceAdminPermission(mService);
-            boolean isFiltered = (client.filters != null) && !client.filters.isEmpty();
             if (DBG) {
                 Log.d(TAG, "handling starting scan");
             }
@@ -316,7 +322,7 @@
                 return;
             }
 
-            if (!mScanNative.isOpportunisticScanClient(client) && !isScreenOn() && !isFiltered) {
+            if (requiresScreenOn(client) && !isScreenOn()) {
                 Log.w(TAG, "Cannot start unfiltered scan in screen-off. This scan will be resumed "
                         + "later: " + client.scannerId);
                 mSuspendedScanClients.add(client);
@@ -327,7 +333,7 @@
             }
 
             final boolean locationEnabled = mLocationManager.isLocationEnabled();
-            if (!locationEnabled && !isFiltered) {
+            if (requiresLocationOn(client) && !locationEnabled) {
                 Log.i(TAG, "Cannot start unfiltered scan in location-off. This scan will be"
                         + " resumed when location is on: " + client.scannerId);
                 mSuspendedScanClients.add(client);
@@ -351,14 +357,24 @@
                         Message msg = obtainMessage(MSG_SCAN_TIMEOUT);
                         msg.obj = client;
                         // Only one timeout message should exist at any time
-                        sendMessageDelayed(msg, AppScanStats.SCAN_TIMEOUT_MS);
+                        sendMessageDelayed(msg, AppScanStats.getScanTimeoutMillis());
                     }
                 }
             }
         }
 
+        private boolean requiresScreenOn(ScanClient client) {
+            boolean isFiltered = (client.filters != null) && !client.filters.isEmpty();
+            return !mScanNative.isOpportunisticScanClient(client) && !isFiltered;
+        }
+
+        private boolean requiresLocationOn(ScanClient client) {
+            boolean isFiltered = (client.filters != null) && !client.filters.isEmpty();
+            return !client.hasDisavowedLocation && !isFiltered;
+        }
+
+        @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN)
         void handleStopScan(ScanClient client) {
-            Utils.enforceAdminPermission(mService);
             if (client == null) {
                 return;
             }
@@ -384,12 +400,11 @@
                 if (DBG) {
                     Log.d(TAG, "app died, unregister scanner - " + client.scannerId);
                 }
-                mService.unregisterScanner(client.scannerId);
+                mService.unregisterScanner(client.scannerId, mService.getAttributionSource());
             }
         }
 
         void handleFlushBatchResults(ScanClient client) {
-            Utils.enforceAdminPermission(mService);
             if (!mBatchClients.contains(client)) {
                 return;
             }
@@ -417,10 +432,11 @@
                     && settings.getReportDelayMillis() == 0;
         }
 
+        @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN)
         void handleSuspendScans() {
             for (ScanClient client : mRegularScanClients) {
-                if (!mScanNative.isOpportunisticScanClient(client) && (client.filters == null
-                        || client.filters.isEmpty())) {
+                if ((requiresScreenOn(client) && !isScreenOn())
+                        || (requiresLocationOn(client) && !mLocationManager.isLocationEnabled())) {
                     /*Suspend unfiltered scans*/
                     if (client.stats != null) {
                         client.stats.recordScanSuspend(client.scannerId);
@@ -433,10 +449,13 @@
 
         void handleResumeScans() {
             for (ScanClient client : mSuspendedScanClients) {
-                if (client.stats != null) {
-                    client.stats.recordScanResume(client.scannerId);
+                if ((!requiresScreenOn(client) || isScreenOn())
+                        && (!requiresLocationOn(client) || mLocationManager.isLocationEnabled())) {
+                    if (client.stats != null) {
+                        client.stats.recordScanResume(client.scannerId);
+                    }
+                    handleStartScan(client);
                 }
-                handleStartScan(client);
             }
             mSuspendedScanClients.clear();
         }
@@ -500,6 +519,8 @@
         private static final int SCAN_MODE_BALANCED_INTERVAL_MS = 4096;
         private static final int SCAN_MODE_LOW_LATENCY_WINDOW_MS = 4096;
         private static final int SCAN_MODE_LOW_LATENCY_INTERVAL_MS = 4096;
+        private static final int SCAN_MODE_AMBIENT_DISCOVERY_WINDOW_MS = 128;
+        private static final int SCAN_MODE_AMBIENT_DISCOVERY_INTERVAL_MS = 640;
 
         /**
          * Onfound/onlost for scan settings
@@ -518,6 +539,8 @@
         private static final int SCAN_MODE_BATCH_BALANCED_INTERVAL_MS = 15000;
         private static final int SCAN_MODE_BATCH_LOW_LATENCY_WINDOW_MS = 1500;
         private static final int SCAN_MODE_BATCH_LOW_LATENCY_INTERVAL_MS = 5000;
+        private static final int SCAN_MODE_BATCH_AMBIENT_DISCOVERY_WINDOW_MS = 1500;
+        private static final int SCAN_MODE_BATCH_AMBIENT_DISCOVERY_INTERVAL_MS = 15000;
 
         // The logic is AND for each filter field.
         private static final int LIST_LOGIC_TYPE = 0x1111111;
@@ -539,7 +562,8 @@
 
             mAlarmManager = (AlarmManager) mService.getSystemService(Context.ALARM_SERVICE);
             Intent batchIntent = new Intent(ACTION_REFRESH_BATCHED_SCAN, null);
-            mBatchScanIntervalIntent = PendingIntent.getBroadcast(mService, 0, batchIntent, 0);
+            mBatchScanIntervalIntent = PendingIntent.getBroadcast(mService, 0, batchIntent,
+                    PendingIntent.FLAG_IMMUTABLE);
             IntentFilter filter = new IntentFilter();
             filter.addAction(ACTION_REFRESH_BATCHED_SCAN);
             mBatchAlarmReceiver = new BroadcastReceiver() {
@@ -618,13 +642,12 @@
 
         ScanClient getAggressiveClient(Set<ScanClient> cList) {
             ScanClient result = null;
-            int curScanSetting = Integer.MIN_VALUE;
+            int currentScanModePriority = Integer.MIN_VALUE;
             for (ScanClient client : cList) {
-                // ScanClient scan settings are assumed to be monotonically increasing in value for
-                // more power hungry(higher duty cycle) operation.
-                if (client.settings.getScanMode() > curScanSetting) {
+                int priority = mPriorityMap.get(client.settings.getScanMode());
+                if (priority > currentScanModePriority) {
                     result = client;
-                    curScanSetting = client.settings.getScanMode();
+                    currentScanModePriority = priority;
                 }
             }
             return result;
@@ -762,6 +785,8 @@
                     return SCAN_MODE_BATCH_BALANCED_WINDOW_MS;
                 case ScanSettings.SCAN_MODE_LOW_POWER:
                     return SCAN_MODE_BATCH_LOW_POWER_WINDOW_MS;
+                case ScanSettings.SCAN_MODE_AMBIENT_DISCOVERY:
+                    return SCAN_MODE_BATCH_AMBIENT_DISCOVERY_WINDOW_MS;
                 default:
                     return SCAN_MODE_BATCH_LOW_POWER_WINDOW_MS;
             }
@@ -775,6 +800,8 @@
                     return SCAN_MODE_BATCH_BALANCED_INTERVAL_MS;
                 case ScanSettings.SCAN_MODE_LOW_POWER:
                     return SCAN_MODE_BATCH_LOW_POWER_INTERVAL_MS;
+                case ScanSettings.SCAN_MODE_AMBIENT_DISCOVERY:
+                    return SCAN_MODE_BATCH_AMBIENT_DISCOVERY_INTERVAL_MS;
                 default:
                     return SCAN_MODE_BATCH_LOW_POWER_INTERVAL_MS;
             }
@@ -1140,6 +1167,8 @@
                         resolver,
                         Settings.Global.BLE_SCAN_LOW_POWER_WINDOW_MS,
                         SCAN_MODE_LOW_POWER_WINDOW_MS);
+                case ScanSettings.SCAN_MODE_AMBIENT_DISCOVERY:
+                    return SCAN_MODE_AMBIENT_DISCOVERY_WINDOW_MS;
                 default:
                     return Settings.Global.getInt(
                         resolver,
@@ -1172,6 +1201,8 @@
                         resolver,
                         Settings.Global.BLE_SCAN_LOW_POWER_INTERVAL_MS,
                         SCAN_MODE_LOW_POWER_INTERVAL_MS);
+                case ScanSettings.SCAN_MODE_AMBIENT_DISCOVERY:
+                    return SCAN_MODE_AMBIENT_DISCOVERY_INTERVAL_MS;
                 default:
                     return Settings.Global.getInt(
                         resolver,
@@ -1327,7 +1358,7 @@
 
                 @Override
                 public void onDisplayChanged(int displayId) {
-                    if (isScreenOn() && mLocationManager.isLocationEnabled()) {
+                    if (isScreenOn()) {
                         sendMessage(MSG_RESUME_SCANS, null);
                     } else {
                         sendMessage(MSG_SUSPEND_SCANS, null);
@@ -1355,7 +1386,7 @@
                     String action = intent.getAction();
                     if (LocationManager.MODE_CHANGED_ACTION.equals(action)) {
                         final boolean locationEnabled = mLocationManager.isLocationEnabled();
-                        if (locationEnabled && isScreenOn()) {
+                        if (locationEnabled) {
                             sendMessage(MSG_RESUME_SCANS, null);
                         } else {
                             sendMessage(MSG_SUSPEND_SCANS, null);
diff --git a/src/com/android/bluetooth/hearingaid/HearingAidNativeInterface.java b/src/com/android/bluetooth/hearingaid/HearingAidNativeInterface.java
index 1db0be6..9ef2678 100644
--- a/src/com/android/bluetooth/hearingaid/HearingAidNativeInterface.java
+++ b/src/com/android/bluetooth/hearingaid/HearingAidNativeInterface.java
@@ -105,14 +105,14 @@
     }
 
     /**
-     * Add a hearing aid device to white list.
+     * Add a hearing aid device to acceptlist.
      *
      * @param device the remote device
      * @return true on success, otherwise false.
      */
     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
-    public boolean addToWhiteList(BluetoothDevice device) {
-        return addToWhiteListNative(getByteAddress(device));
+    public boolean addToAcceptlist(BluetoothDevice device) {
+        return addToAcceptlistNative(getByteAddress(device));
     }
 
     /**
@@ -179,6 +179,6 @@
     private native void cleanupNative();
     private native boolean connectHearingAidNative(byte[] address);
     private native boolean disconnectHearingAidNative(byte[] address);
-    private native boolean addToWhiteListNative(byte[] address);
+    private native boolean addToAcceptlistNative(byte[] address);
     private native void setVolumeNative(int volume);
 }
diff --git a/src/com/android/bluetooth/hearingaid/HearingAidService.java b/src/com/android/bluetooth/hearingaid/HearingAidService.java
index addc011..c712ddb 100644
--- a/src/com/android/bluetooth/hearingaid/HearingAidService.java
+++ b/src/com/android/bluetooth/hearingaid/HearingAidService.java
@@ -16,11 +16,16 @@
 
 package com.android.bluetooth.hearingaid;
 
+import static android.Manifest.permission.BLUETOOTH_CONNECT;
+
+import android.annotation.RequiresPermission;
 import android.bluetooth.BluetoothDevice;
 import android.bluetooth.BluetoothHearingAid;
 import android.bluetooth.BluetoothProfile;
 import android.bluetooth.BluetoothUuid;
 import android.bluetooth.IBluetoothHearingAid;
+import android.content.Attributable;
+import android.content.AttributionSource;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
@@ -37,6 +42,7 @@
 import com.android.bluetooth.btservice.MetricsLogger;
 import com.android.bluetooth.btservice.ProfileService;
 import com.android.bluetooth.btservice.ServiceFactory;
+import com.android.bluetooth.btservice.storage.DatabaseManager;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.ArrayUtils;
 
@@ -60,6 +66,7 @@
     private static HearingAidService sHearingAidService;
 
     private AdapterService mAdapterService;
+    private DatabaseManager mDatabaseManager;
     private HandlerThread mStateMachinesThread;
     private BluetoothDevice mPreviousAudioDevice;
 
@@ -101,12 +108,12 @@
             throw new IllegalStateException("start() called twice");
         }
 
-        // Get AdapterService, HearingAidNativeInterface, AudioManager.
-        // None of them can be null.
         mAdapterService = Objects.requireNonNull(AdapterService.getAdapterService(),
                 "AdapterService cannot be null when HearingAidService starts");
         mHearingAidNativeInterface = Objects.requireNonNull(HearingAidNativeInterface.getInstance(),
                 "HearingAidNativeInterface cannot be null when HearingAidService starts");
+        mDatabaseManager = Objects.requireNonNull(mAdapterService.getDatabase(),
+                "DatabaseManager cannot be null when HearingAidService starts");
         mAudioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
         Objects.requireNonNull(mAudioManager,
                 "AudioManager cannot be null when HearingAidService starts");
@@ -227,6 +234,7 @@
      * @param device is the device with which we will connect the hearing aid profile
      * @return true if hearing aid profile successfully connected, false otherwise
      */
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED)
     public boolean connect(BluetoothDevice device) {
         enforceCallingOrSelfPermission(BLUETOOTH_PRIVILEGED,
                 "Need BLUETOOTH_PRIVILEGED permission");
@@ -294,6 +302,7 @@
      * @param device is the device with which we want to disconnected the hearing aid profile
      * @return true if hearing aid profile successfully disconnected, false otherwise
      */
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED)
     public boolean disconnect(BluetoothDevice device) {
         enforceCallingOrSelfPermission(BLUETOOTH_PRIVILEGED,
                 "Need BLUETOOTH_PRIVILEGED permission");
@@ -328,7 +337,6 @@
     }
 
     List<BluetoothDevice> getConnectedDevices() {
-        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
         synchronized (mStateMachines) {
             List<BluetoothDevice> devices = new ArrayList<>();
             for (HearingAidStateMachine sm : mStateMachines.values()) {
@@ -347,6 +355,7 @@
      * @param device the peer device to connect to
      * @return true if there are any peer device connected.
      */
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED)
     public boolean isConnectedPeerDevices(BluetoothDevice device) {
         long hiSyncId = getHiSyncId(device);
         if (getConnectedPeerDevices(hiSyncId).isEmpty()) {
@@ -363,6 +372,7 @@
      * @return true if connection is allowed, otherwise false
      */
     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED)
     public boolean okToConnect(BluetoothDevice device) {
         // Check if this is an incoming connection in Quiet mode.
         if (mAdapterService.isQuietModeEnabled()) {
@@ -387,7 +397,6 @@
     }
 
     List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
-        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
         ArrayList<BluetoothDevice> devices = new ArrayList<>();
         if (states == null) {
             return devices;
@@ -454,7 +463,6 @@
      * {@link BluetoothProfile#STATE_DISCONNECTING} if this profile is being disconnected
      */
     public int getConnectionState(BluetoothDevice device) {
-        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
         synchronized (mStateMachines) {
             HearingAidStateMachine sm = mStateMachines.get(device);
             if (sm == null) {
@@ -479,14 +487,18 @@
      * @param connectionPolicy is the connection policy to set to for this profile
      * @return true if connectionPolicy is set, false on error
      */
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED)
     public boolean setConnectionPolicy(BluetoothDevice device, int connectionPolicy) {
         enforceCallingOrSelfPermission(BLUETOOTH_PRIVILEGED,
                 "Need BLUETOOTH_PRIVILEGED permission");
         if (DBG) {
             Log.d(TAG, "Saved connectionPolicy " + device + " = " + connectionPolicy);
         }
-        mAdapterService.getDatabase()
-                .setProfileConnectionPolicy(device, BluetoothProfile.HEARING_AID, connectionPolicy);
+
+        if (!mDatabaseManager.setProfileConnectionPolicy(device, BluetoothProfile.HEARING_AID,
+                  connectionPolicy)) {
+            return false;
+        }
         if (connectionPolicy == BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
             connect(device);
         } else if (connectionPolicy == BluetoothProfile.CONNECTION_POLICY_FORBIDDEN) {
@@ -507,10 +519,11 @@
      * @return connection policy of the device
      * @hide
      */
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED)
     public int getConnectionPolicy(BluetoothDevice device) {
         enforceCallingOrSelfPermission(BLUETOOTH_PRIVILEGED,
                 "Need BLUETOOTH_PRIVILEGED permission");
-        return mAdapterService.getDatabase()
+        return mDatabaseManager
                 .getProfileConnectionPolicy(device, BluetoothProfile.HEARING_AID);
     }
 
@@ -518,6 +531,7 @@
         mHearingAidNativeInterface.setVolume(volume);
     }
 
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED)
     long getHiSyncId(BluetoothDevice device) {
         enforceCallingOrSelfPermission(BLUETOOTH_PRIVILEGED,
                 "Need BLUETOOTH_PRIVILEGED permission");
@@ -537,7 +551,6 @@
      * @return true on success, otherwise false
      */
     public boolean setActiveDevice(BluetoothDevice device) {
-        enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH ADMIN permission");
         if (DBG) {
             Log.d(TAG, "setActiveDevice:" + device);
         }
@@ -571,7 +584,6 @@
      * is not active, it will be null on that position
      */
     public List<BluetoothDevice> getActiveDevices() {
-        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
         if (DBG) {
             Log.d(TAG, "getActiveDevices");
         }
@@ -682,7 +694,7 @@
         intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
         intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT
                 | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
-        sendBroadcast(intent, ProfileService.BLUETOOTH_PERM);
+        sendBroadcast(intent, BLUETOOTH_CONNECT, Utils.getTempAllowlistBroadcastOptions());
 
         if (device == null) {
             if (DBG) {
@@ -771,6 +783,7 @@
         }
     }
 
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED)
     private List<BluetoothDevice> getConnectedPeerDevices(long hiSyncId) {
         List<BluetoothDevice> result = new ArrayList<>();
         for (BluetoothDevice peerDevice : getConnectedDevices()) {
@@ -782,6 +795,7 @@
     }
 
     @VisibleForTesting
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED)
     synchronized void connectionStateChanged(BluetoothDevice device, int fromState,
                                                      int toState) {
         if ((device == null) || (fromState == toState)) {
@@ -841,16 +855,14 @@
             implements IProfileServiceBinder {
         private HearingAidService mService;
 
-        private HearingAidService getService() {
-            if (!Utils.checkCaller()) {
-                Log.w(TAG, "HearingAid call not allowed for non-active user");
+        @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
+        private HearingAidService getService(AttributionSource source) {
+            if (!Utils.checkCallerIsSystemOrActiveUser(TAG)
+                    || !Utils.checkServiceAvailable(mService, TAG)
+                    || !Utils.checkConnectPermissionForDataDelivery(mService, source, TAG)) {
                 return null;
             }
-
-            if (mService != null && mService.isAvailable()) {
-                return mService;
-            }
-            return null;
+            return mService;
         }
 
         BluetoothHearingAidBinder(HearingAidService svc) {
@@ -863,8 +875,9 @@
         }
 
         @Override
-        public boolean connect(BluetoothDevice device) {
-            HearingAidService service = getService();
+        public boolean connect(BluetoothDevice device, AttributionSource source) {
+            Attributable.setAttributionSource(device, source);
+            HearingAidService service = getService(source);
             if (service == null) {
                 return false;
             }
@@ -872,8 +885,9 @@
         }
 
         @Override
-        public boolean disconnect(BluetoothDevice device) {
-            HearingAidService service = getService();
+        public boolean disconnect(BluetoothDevice device, AttributionSource source) {
+            Attributable.setAttributionSource(device, source);
+            HearingAidService service = getService(source);
             if (service == null) {
                 return false;
             }
@@ -881,8 +895,8 @@
         }
 
         @Override
-        public List<BluetoothDevice> getConnectedDevices() {
-            HearingAidService service = getService();
+        public List<BluetoothDevice> getConnectedDevices(AttributionSource source) {
+            HearingAidService service = getService(source);
             if (service == null) {
                 return new ArrayList<>();
             }
@@ -890,8 +904,9 @@
         }
 
         @Override
-        public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
-            HearingAidService service = getService();
+        public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states,
+                AttributionSource source) {
+            HearingAidService service = getService(source);
             if (service == null) {
                 return new ArrayList<>();
             }
@@ -899,8 +914,9 @@
         }
 
         @Override
-        public int getConnectionState(BluetoothDevice device) {
-            HearingAidService service = getService();
+        public int getConnectionState(BluetoothDevice device, AttributionSource source) {
+            Attributable.setAttributionSource(device, source);
+            HearingAidService service = getService(source);
             if (service == null) {
                 return BluetoothProfile.STATE_DISCONNECTED;
             }
@@ -908,8 +924,9 @@
         }
 
         @Override
-        public boolean setActiveDevice(BluetoothDevice device) {
-            HearingAidService service = getService();
+        public boolean setActiveDevice(BluetoothDevice device, AttributionSource source) {
+            Attributable.setAttributionSource(device, source);
+            HearingAidService service = getService(source);
             if (service == null) {
                 return false;
             }
@@ -917,8 +934,8 @@
         }
 
         @Override
-        public List<BluetoothDevice> getActiveDevices() {
-            HearingAidService service = getService();
+        public List<BluetoothDevice> getActiveDevices(AttributionSource source) {
+            HearingAidService service = getService(source);
             if (service == null) {
                 return new ArrayList<>();
             }
@@ -926,8 +943,10 @@
         }
 
         @Override
-        public boolean setConnectionPolicy(BluetoothDevice device, int connectionPolicy) {
-            HearingAidService service = getService();
+        public boolean setConnectionPolicy(BluetoothDevice device, int connectionPolicy,
+                AttributionSource source) {
+            Attributable.setAttributionSource(device, source);
+            HearingAidService service = getService(source);
             if (service == null) {
                 return false;
             }
@@ -935,8 +954,9 @@
         }
 
         @Override
-        public int getConnectionPolicy(BluetoothDevice device) {
-            HearingAidService service = getService();
+        public int getConnectionPolicy(BluetoothDevice device, AttributionSource source) {
+            Attributable.setAttributionSource(device, source);
+            HearingAidService service = getService(source);
             if (service == null) {
                 return BluetoothProfile.CONNECTION_POLICY_UNKNOWN;
             }
@@ -944,8 +964,8 @@
         }
 
         @Override
-        public void setVolume(int volume) {
-            HearingAidService service = getService();
+        public void setVolume(int volume, AttributionSource source) {
+            HearingAidService service = getService(source);
             if (service == null) {
                 return;
             }
@@ -953,8 +973,9 @@
         }
 
         @Override
-        public long getHiSyncId(BluetoothDevice device) {
-            HearingAidService service = getService();
+        public long getHiSyncId(BluetoothDevice device, AttributionSource source) {
+            Attributable.setAttributionSource(device, source);
+            HearingAidService service = getService(source);
             if (service == null) {
                 return BluetoothHearingAid.HI_SYNC_ID_INVALID;
             }
@@ -962,8 +983,9 @@
         }
 
         @Override
-        public int getDeviceSide(BluetoothDevice device) {
-            HearingAidService service = getService();
+        public int getDeviceSide(BluetoothDevice device, AttributionSource source) {
+            Attributable.setAttributionSource(device, source);
+            HearingAidService service = getService(source);
             if (service == null) {
                 return BluetoothHearingAid.SIDE_RIGHT;
             }
@@ -971,8 +993,9 @@
         }
 
         @Override
-        public int getDeviceMode(BluetoothDevice device) {
-            HearingAidService service = getService();
+        public int getDeviceMode(BluetoothDevice device, AttributionSource source) {
+            Attributable.setAttributionSource(device, source);
+            HearingAidService service = getService(source);
             if (service == null) {
                 return BluetoothHearingAid.MODE_BINAURAL;
             }
diff --git a/src/com/android/bluetooth/hearingaid/HearingAidStateMachine.java b/src/com/android/bluetooth/hearingaid/HearingAidStateMachine.java
index de1b4ee..93b9737 100644
--- a/src/com/android/bluetooth/hearingaid/HearingAidStateMachine.java
+++ b/src/com/android/bluetooth/hearingaid/HearingAidStateMachine.java
@@ -45,6 +45,8 @@
 
 package com.android.bluetooth.hearingaid;
 
+import static android.Manifest.permission.BLUETOOTH_CONNECT;
+
 import android.bluetooth.BluetoothDevice;
 import android.bluetooth.BluetoothHearingAid;
 import android.bluetooth.BluetoothProfile;
@@ -53,6 +55,7 @@
 import android.os.Message;
 import android.util.Log;
 
+import com.android.bluetooth.Utils;
 import com.android.bluetooth.btservice.ProfileService;
 import com.android.bluetooth.statemachine.State;
 import com.android.bluetooth.statemachine.StateMachine;
@@ -266,8 +269,8 @@
                     Log.w(TAG, "Connecting connection timeout: " + mDevice);
                     mNativeInterface.disconnectHearingAid(mDevice);
                     if (mService.isConnectedPeerDevices(mDevice)) {
-                        Log.w(TAG, "One side connection timeout: " + mDevice + ". Try whitelist");
-                        mNativeInterface.addToWhiteList(mDevice);
+                        Log.w(TAG, "One side connection timeout: " + mDevice + ". Try acceptlist");
+                        mNativeInterface.addToAcceptlist(mDevice);
                     }
                     HearingAidStackEvent disconnectEvent =
                             new HearingAidStackEvent(
@@ -486,7 +489,7 @@
         private void processConnectionEvent(int state) {
             switch (state) {
                 case HearingAidStackEvent.CONNECTION_STATE_DISCONNECTED:
-                    Log.i(TAG, "Disconnected from " + mDevice + " but still in Whitelist");
+                    Log.i(TAG, "Disconnected from " + mDevice + " but still in Acceptlist");
                     transitionTo(mDisconnected);
                     break;
                 case HearingAidStackEvent.CONNECTION_STATE_DISCONNECTING:
@@ -523,7 +526,7 @@
         intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mDevice);
         intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT
                         | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
-        mService.sendBroadcast(intent, ProfileService.BLUETOOTH_PERM);
+        mService.sendBroadcast(intent, BLUETOOTH_CONNECT, Utils.getTempAllowlistBroadcastOptions());
     }
 
     private static String messageWhatToString(int what) {
diff --git a/src/com/android/bluetooth/hfp/AtPhonebook.java b/src/com/android/bluetooth/hfp/AtPhonebook.java
index 7259690..12f1c4c 100644
--- a/src/com/android/bluetooth/hfp/AtPhonebook.java
+++ b/src/com/android/bluetooth/hfp/AtPhonebook.java
@@ -16,6 +16,9 @@
 
 package com.android.bluetooth.hfp;
 
+import static android.Manifest.permission.BLUETOOTH_CONNECT;
+
+import android.app.Activity;
 import android.bluetooth.BluetoothDevice;
 import android.content.ContentResolver;
 import android.content.Context;
@@ -86,7 +89,6 @@
 
     // package and class name to which we send intent to check phone book access permission
     private final String mPairingPackage;
-    private static final String BLUETOOTH_ADMIN_PERM = android.Manifest.permission.BLUETOOTH_ADMIN;
 
     private final HashMap<String, PhonebookResult> mPhonebooks =
             new HashMap<String, PhonebookResult>(4);
@@ -643,7 +645,9 @@
             intent.putExtra(BluetoothDevice.EXTRA_DEVICE, remoteDevice);
             // Leave EXTRA_PACKAGE_NAME and EXTRA_CLASS_NAME field empty.
             // BluetoothHandsfree's broadcast receiver is anonymous, cannot be targeted.
-            mContext.sendOrderedBroadcast(intent, BLUETOOTH_ADMIN_PERM);
+            mContext.sendOrderedBroadcast(intent, BLUETOOTH_CONNECT,
+                    Utils.getTempAllowlistBroadcastOptions(), null, null,
+                    Activity.RESULT_OK, null, null);
         }
 
         return permission;
diff --git a/src/com/android/bluetooth/hfp/BluetoothHeadsetProxy.java b/src/com/android/bluetooth/hfp/BluetoothHeadsetProxy.java
new file mode 100644
index 0000000..824955d
--- /dev/null
+++ b/src/com/android/bluetooth/hfp/BluetoothHeadsetProxy.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.bluetooth.hfp;
+
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothHeadset;
+
+import java.util.List;
+
+/**
+ * A proxy class that facilitates testing of the BluetoothInCallService class.
+ *
+ * This is necessary due to the "final" attribute of the BluetoothHeadset class. In order to
+ * test the correct functioning of the BluetoothInCallService class, the final class must be put
+ * into a container that can be mocked correctly.
+ */
+public class BluetoothHeadsetProxy {
+
+    private BluetoothHeadset mBluetoothHeadset;
+
+    public BluetoothHeadsetProxy(BluetoothHeadset headset) {
+        mBluetoothHeadset = headset;
+    }
+
+    public void clccResponse(int index, int direction, int status, int mode, boolean mpty,
+            String number, int type) {
+
+        mBluetoothHeadset.clccResponse(index, direction, status, mode, mpty, number, type);
+    }
+
+    public void phoneStateChanged(int numActive, int numHeld, int callState, String number,
+            int type, String name) {
+
+        mBluetoothHeadset.phoneStateChanged(numActive, numHeld, callState, number, type,
+                name);
+    }
+
+    public List<BluetoothDevice> getConnectedDevices() {
+        return mBluetoothHeadset.getConnectedDevices();
+    }
+
+    public int getConnectionState(BluetoothDevice device) {
+        return mBluetoothHeadset.getConnectionState(device);
+    }
+
+    public int getAudioState(BluetoothDevice device) {
+        return mBluetoothHeadset.getAudioState(device);
+    }
+
+    public boolean connectAudio() {
+        return mBluetoothHeadset.connectAudio();
+    }
+
+    public boolean setActiveDevice(BluetoothDevice device) {
+        return mBluetoothHeadset.setActiveDevice(device);
+    }
+
+    public BluetoothDevice getActiveDevice() {
+        return mBluetoothHeadset.getActiveDevice();
+    }
+
+    public boolean isAudioOn() {
+        return mBluetoothHeadset.isAudioOn();
+    }
+
+    public boolean disconnectAudio() {
+        return mBluetoothHeadset.disconnectAudio();
+    }
+
+    public boolean isInbandRingingEnabled() {
+        return mBluetoothHeadset.isInbandRingingEnabled();
+    }
+}
diff --git a/src/com/android/bluetooth/hfp/HeadsetNativeInterface.java b/src/com/android/bluetooth/hfp/HeadsetNativeInterface.java
index 29c571d..79fe01f 100644
--- a/src/com/android/bluetooth/hfp/HeadsetNativeInterface.java
+++ b/src/com/android/bluetooth/hfp/HeadsetNativeInterface.java
@@ -298,6 +298,27 @@
     }
 
     /**
+     * Checks whether the device support echo cancellation and/or noise reduction via the AT+BRSF
+     * bitmask
+     *
+     * @param device target headset
+     * @return true if the device support echo cancellation or noise reduction, false otherwise
+     */
+    public boolean isNoiseReductionSupported(BluetoothDevice device) {
+        return isNoiseReductionSupportedNative(Utils.getByteAddress(device));
+    }
+
+    /**
+     * Checks whether the device supports voice recognition via the AT+BRSF bitmask
+     *
+     * @param device target headset
+     * @return true if the device supports voice recognition, false otherwise
+     */
+    public boolean isVoiceRecognitionSupported(BluetoothDevice device) {
+        return isVoiceRecognitionSupportedNative(Utils.getByteAddress(device));
+    }
+
+    /**
      * Start voice recognition
      *
      * @param device target headset
@@ -475,6 +496,10 @@
 
     private native boolean disconnectAudioNative(byte[] address);
 
+    private native boolean isNoiseReductionSupportedNative(byte[] address);
+
+    private native boolean isVoiceRecognitionSupportedNative(byte[] address);
+
     private native boolean startVoiceRecognitionNative(byte[] address);
 
     private native boolean stopVoiceRecognitionNative(byte[] address);
diff --git a/src/com/android/bluetooth/hfp/HeadsetPhoneState.java b/src/com/android/bluetooth/hfp/HeadsetPhoneState.java
index d1dbe32..9bcc534 100644
--- a/src/com/android/bluetooth/hfp/HeadsetPhoneState.java
+++ b/src/com/android/bluetooth/hfp/HeadsetPhoneState.java
@@ -16,6 +16,8 @@
 
 package com.android.bluetooth.hfp;
 
+import android.annotation.RequiresPermission;
+import android.annotation.SuppressLint;
 import android.bluetooth.BluetoothDevice;
 import android.content.Context;
 import android.os.Handler;
@@ -246,6 +248,7 @@
                 new HeadsetDeviceState(mCindService, mCindRoam, signal, mCindBatteryCharge));
     }
 
+    @SuppressLint("AndroidFrameworkRequiresPermission")
     private class HeadsetPhoneStateOnSubscriptionChangedListener
             extends OnSubscriptionsChangedListener {
         HeadsetPhoneStateOnSubscriptionChangedListener() {
@@ -268,6 +271,7 @@
         }
     }
 
+    @SuppressLint("AndroidFrameworkRequiresPermission")
     private class HeadsetPhoneStateListener extends PhoneStateListener {
         HeadsetPhoneStateListener(Executor executor) {
             super(executor);
diff --git a/src/com/android/bluetooth/hfp/HeadsetService.java b/src/com/android/bluetooth/hfp/HeadsetService.java
index bb71e87..cd63287 100644
--- a/src/com/android/bluetooth/hfp/HeadsetService.java
+++ b/src/com/android/bluetooth/hfp/HeadsetService.java
@@ -16,18 +16,20 @@
 
 package com.android.bluetooth.hfp;
 
+import static android.Manifest.permission.BLUETOOTH_CONNECT;
 import static android.Manifest.permission.MODIFY_PHONE_STATE;
 
-import static com.android.bluetooth.Utils.enforceBluetoothAdminPermission;
-import static com.android.bluetooth.Utils.enforceBluetoothPermission;
 import static com.android.bluetooth.Utils.enforceBluetoothPrivilegedPermission;
 
 import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
 import android.bluetooth.BluetoothDevice;
 import android.bluetooth.BluetoothHeadset;
 import android.bluetooth.BluetoothProfile;
 import android.bluetooth.BluetoothUuid;
 import android.bluetooth.IBluetoothHeadset;
+import android.content.Attributable;
+import android.content.AttributionSource;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
@@ -50,6 +52,7 @@
 import com.android.bluetooth.btservice.AdapterService;
 import com.android.bluetooth.btservice.MetricsLogger;
 import com.android.bluetooth.btservice.ProfileService;
+import com.android.bluetooth.btservice.storage.DatabaseManager;
 import com.android.internal.annotations.VisibleForTesting;
 
 import java.util.ArrayList;
@@ -99,6 +102,7 @@
     private int mMaxHeadsetConnections = 1;
     private BluetoothDevice mActiveDevice;
     private AdapterService mAdapterService;
+    private DatabaseManager mDatabaseManager;
     private HandlerThread mStateMachinesThread;
     private Handler mStateMachinesThreadHandler;
     // This is also used as a lock for shared data in HeadsetService
@@ -141,15 +145,16 @@
         if (mStarted) {
             throw new IllegalStateException("start() called twice");
         }
-        // Step 1: Get adapter service, should never be null
+        // Step 1: Get AdapterService and DatabaseManager, should never be null
         mAdapterService = Objects.requireNonNull(AdapterService.getAdapterService(),
                 "AdapterService cannot be null when HeadsetService starts");
+        mDatabaseManager = Objects.requireNonNull(mAdapterService.getDatabase(),
+                "DatabaseManager cannot be null when HeadsetService starts");
         // Step 2: Start handler thread for state machines
         mStateMachinesThread = new HandlerThread("HeadsetService.StateMachines");
         mStateMachinesThread.start();
         // Step 3: Initialize system interface
         mSystemInterface = HeadsetObjectsFactory.getInstance().makeSystemInterface(this);
-        mSystemInterface.init();
         // Step 4: Initialize native interface
         mMaxHeadsetConnections = mAdapterService.getMaxConnectedAudioDevices();
         mNativeInterface = HeadsetObjectsFactory.getInstance().getNativeInterface();
@@ -418,26 +423,25 @@
             mService = null;
         }
 
-        private HeadsetService getService() {
-            final HeadsetService service = mService;
-            if (!Utils.checkCallerAllowManagedProfiles(service)) {
-                Log.w(TAG, "Headset call not allowed for non-active user");
+        @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
+        private HeadsetService getService(AttributionSource source) {
+            if (!Utils.checkCallerIsSystemOrActiveUser(TAG)
+                    || !Utils.checkServiceAvailable(mService, TAG)
+                    || !Utils.checkConnectPermissionForDataDelivery(mService, source, TAG)) {
                 return null;
             }
-            if (service == null) {
-                Log.w(TAG, "Service is null");
-                return null;
-            }
-            if (!service.isAlive()) {
-                Log.w(TAG, "Service is not alive");
-                return null;
-            }
-            return service;
+            return mService;
         }
 
         @Override
         public boolean connect(BluetoothDevice device) {
-            HeadsetService service = getService();
+            return connectWithAttribution(device, Utils.getCallingAttributionSource());
+        }
+
+        @Override
+        public boolean connectWithAttribution(BluetoothDevice device, AttributionSource source) {
+            Attributable.setAttributionSource(device, source);
+            HeadsetService service = getService(source);
             if (service == null) {
                 return false;
             }
@@ -446,7 +450,13 @@
 
         @Override
         public boolean disconnect(BluetoothDevice device) {
-            HeadsetService service = getService();
+            return disconnectWithAttribution(device, Utils.getCallingAttributionSource());
+        }
+
+        @Override
+        public boolean disconnectWithAttribution(BluetoothDevice device, AttributionSource source) {
+            Attributable.setAttributionSource(device, source);
+            HeadsetService service = getService(source);
             if (service == null) {
                 return false;
             }
@@ -455,7 +465,12 @@
 
         @Override
         public List<BluetoothDevice> getConnectedDevices() {
-            HeadsetService service = getService();
+            return getConnectedDevicesWithAttribution(Utils.getCallingAttributionSource());
+        }
+
+        @Override
+        public List<BluetoothDevice> getConnectedDevicesWithAttribution(AttributionSource source) {
+            HeadsetService service = getService(source);
             if (service == null) {
                 return new ArrayList<BluetoothDevice>(0);
             }
@@ -463,8 +478,9 @@
         }
 
         @Override
-        public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
-            HeadsetService service = getService();
+        public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states,
+                AttributionSource source) {
+            HeadsetService service = getService(source);
             if (service == null) {
                 return new ArrayList<BluetoothDevice>(0);
             }
@@ -473,7 +489,14 @@
 
         @Override
         public int getConnectionState(BluetoothDevice device) {
-            HeadsetService service = getService();
+            return getConnectionStateWithAttribution(device, Utils.getCallingAttributionSource());
+        }
+
+        @Override
+        public int getConnectionStateWithAttribution(BluetoothDevice device,
+                AttributionSource source) {
+            Attributable.setAttributionSource(device, source);
+            HeadsetService service = getService(source);
             if (service == null) {
                 return BluetoothProfile.STATE_DISCONNECTED;
             }
@@ -481,18 +504,21 @@
         }
 
         @Override
-        public boolean setPriority(BluetoothDevice device, int connectionPolicy) {
-            HeadsetService service = getService();
+        public boolean setPriority(BluetoothDevice device, int connectionPolicy,
+                AttributionSource source) {
+            Attributable.setAttributionSource(device, source);
+            HeadsetService service = getService(source);
             if (service == null) {
                 return false;
             }
-            enforceBluetoothAdminPermission(service);
             return service.setConnectionPolicy(device, connectionPolicy);
         }
 
         @Override
-        public boolean setConnectionPolicy(BluetoothDevice device, int connectionPolicy) {
-            HeadsetService service = getService();
+        public boolean setConnectionPolicy(BluetoothDevice device, int connectionPolicy,
+                AttributionSource source) {
+            Attributable.setAttributionSource(device, source);
+            HeadsetService service = getService(source);
             if (service == null) {
                 return false;
             }
@@ -501,18 +527,19 @@
         }
 
         @Override
-        public int getPriority(BluetoothDevice device) {
-            HeadsetService service = getService();
+        public int getPriority(BluetoothDevice device, AttributionSource source) {
+            Attributable.setAttributionSource(device, source);
+            HeadsetService service = getService(source);
             if (service == null) {
                 return BluetoothProfile.CONNECTION_POLICY_UNKNOWN;
             }
-            enforceBluetoothPermission(service);
             return service.getConnectionPolicy(device);
         }
 
         @Override
-        public int getConnectionPolicy(BluetoothDevice device) {
-            HeadsetService service = getService();
+        public int getConnectionPolicy(BluetoothDevice device, AttributionSource source) {
+            Attributable.setAttributionSource(device, source);
+            HeadsetService service = getService(source);
             if (service == null) {
                 return BluetoothProfile.CONNECTION_POLICY_UNKNOWN;
             }
@@ -521,8 +548,30 @@
         }
 
         @Override
-        public boolean startVoiceRecognition(BluetoothDevice device) {
-            HeadsetService service = getService();
+        public boolean isNoiseReductionSupported(BluetoothDevice device, AttributionSource source) {
+            Attributable.setAttributionSource(device, source);
+            HeadsetService service = getService(source);
+            if (service == null) {
+                return false;
+            }
+            return service.isNoiseReductionSupported(device);
+        }
+
+        @Override
+        public boolean isVoiceRecognitionSupported(BluetoothDevice device,
+                AttributionSource source) {
+            Attributable.setAttributionSource(device, source);
+            HeadsetService service = getService(source);
+            if (service == null) {
+                return false;
+            }
+            return service.isVoiceRecognitionSupported(device);
+        }
+
+        @Override
+        public boolean startVoiceRecognition(BluetoothDevice device, AttributionSource source) {
+            Attributable.setAttributionSource(device, source);
+            HeadsetService service = getService(source);
             if (service == null) {
                 return false;
             }
@@ -530,8 +579,9 @@
         }
 
         @Override
-        public boolean stopVoiceRecognition(BluetoothDevice device) {
-            HeadsetService service = getService();
+        public boolean stopVoiceRecognition(BluetoothDevice device, AttributionSource source) {
+            Attributable.setAttributionSource(device, source);
+            HeadsetService service = getService(source);
             if (service == null) {
                 return false;
             }
@@ -539,8 +589,8 @@
         }
 
         @Override
-        public boolean isAudioOn() {
-            HeadsetService service = getService();
+        public boolean isAudioOn(AttributionSource source) {
+            HeadsetService service = getService(source);
             if (service == null) {
                 return false;
             }
@@ -548,8 +598,9 @@
         }
 
         @Override
-        public boolean isAudioConnected(BluetoothDevice device) {
-            HeadsetService service = getService();
+        public boolean isAudioConnected(BluetoothDevice device, AttributionSource source) {
+            Attributable.setAttributionSource(device, source);
+            HeadsetService service = getService(source);
             if (service == null) {
                 return false;
             }
@@ -557,8 +608,9 @@
         }
 
         @Override
-        public int getAudioState(BluetoothDevice device) {
-            HeadsetService service = getService();
+        public int getAudioState(BluetoothDevice device, AttributionSource source) {
+            Attributable.setAttributionSource(device, source);
+            HeadsetService service = getService(source);
             if (service == null) {
                 return BluetoothHeadset.STATE_AUDIO_DISCONNECTED;
             }
@@ -566,8 +618,8 @@
         }
 
         @Override
-        public boolean connectAudio() {
-            HeadsetService service = getService();
+        public boolean connectAudio(AttributionSource source) {
+            HeadsetService service = getService(source);
             if (service == null) {
                 return false;
             }
@@ -575,8 +627,8 @@
         }
 
         @Override
-        public boolean disconnectAudio() {
-            HeadsetService service = getService();
+        public boolean disconnectAudio(AttributionSource source) {
+            HeadsetService service = getService(source);
             if (service == null) {
                 return false;
             }
@@ -584,8 +636,8 @@
         }
 
         @Override
-        public void setAudioRouteAllowed(boolean allowed) {
-            HeadsetService service = getService();
+        public void setAudioRouteAllowed(boolean allowed, AttributionSource source) {
+            HeadsetService service = getService(source);
             if (service == null) {
                 return;
             }
@@ -593,8 +645,8 @@
         }
 
         @Override
-        public boolean getAudioRouteAllowed() {
-            HeadsetService service = getService();
+        public boolean getAudioRouteAllowed(AttributionSource source) {
+            HeadsetService service = getService(source);
             if (service != null) {
                 return service.getAudioRouteAllowed();
             }
@@ -602,8 +654,8 @@
         }
 
         @Override
-        public void setForceScoAudio(boolean forced) {
-            HeadsetService service = getService();
+        public void setForceScoAudio(boolean forced, AttributionSource source) {
+            HeadsetService service = getService(source);
             if (service == null) {
                 return;
             }
@@ -611,8 +663,8 @@
         }
 
         @Override
-        public boolean startScoUsingVirtualVoiceCall() {
-            HeadsetService service = getService();
+        public boolean startScoUsingVirtualVoiceCall(AttributionSource source) {
+            HeadsetService service = getService(source);
             if (service == null) {
                 return false;
             }
@@ -620,8 +672,8 @@
         }
 
         @Override
-        public boolean stopScoUsingVirtualVoiceCall() {
-            HeadsetService service = getService();
+        public boolean stopScoUsingVirtualVoiceCall(AttributionSource source) {
+            HeadsetService service = getService(source);
             if (service == null) {
                 return false;
             }
@@ -630,8 +682,8 @@
 
         @Override
         public void phoneStateChanged(int numActive, int numHeld, int callState, String number,
-                int type, String name) {
-            HeadsetService service = getService();
+                int type, String name, AttributionSource source) {
+            HeadsetService service = getService(source);
             if (service == null) {
                 return;
             }
@@ -640,8 +692,8 @@
 
         @Override
         public void clccResponse(int index, int direction, int status, int mode, boolean mpty,
-                String number, int type) {
-            HeadsetService service = getService();
+                String number, int type, AttributionSource source) {
+            HeadsetService service = getService(source);
             if (service == null) {
                 return;
             }
@@ -650,8 +702,9 @@
 
         @Override
         public boolean sendVendorSpecificResultCode(BluetoothDevice device, String command,
-                String arg) {
-            HeadsetService service = getService();
+                String arg, AttributionSource source) {
+            Attributable.setAttributionSource(device, source);
+            HeadsetService service = getService(source);
             if (service == null) {
                 return false;
             }
@@ -659,8 +712,9 @@
         }
 
         @Override
-        public boolean setActiveDevice(BluetoothDevice device) {
-            HeadsetService service = getService();
+        public boolean setActiveDevice(BluetoothDevice device, AttributionSource source) {
+            Attributable.setAttributionSource(device, source);
+            HeadsetService service = getService(source);
             if (service == null) {
                 return false;
             }
@@ -668,8 +722,8 @@
         }
 
         @Override
-        public BluetoothDevice getActiveDevice() {
-            HeadsetService service = getService();
+        public BluetoothDevice getActiveDevice(AttributionSource source) {
+            HeadsetService service = getService(source);
             if (service == null) {
                 return null;
             }
@@ -677,8 +731,8 @@
         }
 
         @Override
-        public boolean isInbandRingingEnabled() {
-            HeadsetService service = getService();
+        public boolean isInbandRingingEnabled(AttributionSource source) {
+            HeadsetService service = getService(source);
             if (service == null) {
                 return false;
             }
@@ -705,8 +759,8 @@
         sHeadsetService = instance;
     }
 
+    @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
     public boolean connect(BluetoothDevice device) {
-        enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH ADMIN permission");
         if (getConnectionPolicy(device) == BluetoothProfile.CONNECTION_POLICY_FORBIDDEN) {
             Log.w(TAG, "connect: CONNECTION_POLICY_FORBIDDEN, device=" + device + ", "
                     + Utils.getUidPidString());
@@ -764,7 +818,6 @@
      * @return true if hfp is disconnected, false if the device is not connected
      */
     public boolean disconnect(BluetoothDevice device) {
-        enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission");
         Log.i(TAG, "disconnect: device=" + device + ", " + Utils.getUidPidString());
         synchronized (mStateMachines) {
             HeadsetStateMachine stateMachine = mStateMachines.get(device);
@@ -785,7 +838,6 @@
     }
 
     public List<BluetoothDevice> getConnectedDevices() {
-        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
         ArrayList<BluetoothDevice> devices = new ArrayList<>();
         synchronized (mStateMachines) {
             for (HeadsetStateMachine stateMachine : mStateMachines.values()) {
@@ -805,7 +857,6 @@
      */
     @VisibleForTesting
     public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
-        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
         ArrayList<BluetoothDevice> devices = new ArrayList<>();
         synchronized (mStateMachines) {
             if (states == null || mAdapterService == null) {
@@ -833,7 +884,6 @@
     }
 
     public int getConnectionState(BluetoothDevice device) {
-        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
         synchronized (mStateMachines) {
             final HeadsetStateMachine stateMachine = mStateMachines.get(device);
             if (stateMachine == null) {
@@ -858,11 +908,15 @@
      * @param connectionPolicy is the connection policy to set to for this profile
      * @return true if connectionPolicy is set, false on error
      */
+    @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
     public boolean setConnectionPolicy(BluetoothDevice device, int connectionPolicy) {
         Log.i(TAG, "setConnectionPolicy: device=" + device
                 + ", connectionPolicy=" + connectionPolicy + ", " + Utils.getUidPidString());
-        mAdapterService.getDatabase()
-                .setProfileConnectionPolicy(device, BluetoothProfile.HEADSET, connectionPolicy);
+
+        if (!mDatabaseManager.setProfileConnectionPolicy(device, BluetoothProfile.HEADSET,
+                  connectionPolicy)) {
+            return false;
+        }
         if (connectionPolicy == BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
             connect(device);
         } else if (connectionPolicy == BluetoothProfile.CONNECTION_POLICY_FORBIDDEN) {
@@ -884,12 +938,20 @@
      * @hide
      */
     public int getConnectionPolicy(BluetoothDevice device) {
-        return mAdapterService.getDatabase()
+        return mDatabaseManager
                 .getProfileConnectionPolicy(device, BluetoothProfile.HEADSET);
     }
 
+    boolean isNoiseReductionSupported(BluetoothDevice device) {
+        return mNativeInterface.isNoiseReductionSupported(device);
+    }
+
+    boolean isVoiceRecognitionSupported(BluetoothDevice device) {
+        return mNativeInterface.isVoiceRecognitionSupported(device);
+    }
+
+    @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
     boolean startVoiceRecognition(BluetoothDevice device) {
-        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
         Log.i(TAG, "startVoiceRecognition: device=" + device + ", " + Utils.getUidPidString());
         synchronized (mStateMachines) {
             // TODO(b/79660380): Workaround in case voice recognition was not terminated properly
@@ -964,7 +1026,6 @@
     }
 
     boolean stopVoiceRecognition(BluetoothDevice device) {
-        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
         Log.i(TAG, "stopVoiceRecognition: device=" + device + ", " + Utils.getUidPidString());
         synchronized (mStateMachines) {
             if (!Objects.equals(mActiveDevice, device)) {
@@ -995,12 +1056,10 @@
     }
 
     boolean isAudioOn() {
-        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
         return getNonIdleAudioDevices().size() > 0;
     }
 
     boolean isAudioConnected(BluetoothDevice device) {
-        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
         synchronized (mStateMachines) {
             final HeadsetStateMachine stateMachine = mStateMachines.get(device);
             if (stateMachine == null) {
@@ -1011,7 +1070,6 @@
     }
 
     int getAudioState(BluetoothDevice device) {
-        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
         synchronized (mStateMachines) {
             final HeadsetStateMachine stateMachine = mStateMachines.get(device);
             if (stateMachine == null) {
@@ -1022,19 +1080,16 @@
     }
 
     public void setAudioRouteAllowed(boolean allowed) {
-        enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission");
         Log.i(TAG, "setAudioRouteAllowed: allowed=" + allowed + ", " + Utils.getUidPidString());
         mAudioRouteAllowed = allowed;
         mNativeInterface.setScoAllowed(allowed);
     }
 
     public boolean getAudioRouteAllowed() {
-        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
         return mAudioRouteAllowed;
     }
 
     public void setForceScoAudio(boolean forced) {
-        enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission");
         Log.i(TAG, "setForceScoAudio: forced=" + forced + ", " + Utils.getUidPidString());
         mForceScoAudio = forced;
     }
@@ -1079,6 +1134,7 @@
      * @return true on success, false on error
      */
     @VisibleForTesting
+    @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
     public boolean setSilenceMode(BluetoothDevice device, boolean silence) {
         Log.d(TAG, "setSilenceMode(" + device + "): " + silence);
 
@@ -1107,8 +1163,8 @@
      * @param device the active device
      * @return true on success, otherwise false
      */
+    @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
     public boolean setActiveDevice(BluetoothDevice device) {
-        enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission");
         Log.i(TAG, "setActiveDevice: device=" + device + ", " + Utils.getUidPidString());
         synchronized (mStateMachines) {
             if (device == null) {
@@ -1179,14 +1235,12 @@
      * @return the active device or null if no device is active
      */
     public BluetoothDevice getActiveDevice() {
-        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
         synchronized (mStateMachines) {
             return mActiveDevice;
         }
     }
 
     boolean connectAudio() {
-        enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission");
         synchronized (mStateMachines) {
             BluetoothDevice device = mActiveDevice;
             if (device == null) {
@@ -1198,7 +1252,6 @@
     }
 
     boolean connectAudio(BluetoothDevice device) {
-        enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission");
         Log.i(TAG, "connectAudio: device=" + device + ", " + Utils.getUidPidString());
         synchronized (mStateMachines) {
             if (!isScoAcceptable(device)) {
@@ -1241,7 +1294,6 @@
     }
 
     boolean disconnectAudio() {
-        enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission");
         boolean result = false;
         synchronized (mStateMachines) {
             for (BluetoothDevice device : getNonIdleAudioDevices()) {
@@ -1259,7 +1311,6 @@
     }
 
     boolean disconnectAudio(BluetoothDevice device) {
-        enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission");
         synchronized (mStateMachines) {
             Log.i(TAG, "disconnectAudio: device=" + device + ", " + Utils.getUidPidString());
             final HeadsetStateMachine stateMachine = mStateMachines.get(device);
@@ -1277,14 +1328,13 @@
     }
 
     boolean isVirtualCallStarted() {
-        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
         synchronized (mStateMachines) {
             return mVirtualCallStarted;
         }
     }
 
+    @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
     private boolean startScoUsingVirtualVoiceCall() {
-        enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission");
         Log.i(TAG, "startScoUsingVirtualVoiceCall: " + Utils.getUidPidString());
         synchronized (mStateMachines) {
             // TODO(b/79660380): Workaround in case voice recognition was not terminated properly
@@ -1323,8 +1373,8 @@
         }
     }
 
+    @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
     boolean stopScoUsingVirtualVoiceCall() {
-        enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission");
         Log.i(TAG, "stopScoUsingVirtualVoiceCall: " + Utils.getUidPidString());
         synchronized (mStateMachines) {
             // 1. Check if virtual call has already started
@@ -1370,6 +1420,7 @@
      * @return true on successful dial out
      */
     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+    @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
     public boolean dialOutgoingCall(BluetoothDevice fromDevice, String dialNumber) {
         synchronized (mStateMachines) {
             Log.i(TAG, "dialOutgoingCall: from " + fromDevice);
@@ -1440,6 +1491,7 @@
         }
     }
 
+    @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
     boolean startVoiceRecognitionByHeadset(BluetoothDevice fromDevice) {
         synchronized (mStateMachines) {
             Log.i(TAG, "startVoiceRecognitionByHeadset: from " + fromDevice);
@@ -1533,6 +1585,7 @@
         }
     }
 
+    @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
     private void phoneStateChanged(int numActive, int numHeld, int callState, String number,
             int type, String name, boolean isVirtualCall) {
         enforceCallingOrSelfPermission(MODIFY_PHONE_STATE, "Need MODIFY_PHONE_STATE permission");
@@ -1573,7 +1626,7 @@
             mSystemInterface.getHeadsetPhoneState().setNumHeldCall(numHeld);
             mSystemInterface.getHeadsetPhoneState().setCallState(callState);
             // Suspend A2DP when call about is about to become active
-            if (callState != HeadsetHalConstants.CALL_STATE_DISCONNECTED
+            if (mActiveDevice != null && callState != HeadsetHalConstants.CALL_STATE_DISCONNECTED
                     && !mSystemInterface.isCallIdle() && isCallIdleBefore) {
                 mSystemInterface.getAudioManager().setParameters("A2dpSuspended=true");
             }
@@ -1591,6 +1644,7 @@
 
     }
 
+    @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
     private void clccResponse(int index, int direction, int status, int mode, boolean mpty,
             String number, int type) {
         enforceCallingOrSelfPermission(MODIFY_PHONE_STATE, "Need MODIFY_PHONE_STATE permission");
@@ -1602,7 +1656,6 @@
 
     private boolean sendVendorSpecificResultCode(BluetoothDevice device, String command,
             String arg) {
-        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
         synchronized (mStateMachines) {
             final HeadsetStateMachine stateMachine = mStateMachines.get(device);
             if (stateMachine == null) {
@@ -1626,7 +1679,6 @@
     }
 
     boolean isInbandRingingEnabled() {
-        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
         return BluetoothHeadset.isInbandRingingSupported(this) && !SystemProperties.getBoolean(
                 DISABLE_INBAND_RINGING_PROPERTY, false) && !mInbandRingingRuntimeDisable;
     }
@@ -1640,6 +1692,7 @@
      * @param toState to which connection state is the change
      */
     @VisibleForTesting
+    @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
     public void onConnectionStateChangedFromStateMachine(BluetoothDevice device, int fromState,
             int toState) {
         synchronized (mStateMachines) {
@@ -1714,6 +1767,7 @@
      * @param toState to which audio connection state is the change
      */
     @VisibleForTesting
+    @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
     public void onAudioStateChangedFromStateMachine(BluetoothDevice device, int fromState,
             int toState) {
         synchronized (mStateMachines) {
@@ -1757,7 +1811,8 @@
         intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
         intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT
                 | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
-        sendBroadcastAsUser(intent, UserHandle.ALL, HeadsetService.BLUETOOTH_PERM);
+        sendBroadcastAsUser(intent, UserHandle.ALL, BLUETOOTH_CONNECT,
+                Utils.getTempAllowlistBroadcastOptions());
     }
 
     /**
diff --git a/src/com/android/bluetooth/hfp/HeadsetStateMachine.java b/src/com/android/bluetooth/hfp/HeadsetStateMachine.java
index 8a6bc55..03ba99a 100644
--- a/src/com/android/bluetooth/hfp/HeadsetStateMachine.java
+++ b/src/com/android/bluetooth/hfp/HeadsetStateMachine.java
@@ -16,6 +16,10 @@
 
 package com.android.bluetooth.hfp;
 
+import static android.Manifest.permission.BLUETOOTH_CONNECT;
+
+import android.annotation.RequiresPermission;
+import android.app.ActivityThread;
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothAssignedNumbers;
 import android.bluetooth.BluetoothDevice;
@@ -23,6 +27,7 @@
 import android.bluetooth.BluetoothProfile;
 import android.bluetooth.BluetoothProtoEnums;
 import android.bluetooth.hfp.BluetoothHfpProtoEnums;
+import android.content.Attributable;
 import android.content.Intent;
 import android.media.AudioManager;
 import android.os.Looper;
@@ -304,7 +309,7 @@
             intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
             intent.addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
             mHeadsetService.sendBroadcastAsUser(intent, UserHandle.ALL,
-                    HeadsetService.BLUETOOTH_PERM);
+                    BLUETOOTH_CONNECT, Utils.getTempAllowlistBroadcastOptions());
         }
 
         // Should not be called from enter() method
@@ -323,7 +328,7 @@
             intent.putExtra(BluetoothProfile.EXTRA_STATE, toState);
             intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
             mHeadsetService.sendBroadcastAsUser(intent, UserHandle.ALL,
-                    HeadsetService.BLUETOOTH_PERM);
+                    BLUETOOTH_CONNECT, Utils.getTempAllowlistBroadcastOptions());
         }
 
         /**
@@ -461,6 +466,8 @@
             switch (message.what) {
                 case CONNECT:
                     BluetoothDevice device = (BluetoothDevice) message.obj;
+                    Attributable.setAttributionSource(device,
+                            ActivityThread.currentAttributionSource());
                     stateLogD("Connecting to " + device);
                     if (!mDevice.equals(device)) {
                         stateLogE(
@@ -579,6 +586,8 @@
                 case CONNECT_TIMEOUT: {
                     // We timed out trying to connect, transition to Disconnected state
                     BluetoothDevice device = (BluetoothDevice) message.obj;
+                    Attributable.setAttributionSource(device,
+                            ActivityThread.currentAttributionSource());
                     if (!mDevice.equals(device)) {
                         stateLogE("Unknown device timeout " + device);
                         break;
@@ -737,6 +746,8 @@
                     break;
                 case CONNECT_TIMEOUT: {
                     BluetoothDevice device = (BluetoothDevice) message.obj;
+                    Attributable.setAttributionSource(device,
+                            ActivityThread.currentAttributionSource());
                     if (!mDevice.equals(device)) {
                         stateLogE("Unknown device timeout " + device);
                         break;
@@ -822,6 +833,8 @@
                             "Illegal message in generic handler: " + message);
                 case VOICE_RECOGNITION_START: {
                     BluetoothDevice device = (BluetoothDevice) message.obj;
+                    Attributable.setAttributionSource(device,
+                            ActivityThread.currentAttributionSource());
                     if (!mDevice.equals(device)) {
                         stateLogW("VOICE_RECOGNITION_START failed " + device
                                 + " is not currentDevice");
@@ -835,6 +848,8 @@
                 }
                 case VOICE_RECOGNITION_STOP: {
                     BluetoothDevice device = (BluetoothDevice) message.obj;
+                    Attributable.setAttributionSource(device,
+                            ActivityThread.currentAttributionSource());
                     if (!mDevice.equals(device)) {
                         stateLogW("VOICE_RECOGNITION_STOP failed " + device
                                 + " is not currentDevice");
@@ -862,6 +877,8 @@
                     break;
                 case CLCC_RSP_TIMEOUT: {
                     BluetoothDevice device = (BluetoothDevice) message.obj;
+                    Attributable.setAttributionSource(device,
+                            ActivityThread.currentAttributionSource());
                     if (!mDevice.equals(device)) {
                         stateLogW("CLCC_RSP_TIMEOUT failed " + device + " is not currentDevice");
                         break;
@@ -878,6 +895,8 @@
                     break;
                 case VOICE_RECOGNITION_RESULT: {
                     BluetoothDevice device = (BluetoothDevice) message.obj;
+                    Attributable.setAttributionSource(device,
+                            ActivityThread.currentAttributionSource());
                     if (!mDevice.equals(device)) {
                         stateLogW("VOICE_RECOGNITION_RESULT failed " + device
                                 + " is not currentDevice");
@@ -890,6 +909,8 @@
                 }
                 case DIALING_OUT_RESULT: {
                     BluetoothDevice device = (BluetoothDevice) message.obj;
+                    Attributable.setAttributionSource(device,
+                            ActivityThread.currentAttributionSource());
                     if (!mDevice.equals(device)) {
                         stateLogW("DIALING_OUT_RESULT failed " + device + " is not currentDevice");
                         break;
@@ -1048,11 +1069,15 @@
             switch (message.what) {
                 case CONNECT: {
                     BluetoothDevice device = (BluetoothDevice) message.obj;
+                    Attributable.setAttributionSource(device,
+                            ActivityThread.currentAttributionSource());
                     stateLogW("CONNECT, ignored, device=" + device + ", currentDevice" + mDevice);
                     break;
                 }
                 case DISCONNECT: {
                     BluetoothDevice device = (BluetoothDevice) message.obj;
+                    Attributable.setAttributionSource(device,
+                            ActivityThread.currentAttributionSource());
                     stateLogD("DISCONNECT from device=" + device);
                     if (!mDevice.equals(device)) {
                         stateLogW("DISCONNECT, device " + device + " not connected");
@@ -1158,6 +1183,8 @@
                     break;
                 case CONNECT_TIMEOUT: {
                     BluetoothDevice device = (BluetoothDevice) message.obj;
+                    Attributable.setAttributionSource(device,
+                            ActivityThread.currentAttributionSource());
                     if (!mDevice.equals(device)) {
                         stateLogW("CONNECT_TIMEOUT for unknown device " + device);
                         break;
@@ -1254,11 +1281,15 @@
             switch (message.what) {
                 case CONNECT: {
                     BluetoothDevice device = (BluetoothDevice) message.obj;
+                    Attributable.setAttributionSource(device,
+                            ActivityThread.currentAttributionSource());
                     stateLogW("CONNECT, ignored, device=" + device + ", currentDevice" + mDevice);
                     break;
                 }
                 case DISCONNECT: {
                     BluetoothDevice device = (BluetoothDevice) message.obj;
+                    Attributable.setAttributionSource(device,
+                            ActivityThread.currentAttributionSource());
                     stateLogD("DISCONNECT, device=" + device);
                     if (!mDevice.equals(device)) {
                         stateLogW("DISCONNECT, device " + device + " not connected");
@@ -1276,6 +1307,8 @@
                 }
                 case CONNECT_AUDIO: {
                     BluetoothDevice device = (BluetoothDevice) message.obj;
+                    Attributable.setAttributionSource(device,
+                            ActivityThread.currentAttributionSource());
                     if (!mDevice.equals(device)) {
                         stateLogW("CONNECT_AUDIO device is not connected " + device);
                         break;
@@ -1285,6 +1318,8 @@
                 }
                 case DISCONNECT_AUDIO: {
                     BluetoothDevice device = (BluetoothDevice) message.obj;
+                    Attributable.setAttributionSource(device,
+                            ActivityThread.currentAttributionSource());
                     if (!mDevice.equals(device)) {
                         stateLogW("DISCONNECT_AUDIO, failed, device=" + device + ", currentDevice="
                                 + mDevice);
@@ -1378,6 +1413,8 @@
                     break;
                 case CONNECT_TIMEOUT: {
                     BluetoothDevice device = (BluetoothDevice) message.obj;
+                    Attributable.setAttributionSource(device,
+                            ActivityThread.currentAttributionSource());
                     if (!mDevice.equals(device)) {
                         stateLogW("CONNECT_TIMEOUT for unknown device " + device);
                         break;
@@ -1500,7 +1537,8 @@
         intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
         intent.addCategory(BluetoothHeadset.VENDOR_SPECIFIC_HEADSET_EVENT_COMPANY_ID_CATEGORY + "."
                 + Integer.toString(companyId));
-        mHeadsetService.sendBroadcastAsUser(intent, UserHandle.ALL, HeadsetService.BLUETOOTH_PERM);
+        mHeadsetService.sendBroadcastAsUser(intent, UserHandle.ALL, BLUETOOTH_CONNECT,
+                Utils.getTempAllowlistBroadcastOptions());
     }
 
     private void setAudioParameters() {
@@ -1555,6 +1593,7 @@
         return commandType;
     }
 
+    @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
     private void processDialCall(String number) {
         String dialNumber;
         if (mHeadsetService.hasDeviceInitiatedDialingOut()) {
@@ -1599,6 +1638,7 @@
         mNeedDialingOutReply = true;
     }
 
+    @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
     private void processVrEvent(int state) {
         if (state == HeadsetHalConstants.VR_STATE_STARTED) {
             if (!mHeadsetService.startVoiceRecognitionByHeadset(mDevice)) {
@@ -1663,6 +1703,7 @@
                 HEADSET_WBS));
     }
 
+    @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
     private void processAtChld(int chld, BluetoothDevice device) {
         if (mSystemInterface.processChld(chld)) {
             mNativeInterface.atResponseCode(device, HeadsetHalConstants.AT_RESPONSE_OK, 0);
@@ -1671,16 +1712,18 @@
         }
     }
 
+    @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
     private void processSubscriberNumberRequest(BluetoothDevice device) {
         String number = mSystemInterface.getSubscriberNumber();
         if (number != null) {
             mNativeInterface.atResponseString(device,
                     "+CNUM: ,\"" + number + "\"," + PhoneNumberUtils.toaFromString(number) + ",,4");
-            mNativeInterface.atResponseCode(device, HeadsetHalConstants.AT_RESPONSE_OK, 0);
         } else {
-            Log.e(TAG, "getSubscriberNumber returns null");
-            mNativeInterface.atResponseCode(device, HeadsetHalConstants.AT_RESPONSE_ERROR, 0);
+            Log.e(TAG, "getSubscriberNumber returns null, no subscriber number can reply");
         }
+
+        // Based on spec, if subscriber number is empty, we should still return OK response.
+        mNativeInterface.atResponseCode(device, HeadsetHalConstants.AT_RESPONSE_OK, 0);
     }
 
     private void processAtCind(BluetoothDevice device) {
@@ -1704,6 +1747,7 @@
                 phoneState.getCindBatteryCharge());
     }
 
+    @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
     private void processAtCops(BluetoothDevice device) {
         // Get operator name suggested by Telephony
         String operatorName = null;
@@ -1721,6 +1765,7 @@
         mNativeInterface.copsResponse(device, operatorName);
     }
 
+    @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
     private void processAtClcc(BluetoothDevice device) {
         if (mHeadsetService.isVirtualCallStarted()) {
             // In virtual call, send our phone number instead of remote phone number
@@ -1907,6 +1952,7 @@
     }
 
     // HSP +CKPD command
+    @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
     private void processKeyPressed(BluetoothDevice device) {
         if (mSystemInterface.isRinging()) {
             mSystemInterface.answerCall(device);
@@ -1954,7 +2000,8 @@
         intent.putExtra(BluetoothHeadset.EXTRA_HF_INDICATORS_IND_ID, indId);
         intent.putExtra(BluetoothHeadset.EXTRA_HF_INDICATORS_IND_VALUE, indValue);
 
-        mHeadsetService.sendBroadcast(intent, HeadsetService.BLUETOOTH_PERM);
+        mHeadsetService.sendBroadcast(intent, BLUETOOTH_CONNECT,
+                Utils.getTempAllowlistBroadcastOptions());
     }
 
     private void processAtBind(String atString, BluetoothDevice device) {
diff --git a/src/com/android/bluetooth/hfp/HeadsetSystemInterface.java b/src/com/android/bluetooth/hfp/HeadsetSystemInterface.java
index 52ca1bc..1088281 100644
--- a/src/com/android/bluetooth/hfp/HeadsetSystemInterface.java
+++ b/src/com/android/bluetooth/hfp/HeadsetSystemInterface.java
@@ -18,21 +18,24 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
 import android.bluetooth.BluetoothDevice;
 import android.bluetooth.BluetoothHeadset;
-import android.bluetooth.IBluetoothHeadsetPhone;
+import com.android.bluetooth.telephony.BluetoothInCallService;
 import android.content.ActivityNotFoundException;
 import android.content.ComponentName;
 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.net.Uri;
 import android.os.PowerManager;
-import android.os.RemoteException;
+import android.telecom.PhoneAccount;
+import android.telecom.PhoneAccountHandle;
+import android.telecom.TelecomManager;
+import android.telephony.TelephonyManager;
 import android.util.Log;
 
 import com.android.internal.annotations.VisibleForTesting;
@@ -52,28 +55,8 @@
     private final AudioManager mAudioManager;
     private final HeadsetPhoneState mHeadsetPhoneState;
     private PowerManager.WakeLock mVoiceRecognitionWakeLock;
-    private volatile IBluetoothHeadsetPhone mPhoneProxy;
-    private final ServiceConnection mPhoneProxyConnection = new ServiceConnection() {
-        @Override
-        public void onServiceConnected(ComponentName className, IBinder service) {
-            if (DBG) {
-                Log.d(TAG, "Proxy object connected");
-            }
-            synchronized (HeadsetSystemInterface.this) {
-                mPhoneProxy = IBluetoothHeadsetPhone.Stub.asInterface(service);
-            }
-        }
-
-        @Override
-        public void onServiceDisconnected(ComponentName className) {
-            if (DBG) {
-                Log.d(TAG, "Proxy object disconnected");
-            }
-            synchronized (HeadsetSystemInterface.this) {
-                mPhoneProxy = null;
-            }
-        }
-    };
+    private final TelephonyManager mTelephonyManager;
+    private final TelecomManager mTelecomManager;
 
     HeadsetSystemInterface(HeadsetService headsetService) {
         if (headsetService == null) {
@@ -86,21 +69,13 @@
         mVoiceRecognitionWakeLock =
                 powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG + ":VoiceRecognition");
         mVoiceRecognitionWakeLock.setReferenceCounted(false);
-        mHeadsetPhoneState = new HeadsetPhoneState(mHeadsetService);
+        mHeadsetPhoneState = new com.android.bluetooth.hfp.HeadsetPhoneState(mHeadsetService);
+        mTelephonyManager = mHeadsetService.getSystemService(TelephonyManager.class);
+        mTelecomManager = mHeadsetService.getSystemService(TelecomManager.class);
     }
 
-    /**
-     * Initialize this system interface
-     */
-    public synchronized void init() {
-        // Bind to Telecom phone proxy service
-        Intent intent = new Intent(IBluetoothHeadsetPhone.class.getName());
-        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.wtf(TAG, "Could not bind to IBluetoothHeadsetPhone Service, intent=" + intent);
-        }
+    private BluetoothInCallService getBluetoothInCallServiceInstance() {
+        return BluetoothInCallService.getInstance();
     }
 
     /**
@@ -140,14 +115,6 @@
      * Stop this system interface
      */
     public synchronized void stop() {
-        if (mPhoneProxy != null) {
-            if (DBG) {
-                Log.d(TAG, "Unbinding phone proxy");
-            }
-            mPhoneProxy = null;
-            // Synchronization should make sure unbind can be successful
-            mHeadsetService.unbindService(mPhoneProxyConnection);
-        }
         mHeadsetPhoneState.cleanup();
     }
 
@@ -188,19 +155,16 @@
      * @param device the Bluetooth device used for answering this call
      */
     @VisibleForTesting
+    @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
     public void answerCall(BluetoothDevice device) {
         if (device == null) {
             Log.w(TAG, "answerCall device is null");
             return;
         }
-
-        if (mPhoneProxy != null) {
-            try {
-                mHeadsetService.setActiveDevice(device);
-                mPhoneProxy.answerCall();
-            } catch (RemoteException e) {
-                Log.e(TAG, Log.getStackTraceString(new Throwable()));
-            }
+        BluetoothInCallService bluetoothInCallService = getBluetoothInCallServiceInstance();
+        if (bluetoothInCallService != null) {
+            mHeadsetService.setActiveDevice(device);
+            bluetoothInCallService.answerCall();
         } else {
             Log.e(TAG, "Handsfree phone proxy null for answering call");
         }
@@ -212,6 +176,7 @@
      * @param device the Bluetooth device used for hanging up this call
      */
     @VisibleForTesting
+    @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
     public void hangupCall(BluetoothDevice device) {
         if (device == null) {
             Log.w(TAG, "hangupCall device is null");
@@ -222,12 +187,9 @@
         if (mHeadsetService.isVirtualCallStarted()) {
             mHeadsetService.stopScoUsingVirtualVoiceCall();
         } else {
-            if (mPhoneProxy != null) {
-                try {
-                    mPhoneProxy.hangupCall();
-                } catch (RemoteException e) {
-                    Log.e(TAG, Log.getStackTraceString(new Throwable()));
-                }
+            BluetoothInCallService bluetoothInCallService = getBluetoothInCallServiceInstance();
+            if (bluetoothInCallService != null) {
+                bluetoothInCallService.hangupCall();
             } else {
                 Log.e(TAG, "Handsfree phone proxy null for hanging up call");
             }
@@ -241,17 +203,15 @@
      * @param device the Bluetooth device that sent this code
      */
     @VisibleForTesting
+    @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
     public boolean sendDtmf(int dtmf, BluetoothDevice device) {
         if (device == null) {
             Log.w(TAG, "sendDtmf device is null");
             return false;
         }
-        if (mPhoneProxy != null) {
-            try {
-                return mPhoneProxy.sendDtmf(dtmf);
-            } catch (RemoteException e) {
-                Log.e(TAG, Log.getStackTraceString(new Throwable()));
-            }
+        BluetoothInCallService bluetoothInCallService = getBluetoothInCallServiceInstance();
+        if (bluetoothInCallService != null) {
+            return bluetoothInCallService.sendDtmf(dtmf);
         } else {
             Log.e(TAG, "Handsfree phone proxy null for sending DTMF");
         }
@@ -264,13 +224,11 @@
      * @param chld index of the call to hold
      */
     @VisibleForTesting
+    @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
     public boolean processChld(int chld) {
-        if (mPhoneProxy != null) {
-            try {
-                return mPhoneProxy.processChld(chld);
-            } catch (RemoteException e) {
-                Log.e(TAG, Log.getStackTraceString(new Throwable()));
-            }
+        BluetoothInCallService bluetoothInCallService = getBluetoothInCallServiceInstance();
+        if (bluetoothInCallService != null) {
+            return bluetoothInCallService.processChld(chld);
         } else {
             Log.e(TAG, "Handsfree phone proxy null for sending DTMF");
         }
@@ -283,20 +241,51 @@
      * @return null on error, empty string if not available
      */
     @VisibleForTesting
+    @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
     public String getNetworkOperator() {
-        final IBluetoothHeadsetPhone phoneProxy = mPhoneProxy;
-        if (phoneProxy == null) {
-            Log.e(TAG, "getNetworkOperator() failed: mPhoneProxy is null");
+        BluetoothInCallService bluetoothInCallService = getBluetoothInCallServiceInstance();
+        if (bluetoothInCallService == null) {
+            Log.e(TAG, "getNetworkOperator() failed: mBluetoothInCallService is null");
             return null;
         }
-        try {
-            // Should never return null
-            return mPhoneProxy.getNetworkOperator();
-        } catch (RemoteException exception) {
-            Log.e(TAG, "getNetworkOperator() failed: " + exception.getMessage());
-            exception.printStackTrace();
-            return null;
+        // Should never return null
+        return bluetoothInCallService.getNetworkOperator();
+    }
+
+    /**
+     * Get the phone number of this device without incall service
+     *
+     * @return emptry if unavailable
+     */
+    @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
+    private String getNumberWithoutInCallService() {
+        PhoneAccount account = null;
+        String address = "";
+
+        // Get the label for the default Phone Account.
+        List<PhoneAccountHandle> handles =
+                mTelecomManager.getPhoneAccountsSupportingScheme(PhoneAccount.SCHEME_TEL);
+        while (handles.iterator().hasNext()) {
+            account = mTelecomManager.getPhoneAccount(handles.iterator().next());
+            break;
         }
+
+        if (account != null) {
+            Uri addressUri = account.getAddress();
+
+            if (addressUri != null) {
+                address = addressUri.getSchemeSpecificPart();
+            }
+        }
+
+        if (address.isEmpty()) {
+            address = mTelephonyManager.getLine1Number();
+            if (address == null) address = "";
+        }
+
+        Log.i(TAG, String.format("get phone number -> '%s'", address));
+
+        return address;
     }
 
     /**
@@ -305,19 +294,16 @@
      * @return null if unavailable
      */
     @VisibleForTesting
+    @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
     public String getSubscriberNumber() {
-        final IBluetoothHeadsetPhone phoneProxy = mPhoneProxy;
-        if (phoneProxy == null) {
-            Log.e(TAG, "getSubscriberNumber() failed: mPhoneProxy is null");
-            return null;
+        BluetoothInCallService bluetoothInCallService = getBluetoothInCallServiceInstance();
+        if (bluetoothInCallService == null) {
+            Log.e(TAG, "getSubscriberNumber() failed: mBluetoothInCallService is null");
+            Log.i(TAG, "Try to get phone number without mBluetoothInCallService.");
+            return getNumberWithoutInCallService();
+
         }
-        try {
-            return mPhoneProxy.getSubscriberNumber();
-        } catch (RemoteException exception) {
-            Log.e(TAG, "getSubscriberNumber() failed: " + exception.getMessage());
-            exception.printStackTrace();
-            return null;
-        }
+        return bluetoothInCallService.getSubscriberNumber();
     }
 
 
@@ -328,19 +314,14 @@
      * @return
      */
     @VisibleForTesting
+    @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
     public boolean listCurrentCalls() {
-        final IBluetoothHeadsetPhone phoneProxy = mPhoneProxy;
-        if (phoneProxy == null) {
-            Log.e(TAG, "listCurrentCalls() failed: mPhoneProxy is null");
+        BluetoothInCallService bluetoothInCallService = getBluetoothInCallServiceInstance();
+        if (bluetoothInCallService == null) {
+            Log.e(TAG, "listCurrentCalls() failed: mBluetoothInCallService is null");
             return false;
         }
-        try {
-            return mPhoneProxy.listCurrentCalls();
-        } catch (RemoteException exception) {
-            Log.e(TAG, "listCurrentCalls() failed: " + exception.getMessage());
-            exception.printStackTrace();
-            return false;
-        }
+        return bluetoothInCallService.listCurrentCalls();
     }
 
     /**
@@ -348,14 +329,11 @@
      * through {@link BluetoothHeadset#phoneStateChanged(int, int, int, String, int)}
      */
     @VisibleForTesting
+    @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
     public void queryPhoneState() {
-        final IBluetoothHeadsetPhone phoneProxy = mPhoneProxy;
-        if (phoneProxy != null) {
-            try {
-                mPhoneProxy.queryPhoneState();
-            } catch (RemoteException e) {
-                Log.e(TAG, Log.getStackTraceString(new Throwable()));
-            }
+        BluetoothInCallService bluetoothInCallService = getBluetoothInCallServiceInstance();
+        if (bluetoothInCallService != null) {
+            bluetoothInCallService.queryPhoneState();
         } else {
             Log.e(TAG, "Handsfree phone proxy null for query phone state");
         }
diff --git a/src/com/android/bluetooth/hfpclient/HeadsetClientHalConstants.java b/src/com/android/bluetooth/hfpclient/HeadsetClientHalConstants.java
index d7b5067..907890d 100644
--- a/src/com/android/bluetooth/hfpclient/HeadsetClientHalConstants.java
+++ b/src/com/android/bluetooth/hfpclient/HeadsetClientHalConstants.java
@@ -97,7 +97,7 @@
     static final int CMD_COMPLETE_ERROR_BUSY = 3;
     static final int CMD_COMPLETE_ERROR_NO_ANSWER = 4;
     static final int CMD_COMPLETE_ERROR_DELAYED = 5;
-    static final int CMD_COMPLETE_ERROR_BLACKLISTED = 6;
+    static final int CMD_COMPLETE_ERROR_REJECTLISTED = 6;
     static final int CMD_COMPLETE_ERROR_CME = 7;
 
     // match up with bthf_client_call_action_t enum of bt_hf_client.h
@@ -146,6 +146,11 @@
     static final int PEER_FEAT_EXTERR = 0x00000100;
     // Codec Negotiation
     static final int PEER_FEAT_CODEC = 0x00000200;
+    // HFP 1.7 features
+    // HF Indicators
+    static final int PEER_FEAT_HF_IND = 0x00000400;
+    // ESCO S4 link setting
+    static final int PEER_FEAT_ESCO_S4 = 0x00000800;
 
     // AG's 3WC features masks
     // match up with masks in bt_hf_client.h
@@ -171,6 +176,7 @@
 
     static final int HANDSFREECLIENT_AT_CMD_NREC = 15;
     static final int HANDSFREECLIENT_AT_CMD_VENDOR_SPECIFIC_CMD = 16;
+    static final int HANDSFREECLIENT_AT_CMD_BIEV = 17;
 
     // Flag to check for local NREC support
     static final boolean HANDSFREECLIENT_NREC_SUPPORTED = true;
diff --git a/src/com/android/bluetooth/hfpclient/HeadsetClientService.java b/src/com/android/bluetooth/hfpclient/HeadsetClientService.java
index ce843d9..dfb9f41 100644
--- a/src/com/android/bluetooth/hfpclient/HeadsetClientService.java
+++ b/src/com/android/bluetooth/hfpclient/HeadsetClientService.java
@@ -16,16 +16,20 @@
 
 package com.android.bluetooth.hfpclient;
 
+import android.annotation.RequiresPermission;
 import android.bluetooth.BluetoothDevice;
 import android.bluetooth.BluetoothHeadsetClient;
 import android.bluetooth.BluetoothHeadsetClientCall;
 import android.bluetooth.BluetoothProfile;
 import android.bluetooth.IBluetoothHeadsetClient;
+import android.content.Attributable;
+import android.content.AttributionSource;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.media.AudioManager;
+import android.os.BatteryManager;
 import android.os.Bundle;
 import android.os.HandlerThread;
 import android.os.Message;
@@ -34,13 +38,16 @@
 import com.android.bluetooth.Utils;
 import com.android.bluetooth.btservice.AdapterService;
 import com.android.bluetooth.btservice.ProfileService;
+import com.android.bluetooth.btservice.storage.DatabaseManager;
 import com.android.bluetooth.hfpclient.connserv.HfpClientConnectionService;
+import com.android.internal.annotations.VisibleForTesting;
 
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 import java.util.UUID;
 
 /**
@@ -58,7 +65,10 @@
     private NativeInterface mNativeInterface = null;
     private HandlerThread mSmThread = null;
     private HeadsetClientStateMachineFactory mSmFactory = null;
+    private DatabaseManager mDatabaseManager;
     private AudioManager mAudioManager = null;
+    private BatteryManager mBatteryManager = null;
+    private int mLastBatteryLevel = -1;
     // Maxinum number of devices we can try connecting to in one session
     private static final int MAX_STATE_MACHINES_POSSIBLE = 100;
 
@@ -79,10 +89,15 @@
             return false;
         }
 
+        mDatabaseManager = Objects.requireNonNull(AdapterService.getAdapterService().getDatabase(),
+                "DatabaseManager cannot be null when HeadsetClientService starts");
+
         // Setup the JNI service
         mNativeInterface = NativeInterface.getInstance();
         mNativeInterface.initialize();
 
+        mBatteryManager = getSystemService(BatteryManager.class);
+
         mAudioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
         if (mAudioManager == null) {
             Log.e(TAG, "AudioManager service doesn't exist?");
@@ -95,6 +110,7 @@
         mStateMachineMap.clear();
 
         IntentFilter filter = new IntentFilter(AudioManager.VOLUME_CHANGED_ACTION);
+        filter.addAction(Intent.ACTION_BATTERY_CHANGED);
         registerReceiver(mBroadcastReceiver, filter);
 
         // Start the HfpClientConnectionService to create connection with telecom when HFP
@@ -177,6 +193,29 @@
                         }
                     }
                 }
+            } else if (action.equals(Intent.ACTION_BATTERY_CHANGED)) {
+                int batteryIndicatorID = 2;
+                int batteryLevel = intent.getIntExtra(BatteryManager.EXTRA_LEVEL, 0);
+
+                if (batteryLevel == mLastBatteryLevel) {
+                    return;
+                }
+                mLastBatteryLevel = batteryLevel;
+
+                if (DBG) {
+                    Log.d(TAG,
+                            "Send battery level update BIEV(2," + batteryLevel + ") command");
+                }
+
+                synchronized (this) {
+                    for (HeadsetClientStateMachine sm : mStateMachineMap.values()) {
+                        if (sm != null) {
+                            sm.sendMessage(HeadsetClientStateMachine.SEND_BIEV,
+                                    batteryIndicatorID,
+                                    batteryLevel);
+                        }
+                    }
+                }
             }
         }
     };
@@ -197,23 +236,20 @@
             mService = null;
         }
 
-        private HeadsetClientService getService() {
-            if (!Utils.checkCaller()) {
-                Log.w(TAG, "HeadsetClient call not allowed for non-active user");
+        @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
+        private HeadsetClientService getService(AttributionSource source) {
+            if (!Utils.checkCallerIsSystemOrActiveUser(TAG)
+                    || !Utils.checkServiceAvailable(mService, TAG)
+                    || !Utils.checkConnectPermissionForDataDelivery(mService, source, TAG)) {
                 return null;
             }
-
-            if (mService != null && mService.isAvailable()) {
-                return mService;
-            }
-
-            Log.e(TAG, "HeadsetClientService is not available.");
-            return null;
-        }
+            return mService;
+       }
 
         @Override
-        public boolean connect(BluetoothDevice device) {
-            HeadsetClientService service = getService();
+        public boolean connect(BluetoothDevice device, AttributionSource source) {
+            Attributable.setAttributionSource(device, source);
+            HeadsetClientService service = getService(source);
             if (service == null) {
                 return false;
             }
@@ -221,8 +257,9 @@
         }
 
         @Override
-        public boolean disconnect(BluetoothDevice device) {
-            HeadsetClientService service = getService();
+        public boolean disconnect(BluetoothDevice device, AttributionSource source) {
+            Attributable.setAttributionSource(device, source);
+            HeadsetClientService service = getService(source);
             if (service == null) {
                 return false;
             }
@@ -230,8 +267,8 @@
         }
 
         @Override
-        public List<BluetoothDevice> getConnectedDevices() {
-            HeadsetClientService service = getService();
+        public List<BluetoothDevice> getConnectedDevices(AttributionSource source) {
+            HeadsetClientService service = getService(source);
             if (service == null) {
                 return new ArrayList<BluetoothDevice>(0);
             }
@@ -239,8 +276,9 @@
         }
 
         @Override
-        public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
-            HeadsetClientService service = getService();
+        public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states,
+                AttributionSource source) {
+            HeadsetClientService service = getService(source);
             if (service == null) {
                 return new ArrayList<BluetoothDevice>(0);
             }
@@ -248,8 +286,9 @@
         }
 
         @Override
-        public int getConnectionState(BluetoothDevice device) {
-            HeadsetClientService service = getService();
+        public int getConnectionState(BluetoothDevice device, AttributionSource source) {
+            Attributable.setAttributionSource(device, source);
+            HeadsetClientService service = getService(source);
             if (service == null) {
                 return BluetoothProfile.STATE_DISCONNECTED;
             }
@@ -257,8 +296,10 @@
         }
 
         @Override
-        public boolean setConnectionPolicy(BluetoothDevice device, int connectionPolicy) {
-            HeadsetClientService service = getService();
+        public boolean setConnectionPolicy(BluetoothDevice device, int connectionPolicy,
+                AttributionSource source) {
+            Attributable.setAttributionSource(device, source);
+            HeadsetClientService service = getService(source);
             if (service == null) {
                 return false;
             }
@@ -266,8 +307,9 @@
         }
 
         @Override
-        public int getConnectionPolicy(BluetoothDevice device) {
-            HeadsetClientService service = getService();
+        public int getConnectionPolicy(BluetoothDevice device, AttributionSource source) {
+            Attributable.setAttributionSource(device, source);
+            HeadsetClientService service = getService(source);
             if (service == null) {
                 return BluetoothProfile.CONNECTION_POLICY_UNKNOWN;
             }
@@ -275,8 +317,9 @@
         }
 
         @Override
-        public boolean startVoiceRecognition(BluetoothDevice device) {
-            HeadsetClientService service = getService();
+        public boolean startVoiceRecognition(BluetoothDevice device, AttributionSource source) {
+            Attributable.setAttributionSource(device, source);
+            HeadsetClientService service = getService(source);
             if (service == null) {
                 return false;
             }
@@ -284,8 +327,9 @@
         }
 
         @Override
-        public boolean stopVoiceRecognition(BluetoothDevice device) {
-            HeadsetClientService service = getService();
+        public boolean stopVoiceRecognition(BluetoothDevice device, AttributionSource source) {
+            Attributable.setAttributionSource(device, source);
+            HeadsetClientService service = getService(source);
             if (service == null) {
                 return false;
             }
@@ -293,8 +337,9 @@
         }
 
         @Override
-        public int getAudioState(BluetoothDevice device) {
-            HeadsetClientService service = getService();
+        public int getAudioState(BluetoothDevice device, AttributionSource source) {
+            Attributable.setAttributionSource(device, source);
+            HeadsetClientService service = getService(source);
             if (service == null) {
                 return BluetoothHeadsetClient.STATE_AUDIO_DISCONNECTED;
             }
@@ -302,19 +347,31 @@
         }
 
         @Override
-        public void setAudioRouteAllowed(BluetoothDevice device, boolean allowed) {
+        public void setAudioRouteAllowed(BluetoothDevice device, boolean allowed,
+                AttributionSource source) {
+            Attributable.setAttributionSource(device, source);
+            HeadsetClientService service = getService(source);
+            if (service == null) {
+                return;
+            }
             Log.e(TAG, "setAudioRouteAllowed API not supported");
         }
 
         @Override
-        public boolean getAudioRouteAllowed(BluetoothDevice device) {
+        public boolean getAudioRouteAllowed(BluetoothDevice device, AttributionSource source) {
+            Attributable.setAttributionSource(device, source);
+            HeadsetClientService service = getService(source);
+            if (service == null) {
+                return false;
+            }
             Log.e(TAG, "getAudioRouteAllowed API not supported");
             return false;
         }
 
         @Override
-        public boolean connectAudio(BluetoothDevice device) {
-            HeadsetClientService service = getService();
+        public boolean connectAudio(BluetoothDevice device, AttributionSource source) {
+            Attributable.setAttributionSource(device, source);
+            HeadsetClientService service = getService(source);
             if (service == null) {
                 return false;
             }
@@ -322,8 +379,9 @@
         }
 
         @Override
-        public boolean disconnectAudio(BluetoothDevice device) {
-            HeadsetClientService service = getService();
+        public boolean disconnectAudio(BluetoothDevice device, AttributionSource source) {
+            Attributable.setAttributionSource(device, source);
+            HeadsetClientService service = getService(source);
             if (service == null) {
                 return false;
             }
@@ -331,8 +389,9 @@
         }
 
         @Override
-        public boolean acceptCall(BluetoothDevice device, int flag) {
-            HeadsetClientService service = getService();
+        public boolean acceptCall(BluetoothDevice device, int flag, AttributionSource source) {
+            Attributable.setAttributionSource(device, source);
+            HeadsetClientService service = getService(source);
             if (service == null) {
                 return false;
             }
@@ -340,8 +399,9 @@
         }
 
         @Override
-        public boolean rejectCall(BluetoothDevice device) {
-            HeadsetClientService service = getService();
+        public boolean rejectCall(BluetoothDevice device, AttributionSource source) {
+            Attributable.setAttributionSource(device, source);
+            HeadsetClientService service = getService(source);
             if (service == null) {
                 return false;
             }
@@ -349,8 +409,9 @@
         }
 
         @Override
-        public boolean holdCall(BluetoothDevice device) {
-            HeadsetClientService service = getService();
+        public boolean holdCall(BluetoothDevice device, AttributionSource source) {
+            Attributable.setAttributionSource(device, source);
+            HeadsetClientService service = getService(source);
             if (service == null) {
                 return false;
             }
@@ -358,8 +419,10 @@
         }
 
         @Override
-        public boolean terminateCall(BluetoothDevice device, BluetoothHeadsetClientCall call) {
-            HeadsetClientService service = getService();
+        public boolean terminateCall(BluetoothDevice device, BluetoothHeadsetClientCall call,
+                AttributionSource source) {
+            Attributable.setAttributionSource(device, source);
+            HeadsetClientService service = getService(source);
             if (service == null) {
                 Log.w(TAG, "service is null");
                 return false;
@@ -368,8 +431,9 @@
         }
 
         @Override
-        public boolean explicitCallTransfer(BluetoothDevice device) {
-            HeadsetClientService service = getService();
+        public boolean explicitCallTransfer(BluetoothDevice device, AttributionSource source) {
+            Attributable.setAttributionSource(device, source);
+            HeadsetClientService service = getService(source);
             if (service == null) {
                 return false;
             }
@@ -377,8 +441,10 @@
         }
 
         @Override
-        public boolean enterPrivateMode(BluetoothDevice device, int index) {
-            HeadsetClientService service = getService();
+        public boolean enterPrivateMode(BluetoothDevice device, int index,
+                AttributionSource source) {
+            Attributable.setAttributionSource(device, source);
+            HeadsetClientService service = getService(source);
             if (service == null) {
                 return false;
             }
@@ -386,8 +452,10 @@
         }
 
         @Override
-        public BluetoothHeadsetClientCall dial(BluetoothDevice device, String number) {
-            HeadsetClientService service = getService();
+        public BluetoothHeadsetClientCall dial(BluetoothDevice device, String number,
+                AttributionSource source) {
+            Attributable.setAttributionSource(device, source);
+            HeadsetClientService service = getService(source);
             if (service == null) {
                 return null;
             }
@@ -395,8 +463,10 @@
         }
 
         @Override
-        public List<BluetoothHeadsetClientCall> getCurrentCalls(BluetoothDevice device) {
-            HeadsetClientService service = getService();
+        public List<BluetoothHeadsetClientCall> getCurrentCalls(BluetoothDevice device,
+                AttributionSource source) {
+            Attributable.setAttributionSource(device, source);
+            HeadsetClientService service = getService(source);
             if (service == null) {
                 return new ArrayList<BluetoothHeadsetClientCall>();
             }
@@ -404,8 +474,9 @@
         }
 
         @Override
-        public boolean sendDTMF(BluetoothDevice device, byte code) {
-            HeadsetClientService service = getService();
+        public boolean sendDTMF(BluetoothDevice device, byte code, AttributionSource source) {
+            Attributable.setAttributionSource(device, source);
+            HeadsetClientService service = getService(source);
             if (service == null) {
                 return false;
             }
@@ -413,8 +484,9 @@
         }
 
         @Override
-        public boolean getLastVoiceTagNumber(BluetoothDevice device) {
-            HeadsetClientService service = getService();
+        public boolean getLastVoiceTagNumber(BluetoothDevice device, AttributionSource source) {
+            Attributable.setAttributionSource(device, source);
+            HeadsetClientService service = getService(source);
             if (service == null) {
                 return false;
             }
@@ -422,8 +494,9 @@
         }
 
         @Override
-        public Bundle getCurrentAgEvents(BluetoothDevice device) {
-            HeadsetClientService service = getService();
+        public Bundle getCurrentAgEvents(BluetoothDevice device, AttributionSource source) {
+            Attributable.setAttributionSource(device, source);
+            HeadsetClientService service = getService(source);
             if (service == null) {
                 return null;
             }
@@ -431,8 +504,10 @@
         }
 
         @Override
-        public boolean sendVendorAtCommand(BluetoothDevice device, int vendorId, String atCommand) {
-            HeadsetClientService service = getService();
+        public boolean sendVendorAtCommand(BluetoothDevice device, int vendorId, String atCommand,
+                AttributionSource source) {
+            Attributable.setAttributionSource(device, source);
+            HeadsetClientService service = getService(source);
             if (service == null) {
                 return false;
             }
@@ -440,8 +515,9 @@
         }
 
         @Override
-        public Bundle getCurrentAgFeatures(BluetoothDevice device) {
-            HeadsetClientService service = getService();
+        public Bundle getCurrentAgFeatures(BluetoothDevice device, AttributionSource source) {
+            Attributable.setAttributionSource(device, source);
+            HeadsetClientService service = getService(source);
             if (service == null) {
                 return null;
             }
@@ -464,7 +540,9 @@
         return sHeadsetClientService;
     }
 
-    private static synchronized void setHeadsetClientService(HeadsetClientService instance) {
+    /** Set a {@link HeadsetClientService} instance. */
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+    public static synchronized void setHeadsetClientService(HeadsetClientService instance) {
         if (DBG) {
             Log.d(TAG, "setHeadsetClientService(): set to: " + instance);
         }
@@ -472,7 +550,6 @@
     }
 
     public boolean connect(BluetoothDevice device) {
-        enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission");
         if (DBG) {
             Log.d(TAG, "connect " + device);
         }
@@ -499,7 +576,6 @@
      * @return true if hfp client profile successfully disconnected, false otherwise
      */
     public boolean disconnect(BluetoothDevice device) {
-        enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission");
         HeadsetClientStateMachine sm = getStateMachine(device);
         if (sm == null) {
             Log.e(TAG, "Cannot allocate SM for device " + device);
@@ -517,8 +593,6 @@
     }
 
     public synchronized List<BluetoothDevice> getConnectedDevices() {
-        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
-
         ArrayList<BluetoothDevice> connectedDevices = new ArrayList<>();
         for (BluetoothDevice bd : mStateMachineMap.keySet()) {
             HeadsetClientStateMachine sm = mStateMachineMap.get(bd);
@@ -530,7 +604,6 @@
     }
 
     private synchronized List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
-        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
         List<BluetoothDevice> devices = new ArrayList<BluetoothDevice>();
         for (BluetoothDevice bd : mStateMachineMap.keySet()) {
             for (int state : states) {
@@ -553,7 +626,6 @@
      * {@link BluetoothProfile#STATE_DISCONNECTING} if this profile is being disconnected
      */
     public synchronized int getConnectionState(BluetoothDevice device) {
-        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
         HeadsetClientStateMachine sm = mStateMachineMap.get(device);
         if (sm != null) {
             return sm.getConnectionState(device);
@@ -577,12 +649,14 @@
      * @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 connectionPolicy " + device + " = " + connectionPolicy);
         }
-        AdapterService.getAdapterService().getDatabase().setProfileConnectionPolicy(
-                device, BluetoothProfile.HEADSET_CLIENT, connectionPolicy);
+
+        if (!mDatabaseManager.setProfileConnectionPolicy(device, BluetoothProfile.HEADSET_CLIENT,
+                  connectionPolicy)) {
+            return false;
+        }
         if (connectionPolicy == BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
             connect(device);
         } else if (connectionPolicy == BluetoothProfile.CONNECTION_POLICY_FORBIDDEN) {
@@ -604,13 +678,11 @@
      * @hide
      */
     public int getConnectionPolicy(BluetoothDevice device) {
-        enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission");
-        return AdapterService.getAdapterService().getDatabase()
+        return mDatabaseManager
                 .getProfileConnectionPolicy(device, BluetoothProfile.HEADSET_CLIENT);
     }
 
     boolean startVoiceRecognition(BluetoothDevice device) {
-        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
         HeadsetClientStateMachine sm = getStateMachine(device);
         if (sm == null) {
             Log.e(TAG, "Cannot allocate SM for device " + device);
@@ -625,7 +697,6 @@
     }
 
     boolean stopVoiceRecognition(BluetoothDevice device) {
-        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
         HeadsetClientStateMachine sm = getStateMachine(device);
         if (sm == null) {
             Log.e(TAG, "Cannot allocate SM for device " + device);
@@ -639,7 +710,13 @@
         return true;
     }
 
-    int getAudioState(BluetoothDevice device) {
+    /**
+     * Gets audio state of the connection with {@code device}.
+     *
+     * <p>Can be one of {@link STATE_AUDIO_CONNECTED}, {@link STATE_AUDIO_CONNECTING}, or
+     * {@link STATE_AUDIO_DISCONNECTED}.
+     */
+    public int getAudioState(BluetoothDevice device) {
         HeadsetClientStateMachine sm = getStateMachine(device);
         if (sm == null) {
             Log.e(TAG, "Cannot allocate SM for device " + device);
@@ -650,7 +727,6 @@
     }
 
     boolean connectAudio(BluetoothDevice device) {
-        enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission");
         HeadsetClientStateMachine sm = getStateMachine(device);
         if (sm == null) {
             Log.e(TAG, "Cannot allocate SM for device " + device);
@@ -668,7 +744,6 @@
     }
 
     boolean disconnectAudio(BluetoothDevice device) {
-        enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission");
         HeadsetClientStateMachine sm = getStateMachine(device);
         if (sm == null) {
             Log.e(TAG, "Cannot allocate SM for device " + device);
@@ -683,7 +758,6 @@
     }
 
     boolean holdCall(BluetoothDevice device) {
-        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
         HeadsetClientStateMachine sm = getStateMachine(device);
         if (sm == null) {
             Log.e(TAG, "Cannot allocate SM for device " + device);
@@ -701,7 +775,6 @@
     }
 
     boolean acceptCall(BluetoothDevice device, int flag) {
-        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
         /* Phonecalls from a single device are supported, hang up any calls on the other phone */
         synchronized (this) {
             for (Map.Entry<BluetoothDevice, HeadsetClientStateMachine> entry : mStateMachineMap
@@ -739,7 +812,6 @@
     }
 
     boolean rejectCall(BluetoothDevice device) {
-        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
         HeadsetClientStateMachine sm = getStateMachine(device);
         if (sm == null) {
             Log.e(TAG, "Cannot allocate SM for device " + device);
@@ -758,7 +830,6 @@
     }
 
     boolean terminateCall(BluetoothDevice device, UUID uuid) {
-        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
         HeadsetClientStateMachine sm = getStateMachine(device);
         if (sm == null) {
             Log.e(TAG, "Cannot allocate SM for device " + device);
@@ -778,7 +849,6 @@
     }
 
     boolean enterPrivateMode(BluetoothDevice device, int index) {
-        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
         HeadsetClientStateMachine sm = getStateMachine(device);
         if (sm == null) {
             Log.e(TAG, "Cannot allocate SM for device " + device);
@@ -798,7 +868,6 @@
     }
 
     BluetoothHeadsetClientCall dial(BluetoothDevice device, String number) {
-        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
         HeadsetClientStateMachine sm = getStateMachine(device);
         if (sm == null) {
             Log.e(TAG, "Cannot allocate SM for device " + device);
@@ -822,7 +891,6 @@
     }
 
     public boolean sendDTMF(BluetoothDevice device, byte code) {
-        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
         HeadsetClientStateMachine sm = getStateMachine(device);
         if (sm == null) {
             Log.e(TAG, "Cannot allocate SM for device " + device);
@@ -845,7 +913,6 @@
     }
 
     public List<BluetoothHeadsetClientCall> getCurrentCalls(BluetoothDevice device) {
-        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
         HeadsetClientStateMachine sm = getStateMachine(device);
         if (sm == null) {
             Log.e(TAG, "Cannot allocate SM for device " + device);
@@ -860,7 +927,6 @@
     }
 
     public boolean explicitCallTransfer(BluetoothDevice device) {
-        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
         HeadsetClientStateMachine sm = getStateMachine(device);
         if (sm == null) {
             Log.e(TAG, "Cannot allocate SM for device " + device);
@@ -879,7 +945,6 @@
 
     /** Send vendor AT command. */
     public boolean sendVendorAtCommand(BluetoothDevice device, int vendorId, String atCommand) {
-        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
         HeadsetClientStateMachine sm = getStateMachine(device);
         if (sm == null) {
             Log.e(TAG, "Cannot allocate SM for device " + device);
@@ -898,7 +963,6 @@
     }
 
     public Bundle getCurrentAgEvents(BluetoothDevice device) {
-        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
         HeadsetClientStateMachine sm = getStateMachine(device);
         if (sm == null) {
             Log.e(TAG, "Cannot allocate SM for device " + device);
@@ -913,7 +977,6 @@
     }
 
     public Bundle getCurrentAgFeatures(BluetoothDevice device) {
-        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
         HeadsetClientStateMachine sm = getStateMachine(device);
         if (sm == null) {
             Log.e(TAG, "Cannot allocate SM for device " + device);
@@ -1007,4 +1070,19 @@
     protected AudioManager getAudioManager() {
         return mAudioManager;
     }
+
+    protected void updateBatteryLevel() {
+        int batteryLevel = mBatteryManager.getIntProperty(BatteryManager.BATTERY_PROPERTY_CAPACITY);
+        int batteryIndicatorID = 2;
+
+        synchronized (this) {
+            for (HeadsetClientStateMachine sm : mStateMachineMap.values()) {
+                if (sm != null) {
+                    sm.sendMessage(HeadsetClientStateMachine.SEND_BIEV,
+                            batteryIndicatorID,
+                            batteryLevel);
+                }
+            }
+        }
+    }
 }
diff --git a/src/com/android/bluetooth/hfpclient/HeadsetClientStateMachine.java b/src/com/android/bluetooth/hfpclient/HeadsetClientStateMachine.java
index be84681..881ef38 100644
--- a/src/com/android/bluetooth/hfpclient/HeadsetClientStateMachine.java
+++ b/src/com/android/bluetooth/hfpclient/HeadsetClientStateMachine.java
@@ -33,6 +33,10 @@
 
 package com.android.bluetooth.hfpclient;
 
+import static android.Manifest.permission.BLUETOOTH_CONNECT;
+
+import android.annotation.RequiresPermission;
+import android.app.ActivityThread;
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothDevice;
 import android.bluetooth.BluetoothHeadsetClient;
@@ -40,6 +44,7 @@
 import android.bluetooth.BluetoothProfile;
 import android.bluetooth.BluetoothUuid;
 import android.bluetooth.hfp.BluetoothHfpProtoEnums;
+import android.content.Attributable;
 import android.content.Intent;
 import android.media.AudioAttributes;
 import android.media.AudioFocusRequest;
@@ -65,6 +70,9 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.ArrayUtils;
 
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.io.StringWriter;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.HashSet;
@@ -72,6 +80,7 @@
 import java.util.LinkedList;
 import java.util.List;
 import java.util.Queue;
+import java.util.Scanner;
 import java.util.Set;
 
 public class HeadsetClientStateMachine extends StateMachine {
@@ -101,6 +110,7 @@
     public static final int EXPLICIT_CALL_TRANSFER = 18;
     public static final int DISABLE_NREC = 20;
     public static final int SEND_VENDOR_AT_COMMAND = 21;
+    public static final int SEND_BIEV = 22;
 
     // internal actions
     private static final int QUERY_CURRENT_CALLS = 50;
@@ -192,30 +202,114 @@
 
     public void dump(StringBuilder sb) {
         if (mCurrentDevice == null) return;
-        ProfileService.println(sb, "mCurrentDevice: " + mCurrentDevice.getAddress() + "("
-                + mCurrentDevice.getName() + ") " + this.toString());
-        ProfileService.println(sb, "mAudioState: " + mAudioState);
-        ProfileService.println(sb, "mAudioWbs: " + mAudioWbs);
-        ProfileService.println(sb, "mIndicatorNetworkState: " + mIndicatorNetworkState);
-        ProfileService.println(sb, "mIndicatorNetworkType: " + mIndicatorNetworkType);
-        ProfileService.println(sb, "mIndicatorNetworkSignal: " + mIndicatorNetworkSignal);
-        ProfileService.println(sb, "mIndicatorBatteryLevel: " + mIndicatorBatteryLevel);
-        ProfileService.println(sb, "mOperatorName: " + mOperatorName);
-        ProfileService.println(sb, "mSubscriberInfo: " + mSubscriberInfo);
+        ProfileService.println(sb,
+                "==== StateMachine for " + mCurrentDevice + " ====");
+        ProfileService.println(sb, "  mCurrentDevice: " + mCurrentDevice.getAddress() + "("
+                + Utils.getName(mCurrentDevice) + ") " + this.toString());
+        ProfileService.println(sb, "  mAudioState: " + mAudioState);
+        ProfileService.println(sb, "  mAudioWbs: " + mAudioWbs);
+        ProfileService.println(sb, "  mIndicatorNetworkState: " + mIndicatorNetworkState);
+        ProfileService.println(sb, "  mIndicatorNetworkType: " + mIndicatorNetworkType);
+        ProfileService.println(sb, "  mIndicatorNetworkSignal: " + mIndicatorNetworkSignal);
+        ProfileService.println(sb, "  mIndicatorBatteryLevel: " + mIndicatorBatteryLevel);
+        ProfileService.println(sb, "  mOperatorName: " + mOperatorName);
+        ProfileService.println(sb, "  mSubscriberInfo: " + mSubscriberInfo);
 
-        ProfileService.println(sb, "mCalls:");
+        ProfileService.println(sb, "  mCalls:");
         if (mCalls != null) {
             for (BluetoothHeadsetClientCall call : mCalls.values()) {
-                ProfileService.println(sb, "  " + call);
+                ProfileService.println(sb, "    " + call);
             }
         }
 
-        ProfileService.println(sb, "mCallsUpdate:");
+        ProfileService.println(sb, "  mCallsUpdate:");
         if (mCallsUpdate != null) {
             for (BluetoothHeadsetClientCall call : mCallsUpdate.values()) {
-                ProfileService.println(sb, "  " + call);
+                ProfileService.println(sb, "    " + call);
             }
         }
+
+        // Dump the state machine logs
+        StringWriter stringWriter = new StringWriter();
+        PrintWriter printWriter = new PrintWriter(stringWriter);
+        super.dump(new FileDescriptor(), printWriter, new String[]{});
+        printWriter.flush();
+        stringWriter.flush();
+        ProfileService.println(sb, "  StateMachineLog:");
+        Scanner scanner = new Scanner(stringWriter.toString());
+        while (scanner.hasNextLine()) {
+            String line = scanner.nextLine();
+            ProfileService.println(sb, "    " + line);
+        }
+    }
+
+    @Override
+    protected String getLogRecString(Message msg) {
+        StringBuilder builder = new StringBuilder();
+        builder.append(getMessageName(msg.what));
+        builder.append(": ");
+        builder.append("arg1=")
+                .append(msg.arg1)
+                .append(", arg2=")
+                .append(msg.arg2)
+                .append(", obj=")
+                .append(msg.obj);
+        return builder.toString();
+    }
+
+    private static String getMessageName(int what) {
+        switch (what) {
+            case StackEvent.STACK_EVENT:
+                return "STACK_EVENT";
+            case CONNECT:
+                return "CONNECT";
+            case DISCONNECT:
+                return "DISCONNECT";
+            case CONNECT_AUDIO:
+                return "CONNECT_AUDIO";
+            case DISCONNECT_AUDIO:
+                return "DISCONNECT_AUDIO";
+            case VOICE_RECOGNITION_START:
+                return "VOICE_RECOGNITION_START";
+            case VOICE_RECOGNITION_STOP:
+                return "VOICE_RECOGNITION_STOP";
+            case SET_MIC_VOLUME:
+                return "SET_MIC_VOLUME";
+            case SET_SPEAKER_VOLUME:
+                return "SET_SPEAKER_VOLUME";
+            case DIAL_NUMBER:
+                return "DIAL_NUMBER";
+            case ACCEPT_CALL:
+                return "ACCEPT_CALL";
+            case REJECT_CALL:
+                return "REJECT_CALL";
+            case HOLD_CALL:
+                return "HOLD_CALL";
+            case TERMINATE_CALL:
+                return "TERMINATE_CALL";
+            case ENTER_PRIVATE_MODE:
+                return "ENTER_PRIVATE_MODE";
+            case SEND_DTMF:
+                return "SEND_DTMF";
+            case EXPLICIT_CALL_TRANSFER:
+                return "EXPLICIT_CALL_TRANSFER";
+            case DISABLE_NREC:
+                return "DISABLE_NREC";
+            case SEND_VENDOR_AT_COMMAND:
+                return "SEND_VENDOR_AT_COMMAND";
+            case SEND_BIEV:
+                return "SEND_BIEV";
+            case QUERY_CURRENT_CALLS:
+                return "QUERY_CURRENT_CALLS";
+            case QUERY_OPERATOR_NAME:
+                return "QUERY_OPERATOR_NAME";
+            case SUBSCRIBER_INFO:
+                return "SUBSCRIBER_INFO";
+            case CONNECTING_TIMEOUT:
+                return "CONNECTING_TIMEOUT";
+            default:
+                return "UNKNOWN(" + what + ")";
+        }
     }
 
     private void clearPendingAction() {
@@ -262,7 +356,7 @@
         Intent intent = new Intent(BluetoothHeadsetClient.ACTION_CALL_CHANGED);
         intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
         intent.putExtra(BluetoothHeadsetClient.EXTRA_CALL, c);
-        mService.sendBroadcast(intent, ProfileService.BLUETOOTH_PERM);
+        mService.sendBroadcast(intent, BLUETOOTH_CONNECT, Utils.getTempAllowlistBroadcastOptions());
     }
 
     private boolean queryCallsStart() {
@@ -835,6 +929,8 @@
             switch (message.what) {
                 case CONNECT:
                     BluetoothDevice device = (BluetoothDevice) message.obj;
+                    Attributable.setAttributionSource(device,
+                            ActivityThread.currentAttributionSource());
                     if (!mNativeInterface.connect(getByteAddress(device))) {
                         // No state transition is involved, fire broadcast immediately
                         broadcastConnectionState(device, BluetoothProfile.STATE_DISCONNECTED,
@@ -1065,6 +1161,7 @@
             logD("Enter Connected: " + getCurrentMessage().what);
             mAudioWbs = false;
             mCommandedSpeakerVolume = -1;
+
             if (mPrevState == mConnecting) {
                 broadcastConnectionState(mCurrentDevice, BluetoothProfile.STATE_CONNECTED,
                         BluetoothProfile.STATE_CONNECTING);
@@ -1075,6 +1172,7 @@
                 Log.e(TAG, "Connected: Illegal state transition from " + prevStateName
                         + " to Connecting, mCurrentDevice=" + mCurrentDevice);
             }
+            mService.updateBatteryLevel();
         }
 
         @Override
@@ -1090,6 +1188,8 @@
             switch (message.what) {
                 case CONNECT:
                     BluetoothDevice device = (BluetoothDevice) message.obj;
+                    Attributable.setAttributionSource(device,
+                            ActivityThread.currentAttributionSource());
                     if (mCurrentDevice.equals(device)) {
                         // already connected to this device, do nothing
                         break;
@@ -1098,6 +1198,8 @@
                     break;
                 case DISCONNECT:
                     BluetoothDevice dev = (BluetoothDevice) message.obj;
+                    Attributable.setAttributionSource(dev,
+                            ActivityThread.currentAttributionSource());
                     if (!mCurrentDevice.equals(dev)) {
                         break;
                     }
@@ -1153,6 +1255,20 @@
                     break;
                 }
 
+                case SEND_BIEV: {
+                    if ((mPeerFeatures & HeadsetClientHalConstants.PEER_FEAT_HF_IND)
+                            == HeadsetClientHalConstants.PEER_FEAT_HF_IND) {
+                        int indicatorID = message.arg1;
+                        int value = message.arg2;
+                        mNativeInterface.sendATCmd(getByteAddress(mCurrentDevice),
+                                HeadsetClientHalConstants.HANDSFREECLIENT_AT_CMD_BIEV,
+                                indicatorID,
+                                value,
+                                null);
+                    }
+                    break;
+                }
+
                 // Called only for Mute/Un-mute - Mic volume change is not allowed.
                 case SET_MIC_VOLUME:
                     break;
@@ -1181,7 +1297,7 @@
                         sendMessage(QUERY_CURRENT_CALLS);
                     } else {
                         Log.e(TAG,
-                                "ERROR: Cannot dial with a given number:" + (String) message.obj);
+                                "ERROR: Cannot dial with a given number:" + c.toString(true));
                         // Set the call to terminated remove.
                         c.setState(BluetoothHeadsetClientCall.CALL_STATE_TERMINATED);
                         sendCallChangedIntent(c);
@@ -1262,7 +1378,8 @@
                             }
 
                             intent.putExtra(BluetoothDevice.EXTRA_DEVICE, event.device);
-                            mService.sendBroadcast(intent, ProfileService.BLUETOOTH_PERM);
+                            mService.sendBroadcast(intent, BLUETOOTH_CONNECT,
+                                    Utils.getTempAllowlistBroadcastOptions());
 
                             if (mIndicatorNetworkState
                                     == HeadsetClientHalConstants.NETWORK_STATE_AVAILABLE) {
@@ -1281,7 +1398,8 @@
                             intent.putExtra(BluetoothHeadsetClient.EXTRA_NETWORK_ROAMING,
                                     event.valueInt);
                             intent.putExtra(BluetoothDevice.EXTRA_DEVICE, event.device);
-                            mService.sendBroadcast(intent, ProfileService.BLUETOOTH_PERM);
+                            mService.sendBroadcast(intent, BLUETOOTH_CONNECT,
+                                    Utils.getTempAllowlistBroadcastOptions());
                             break;
                         case StackEvent.EVENT_TYPE_NETWORK_SIGNAL:
                             mIndicatorNetworkSignal = event.valueInt;
@@ -1290,7 +1408,8 @@
                             intent.putExtra(BluetoothHeadsetClient.EXTRA_NETWORK_SIGNAL_STRENGTH,
                                     event.valueInt);
                             intent.putExtra(BluetoothDevice.EXTRA_DEVICE, event.device);
-                            mService.sendBroadcast(intent, ProfileService.BLUETOOTH_PERM);
+                            mService.sendBroadcast(intent, BLUETOOTH_CONNECT,
+                                    Utils.getTempAllowlistBroadcastOptions());
                             break;
                         case StackEvent.EVENT_TYPE_BATTERY_LEVEL:
                             mIndicatorBatteryLevel = event.valueInt;
@@ -1299,7 +1418,8 @@
                             intent.putExtra(BluetoothHeadsetClient.EXTRA_BATTERY_LEVEL,
                                     event.valueInt);
                             intent.putExtra(BluetoothDevice.EXTRA_DEVICE, event.device);
-                            mService.sendBroadcast(intent, ProfileService.BLUETOOTH_PERM);
+                            mService.sendBroadcast(intent, BLUETOOTH_CONNECT,
+                                    Utils.getTempAllowlistBroadcastOptions());
                             break;
                         case StackEvent.EVENT_TYPE_OPERATOR_NAME:
                             mOperatorName = event.valueString;
@@ -1308,7 +1428,8 @@
                             intent.putExtra(BluetoothHeadsetClient.EXTRA_OPERATOR_NAME,
                                     event.valueString);
                             intent.putExtra(BluetoothDevice.EXTRA_DEVICE, event.device);
-                            mService.sendBroadcast(intent, ProfileService.BLUETOOTH_PERM);
+                            mService.sendBroadcast(intent, BLUETOOTH_CONNECT,
+                                    Utils.getTempAllowlistBroadcastOptions());
                             break;
                         case StackEvent.EVENT_TYPE_VR_STATE_CHANGED:
                             int oldState = mVoiceRecognitionActive;
@@ -1388,7 +1509,8 @@
                             intent.putExtra(BluetoothHeadsetClient.EXTRA_SUBSCRIBER_INFO,
                                     mSubscriberInfo);
                             intent.putExtra(BluetoothDevice.EXTRA_DEVICE, event.device);
-                            mService.sendBroadcast(intent, ProfileService.BLUETOOTH_PERM);
+                            mService.sendBroadcast(intent, BLUETOOTH_CONNECT,
+                                    Utils.getTempAllowlistBroadcastOptions());
                             break;
                         case StackEvent.EVENT_TYPE_IN_BAND_RINGTONE:
                             intent = new Intent(BluetoothHeadsetClient.ACTION_AG_EVENT);
@@ -1396,7 +1518,8 @@
                             intent.putExtra(BluetoothHeadsetClient.EXTRA_IN_BAND_RING,
                                     event.valueInt);
                             intent.putExtra(BluetoothDevice.EXTRA_DEVICE, event.device);
-                            mService.sendBroadcast(intent, ProfileService.BLUETOOTH_PERM);
+                            mService.sendBroadcast(intent, BLUETOOTH_CONNECT,
+                                    Utils.getTempAllowlistBroadcastOptions());
                             logD(event.device.toString() + "onInBandRing" + event.valueInt);
                             break;
                         case StackEvent.EVENT_TYPE_RING_INDICATION:
@@ -1430,7 +1553,8 @@
             Intent intent = new Intent(BluetoothHeadsetClient.ACTION_AG_EVENT);
             intent.putExtra(BluetoothHeadsetClient.EXTRA_VOICE_RECOGNITION, newState);
             intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
-            mService.sendBroadcast(intent, ProfileService.BLUETOOTH_PERM);
+            mService.sendBroadcast(intent, BLUETOOTH_CONNECT,
+                    Utils.getTempAllowlistBroadcastOptions());
         }
 
         // in Connected state
@@ -1551,6 +1675,8 @@
             switch (message.what) {
                 case DISCONNECT:
                     BluetoothDevice device = (BluetoothDevice) message.obj;
+                    Attributable.setAttributionSource(device,
+                            ActivityThread.currentAttributionSource());
                     if (!mCurrentDevice.equals(device)) {
                         break;
                     }
@@ -1656,11 +1782,7 @@
      * @hide
      */
     public synchronized int getConnectionState(BluetoothDevice device) {
-        if (mCurrentDevice == null) {
-            return BluetoothProfile.STATE_DISCONNECTED;
-        }
-
-        if (!mCurrentDevice.equals(device)) {
+        if (device == null || !device.equals(mCurrentDevice)) {
             return BluetoothProfile.STATE_DISCONNECTED;
         }
 
@@ -1691,7 +1813,8 @@
             intent.putExtra(BluetoothHeadsetClient.EXTRA_AUDIO_WBS, mAudioWbs);
         }
         intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
-        mService.sendBroadcast(intent, ProfileService.BLUETOOTH_PERM);
+        mService.sendBroadcast(intent, BLUETOOTH_CONNECT,
+                Utils.getTempAllowlistBroadcastOptions());
         logD("Audio state " + device + ": " + prevState + "->" + newState);
     }
 
@@ -1752,7 +1875,8 @@
                 intent.putExtra(BluetoothHeadsetClient.EXTRA_AG_FEATURE_MERGE_AND_DETACH, true);
             }
         }
-        mService.sendBroadcast(intent, ProfileService.BLUETOOTH_PERM);
+        mService.sendBroadcast(intent, BLUETOOTH_CONNECT,
+                Utils.getTempAllowlistBroadcastOptions());
     }
 
     boolean isConnected() {
diff --git a/src/com/android/bluetooth/hfpclient/VendorCommandResponseProcessor.java b/src/com/android/bluetooth/hfpclient/VendorCommandResponseProcessor.java
index 7e81d71..74ecadf 100644
--- a/src/com/android/bluetooth/hfpclient/VendorCommandResponseProcessor.java
+++ b/src/com/android/bluetooth/hfpclient/VendorCommandResponseProcessor.java
@@ -20,6 +20,8 @@
  */
 package com.android.bluetooth.hfpclient;
 
+import static android.Manifest.permission.BLUETOOTH_CONNECT;
+
 import android.bluetooth.BluetoothAssignedNumbers;
 import android.bluetooth.BluetoothDevice;
 import android.bluetooth.BluetoothHeadsetClient;
@@ -162,7 +164,7 @@
         intent.putExtra(BluetoothHeadsetClient.EXTRA_VENDOR_EVENT_CODE, vendorEventCode);
         intent.putExtra(BluetoothHeadsetClient.EXTRA_VENDOR_EVENT_FULL_ARGS, vendorResponse);
         intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
-        mService.sendBroadcast(intent, HeadsetClientService.BLUETOOTH_PERM);
+        mService.sendBroadcast(intent, BLUETOOTH_CONNECT, Utils.getTempAllowlistBroadcastOptions());
     }
 
     private void logD(String msg) {
diff --git a/src/com/android/bluetooth/hfpclient/connserv/HfpClientConnection.java b/src/com/android/bluetooth/hfpclient/connserv/HfpClientConnection.java
index d61b827..f8bc92e 100644
--- a/src/com/android/bluetooth/hfpclient/connserv/HfpClientConnection.java
+++ b/src/com/android/bluetooth/hfpclient/connserv/HfpClientConnection.java
@@ -20,6 +20,7 @@
 import android.bluetooth.BluetoothHeadsetClientCall;
 import android.content.Context;
 import android.net.Uri;
+import android.os.Bundle;
 import android.telecom.Connection;
 import android.telecom.DisconnectCause;
 import android.telecom.PhoneAccount;
@@ -32,6 +33,10 @@
     private static final String TAG = "HfpClientConnection";
     private static final boolean DBG = false;
 
+    private static final String EVENT_SCO_CONNECT = "com.android.bluetooth.hfpclient.SCO_CONNECT";
+    private static final String EVENT_SCO_DISCONNECT =
+             "com.android.bluetooth.hfpclient.SCO_DISCONNECT";
+
     private final Context mContext;
     private final BluetoothDevice mDevice;
     private BluetoothHeadsetClient mHeadsetProfile;
@@ -278,6 +283,21 @@
     }
 
     @Override
+    public void onCallEvent(String event, Bundle extras) {
+        if (DBG) {
+            Log.d(TAG, "onCallEvent(" + event + ", " + extras + ")");
+        }
+        switch (event) {
+            case EVENT_SCO_CONNECT:
+                mHeadsetProfile.connectAudio(mDevice);
+                break;
+            case EVENT_SCO_DISCONNECT:
+                mHeadsetProfile.disconnectAudio(mDevice);
+                break;
+        }
+    }
+
+    @Override
     public boolean equals(Object o) {
         if (!(o instanceof HfpClientConnection)) {
             return false;
diff --git a/src/com/android/bluetooth/hfpclient/connserv/HfpClientConnectionService.java b/src/com/android/bluetooth/hfpclient/connserv/HfpClientConnectionService.java
index c5ef578..978fe5a 100644
--- a/src/com/android/bluetooth/hfpclient/connserv/HfpClientConnectionService.java
+++ b/src/com/android/bluetooth/hfpclient/connserv/HfpClientConnectionService.java
@@ -110,6 +110,18 @@
                 // the calls should
                 // be added (see ACTION_CONNECTION_STATE_CHANGED intent above).
                 block.handleCall(call);
+            } else if (BluetoothHeadsetClient.ACTION_AUDIO_STATE_CHANGED.equals(action)) {
+                BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
+                int oldState = intent.getIntExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE,
+                        BluetoothHeadsetClient.STATE_AUDIO_DISCONNECTED);
+                int newState = intent.getIntExtra(BluetoothProfile.EXTRA_STATE,
+                        BluetoothHeadsetClient.STATE_AUDIO_DISCONNECTED);
+                HfpClientDeviceBlock block = findBlockForDevice(device);
+                if (block == null) {
+                    Log.w(TAG, "Device audio state changed but no block for device");
+                    return;
+                }
+                block.onAudioStateChange(newState, oldState);
             }
         }
     };
@@ -172,6 +184,7 @@
         } else {
             IntentFilter filter = new IntentFilter();
             filter.addAction(BluetoothHeadsetClient.ACTION_CONNECTION_STATE_CHANGED);
+            filter.addAction(BluetoothHeadsetClient.ACTION_AUDIO_STATE_CHANGED);
             filter.addAction(BluetoothHeadsetClient.ACTION_CALL_CHANGED);
             registerReceiver(mBroadcastReceiver, filter);
             return START_STICKY;
diff --git a/src/com/android/bluetooth/hfpclient/connserv/HfpClientDeviceBlock.java b/src/com/android/bluetooth/hfpclient/connserv/HfpClientDeviceBlock.java
index aba50c0..3e5e926 100644
--- a/src/com/android/bluetooth/hfpclient/connserv/HfpClientDeviceBlock.java
+++ b/src/com/android/bluetooth/hfpclient/connserv/HfpClientDeviceBlock.java
@@ -27,19 +27,23 @@
 import android.telecom.TelecomManager;
 import android.util.Log;
 
+import com.android.bluetooth.hfpclient.HeadsetClientService;
+
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.UUID;
 
-// Helper class that manages the call handling for one device. HfpClientConnectionService holdes a
+// Helper class that manages the call handling for one device. HfpClientConnectionService holds a
 // list of such blocks and routes traffic from the UI.
 //
 // Lifecycle of a Device Block is managed entirely by the Service which creates it. In essence it
 // has only the active state otherwise the block should be GCed.
 public class HfpClientDeviceBlock {
-    private final String mTAG;
+    private static final String KEY_SCO_STATE = "com.android.bluetooth.hfpclient.SCO_STATE";
     private static final boolean DBG = false;
+
+    private final String mTAG;
     private final Context mContext;
     private final BluetoothDevice mDevice;
     private final PhoneAccount mPhoneAccount;
@@ -47,8 +51,8 @@
     private final TelecomManager mTelecomManager;
     private final HfpClientConnectionService mConnServ;
     private HfpClientConference mConference;
-
     private BluetoothHeadsetClient mHeadsetProfile;
+    private Bundle mScoState;
 
     HfpClientDeviceBlock(HfpClientConnectionService connServ, BluetoothDevice device,
             BluetoothHeadsetClient headsetProfile) {
@@ -64,6 +68,10 @@
         mTelecomManager.enablePhoneAccount(mPhoneAccount.getAccountHandle(), true);
         mTelecomManager.setUserSelectedOutgoingPhoneAccount(mPhoneAccount.getAccountHandle());
         mHeadsetProfile = headsetProfile;
+        mScoState = getScoStateFromDevice(device);
+        if (DBG) {
+            Log.d(mTAG, "SCO state = " + mScoState);
+        }
 
         // Read the current calls and add them to telecom if already present
         if (mHeadsetProfile != null) {
@@ -106,6 +114,20 @@
         return connection;
     }
 
+    synchronized void onAudioStateChange(int newState, int oldState) {
+        if (DBG) {
+            Log.d(mTAG, "Call audio state changed " + oldState + " -> " + newState);
+        }
+        mScoState.putInt(KEY_SCO_STATE, newState);
+
+        for (HfpClientConnection connection : mConnections.values()) {
+            connection.setExtras(mScoState);
+        }
+        if (mConference != null) {
+            mConference.setExtras(mScoState);
+        }
+    }
+
     synchronized HfpClientConnection onCreateUnknownConnection(BluetoothHeadsetClientCall call) {
         Uri number = Uri.fromParts(PhoneAccount.SCHEME_TEL, call.getNumber(), null);
         HfpClientConnection connection = mConnections.get(call.getUUID());
@@ -123,6 +145,7 @@
         if (mConference == null) {
             mConference = new HfpClientConference(mPhoneAccount.getAccountHandle(), mDevice,
                     mHeadsetProfile);
+            mConference.setExtras(mScoState);
         }
 
         if (connection1.getConference() == null) {
@@ -254,6 +277,10 @@
         } else {
             connection = new HfpClientConnection(mConnServ, mDevice, mHeadsetProfile, number);
         }
+        connection.setExtras(mScoState);
+        if (DBG) {
+            Log.d(mTAG, "Connection extras = " + connection.getExtras().toString());
+        }
 
         if (connection.getState() != Connection.STATE_DISCONNECTED) {
             mConnections.put(connection.getUUID(), connection);
@@ -292,6 +319,7 @@
                 if (mConference == null) {
                     mConference = new HfpClientConference(mPhoneAccount.getAccountHandle(), mDevice,
                             mHeadsetProfile);
+                    mConference.setExtras(mScoState);
                 }
                 if (mConference.addConnection(otherConn)) {
                     if (DBG) {
@@ -321,4 +349,16 @@
         }
     }
 
+    private Bundle getScoStateFromDevice(BluetoothDevice device) {
+        Bundle bundle = new Bundle();
+
+        HeadsetClientService headsetClientService = HeadsetClientService.getHeadsetClientService();
+        if (headsetClientService == null) {
+            return bundle;
+        }
+
+        bundle.putInt(KEY_SCO_STATE, headsetClientService.getAudioState(device));
+
+        return bundle;
+    }
 }
diff --git a/src/com/android/bluetooth/hid/HidDeviceService.java b/src/com/android/bluetooth/hid/HidDeviceService.java
index 6086175..b99fbc0 100644
--- a/src/com/android/bluetooth/hid/HidDeviceService.java
+++ b/src/com/android/bluetooth/hid/HidDeviceService.java
@@ -16,7 +16,11 @@
 
 package com.android.bluetooth.hid;
 
+import static android.Manifest.permission.BLUETOOTH_CONNECT;
+
+import android.annotation.RequiresPermission;
 import android.app.ActivityManager;
+import android.app.ActivityThread;
 import android.bluetooth.BluetoothDevice;
 import android.bluetooth.BluetoothHidDevice;
 import android.bluetooth.BluetoothHidDeviceAppQosSettings;
@@ -24,6 +28,8 @@
 import android.bluetooth.BluetoothProfile;
 import android.bluetooth.IBluetoothHidDevice;
 import android.bluetooth.IBluetoothHidDeviceCallback;
+import android.content.Attributable;
+import android.content.AttributionSource;
 import android.content.Context;
 import android.content.Intent;
 import android.os.Binder;
@@ -39,6 +45,7 @@
 import com.android.bluetooth.btservice.AdapterService;
 import com.android.bluetooth.btservice.MetricsLogger;
 import com.android.bluetooth.btservice.ProfileService;
+import com.android.bluetooth.btservice.storage.DatabaseManager;
 import com.android.internal.annotations.VisibleForTesting;
 
 import java.nio.ByteBuffer;
@@ -46,6 +53,7 @@
 import java.util.Arrays;
 import java.util.List;
 import java.util.NoSuchElementException;
+import java.util.Objects;
 
 /** @hide */
 public class HidDeviceService extends ProfileService {
@@ -66,6 +74,7 @@
 
     private static HidDeviceService sHidDeviceService;
 
+    private DatabaseManager mDatabaseManager;
     private HidDeviceNativeInterface mHidDeviceNativeInterface;
 
     private boolean mNativeAvailable = false;
@@ -88,6 +97,8 @@
             switch (msg.what) {
                 case MESSAGE_APPLICATION_STATE_CHANGED: {
                     BluetoothDevice device = msg.obj != null ? (BluetoothDevice) msg.obj : null;
+                    Attributable.setAttributionSource(device,
+                            ActivityThread.currentAttributionSource());
                     boolean success = (msg.arg1 != 0);
 
                     if (success) {
@@ -142,6 +153,8 @@
 
                 case MESSAGE_CONNECT_STATE_CHANGED: {
                     BluetoothDevice device = (BluetoothDevice) msg.obj;
+                    Attributable.setAttributionSource(device,
+                            ActivityThread.currentAttributionSource());
                     int halState = msg.arg1;
                     int state = convertHalState(halState);
 
@@ -292,28 +305,25 @@
             mService = null;
         }
 
-        private HidDeviceService getService() {
-            if (!Utils.checkCaller()) {
-                Log.w(TAG, "HidDevice call not allowed for non-active user");
+        @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
+        private HidDeviceService getService(AttributionSource source) {
+            if (!Utils.checkCallerIsSystemOrActiveUser(TAG)
+                    || !Utils.checkServiceAvailable(mService, TAG)
+                    || !Utils.checkConnectPermissionForDataDelivery(mService, source, TAG)) {
                 return null;
             }
-
-            if (mService != null && mService.isAvailable()) {
-                return mService;
-            }
-
-            return null;
+            return mService;
         }
 
         @Override
         public boolean registerApp(BluetoothHidDeviceAppSdpSettings sdp,
                 BluetoothHidDeviceAppQosSettings inQos, BluetoothHidDeviceAppQosSettings outQos,
-                IBluetoothHidDeviceCallback callback) {
+                IBluetoothHidDeviceCallback callback, AttributionSource source) {
             if (DBG) {
                 Log.d(TAG, "registerApp()");
             }
 
-            HidDeviceService service = getService();
+            HidDeviceService service = getService(source);
             if (service == null) {
                 return false;
             }
@@ -322,12 +332,12 @@
         }
 
         @Override
-        public boolean unregisterApp() {
+        public boolean unregisterApp(AttributionSource source) {
             if (DBG) {
                 Log.d(TAG, "unregisterApp()");
             }
 
-            HidDeviceService service = getService();
+            HidDeviceService service = getService(source);
             if (service == null) {
                 return false;
             }
@@ -336,12 +346,14 @@
         }
 
         @Override
-        public boolean sendReport(BluetoothDevice device, int id, byte[] data) {
+        public boolean sendReport(BluetoothDevice device, int id, byte[] data,
+                AttributionSource source) {
             if (DBG) {
                 Log.d(TAG, "sendReport(): device=" + device + "  id=" + id);
             }
 
-            HidDeviceService service = getService();
+            Attributable.setAttributionSource(device, source);
+            HidDeviceService service = getService(source);
             if (service == null) {
                 return false;
             }
@@ -350,12 +362,14 @@
         }
 
         @Override
-        public boolean replyReport(BluetoothDevice device, byte type, byte id, byte[] data) {
+        public boolean replyReport(BluetoothDevice device, byte type, byte id, byte[] data,
+                AttributionSource source) {
             if (DBG) {
                 Log.d(TAG, "replyReport(): device=" + device + " type=" + type + " id=" + id);
             }
 
-            HidDeviceService service = getService();
+            Attributable.setAttributionSource(device, source);
+            HidDeviceService service = getService(source);
             if (service == null) {
                 return false;
             }
@@ -364,12 +378,13 @@
         }
 
         @Override
-        public boolean unplug(BluetoothDevice device) {
+        public boolean unplug(BluetoothDevice device, AttributionSource source) {
             if (DBG) {
                 Log.d(TAG, "unplug(): device=" + device);
             }
 
-            HidDeviceService service = getService();
+            Attributable.setAttributionSource(device, source);
+            HidDeviceService service = getService(source);
             if (service == null) {
                 return false;
             }
@@ -378,12 +393,13 @@
         }
 
         @Override
-        public boolean connect(BluetoothDevice device) {
+        public boolean connect(BluetoothDevice device, AttributionSource source) {
             if (DBG) {
                 Log.d(TAG, "connect(): device=" + device);
             }
 
-            HidDeviceService service = getService();
+            Attributable.setAttributionSource(device, source);
+            HidDeviceService service = getService(source);
             if (service == null) {
                 return false;
             }
@@ -392,12 +408,13 @@
         }
 
         @Override
-        public boolean disconnect(BluetoothDevice device) {
+        public boolean disconnect(BluetoothDevice device, AttributionSource source) {
             if (DBG) {
                 Log.d(TAG, "disconnect(): device=" + device);
             }
 
-            HidDeviceService service = getService();
+            Attributable.setAttributionSource(device, source);
+            HidDeviceService service = getService(source);
             if (service == null) {
                 return false;
             }
@@ -406,13 +423,15 @@
         }
 
         @Override
-        public boolean setConnectionPolicy(BluetoothDevice device, int connectionPolicy) {
+        public boolean setConnectionPolicy(BluetoothDevice device, int connectionPolicy,
+                AttributionSource source) {
             if (DBG) {
                 Log.d(TAG, "setConnectionPolicy(): device=" + device + " connectionPolicy="
                         + connectionPolicy);
             }
 
-            HidDeviceService service = getService();
+            Attributable.setAttributionSource(device, source);
+            HidDeviceService service = getService(source);
             if (service == null) {
                 return false;
             }
@@ -421,12 +440,13 @@
         }
 
         @Override
-        public boolean reportError(BluetoothDevice device, byte error) {
+        public boolean reportError(BluetoothDevice device, byte error, AttributionSource source) {
             if (DBG) {
                 Log.d(TAG, "reportError(): device=" + device + " error=" + error);
             }
 
-            HidDeviceService service = getService();
+            Attributable.setAttributionSource(device, source);
+            HidDeviceService service = getService(source);
             if (service == null) {
                 return false;
             }
@@ -435,12 +455,13 @@
         }
 
         @Override
-        public int getConnectionState(BluetoothDevice device) {
+        public int getConnectionState(BluetoothDevice device, AttributionSource source) {
             if (DBG) {
                 Log.d(TAG, "getConnectionState(): device=" + device);
             }
 
-            HidDeviceService service = getService();
+            Attributable.setAttributionSource(device, source);
+            HidDeviceService service = getService(source);
             if (service == null) {
                 return BluetoothHidDevice.STATE_DISCONNECTED;
             }
@@ -449,22 +470,25 @@
         }
 
         @Override
-        public List<BluetoothDevice> getConnectedDevices() {
+        public List<BluetoothDevice> getConnectedDevices(AttributionSource source) {
             if (DBG) {
                 Log.d(TAG, "getConnectedDevices()");
             }
 
-            return getDevicesMatchingConnectionStates(new int[]{BluetoothProfile.STATE_CONNECTED});
+            return getDevicesMatchingConnectionStates(new int[] {
+                    BluetoothProfile.STATE_CONNECTED
+            }, source);
         }
 
         @Override
-        public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
+        public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states,
+                AttributionSource source) {
             if (DBG) {
                 Log.d(TAG,
                         "getDevicesMatchingConnectionStates(): states=" + Arrays.toString(states));
             }
 
-            HidDeviceService service = getService();
+            HidDeviceService service = getService(source);
             if (service == null) {
                 return new ArrayList<BluetoothDevice>(0);
             }
@@ -473,8 +497,8 @@
         }
 
         @Override
-        public String getUserAppName() {
-            HidDeviceService service = getService();
+        public String getUserAppName(AttributionSource source) {
+            HidDeviceService service = getService(source);
             if (service == null) {
                 return "";
             }
@@ -507,7 +531,6 @@
     synchronized boolean registerApp(BluetoothHidDeviceAppSdpSettings sdp,
             BluetoothHidDeviceAppQosSettings inQos, BluetoothHidDeviceAppQosSettings outQos,
             IBluetoothHidDeviceCallback callback) {
-        enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH ADMIN permission");
         if (mUserUid != 0) {
             Log.w(TAG, "registerApp(): failed because another app is registered");
             return false;
@@ -554,7 +577,6 @@
     }
 
     synchronized boolean unregisterApp() {
-        enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH ADMIN permission");
         if (DBG) {
             Log.d(TAG, "unregisterApp()");
         }
@@ -579,7 +601,6 @@
     }
 
     synchronized boolean sendReport(BluetoothDevice device, int id, byte[] data) {
-        enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH ADMIN permission");
         if (DBG) {
             Log.d(TAG, "sendReport(): device=" + device + " id=" + id);
         }
@@ -589,7 +610,6 @@
     }
 
     synchronized boolean replyReport(BluetoothDevice device, byte type, byte id, byte[] data) {
-        enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH ADMIN permission");
         if (DBG) {
             Log.d(TAG, "replyReport(): device=" + device + " type=" + type + " id=" + id);
         }
@@ -599,7 +619,6 @@
     }
 
     synchronized boolean unplug(BluetoothDevice device) {
-        enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH ADMIN permission");
         if (DBG) {
             Log.d(TAG, "unplug(): device=" + device);
         }
@@ -615,7 +634,6 @@
      * @return true if the connection is successful, false otherwise
      */
     public synchronized boolean connect(BluetoothDevice device) {
-        enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH ADMIN permission");
         if (DBG) {
             Log.d(TAG, "connect(): device=" + device);
         }
@@ -630,7 +648,6 @@
      * @return true if the disconnection is successful, false otherwise
      */
     public synchronized boolean disconnect(BluetoothDevice device) {
-        enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH ADMIN permission");
         if (DBG) {
             Log.d(TAG, "disconnect(): device=" + device);
         }
@@ -658,19 +675,22 @@
      * @param connectionPolicy determines whether hid device should be connected or disconnected
      * @return true if hid device is connected or disconnected, false otherwise
      */
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED)
     public boolean setConnectionPolicy(BluetoothDevice device, int connectionPolicy) {
         enforceCallingOrSelfPermission(
                 BLUETOOTH_PRIVILEGED, "Need BLUETOOTH_PRIVILEGED permission");
         if (DBG) {
             Log.d(TAG, "Saved connectionPolicy " + device + " = " + connectionPolicy);
         }
-        AdapterService.getAdapterService().getDatabase()
-                .setProfileConnectionPolicy(device, BluetoothProfile.HID_DEVICE, connectionPolicy);
+
+        if (!mDatabaseManager.setProfileConnectionPolicy(device, BluetoothProfile.HID_DEVICE,
+                  connectionPolicy)) {
+            return false;
+        }
         if (connectionPolicy == BluetoothProfile.CONNECTION_POLICY_FORBIDDEN) {
             disconnect(device);
-            return true;
         }
-        return false;
+        return true;
     }
 
     /**
@@ -685,18 +705,18 @@
      * @return connection policy of the device
      * @hide
      */
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED)
     public int getConnectionPolicy(BluetoothDevice device) {
         if (device == null) {
             throw new IllegalArgumentException("Null device");
         }
         enforceCallingOrSelfPermission(
                 BLUETOOTH_PRIVILEGED, "Need BLUETOOTH_PRIVILEGED permission");
-        return AdapterService.getAdapterService().getDatabase()
+        return mDatabaseManager
                 .getProfileConnectionPolicy(device, BluetoothProfile.HID_DEVICE);
     }
 
     synchronized boolean reportError(BluetoothDevice device, byte error) {
-        enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH ADMIN permission");
         if (DBG) {
             Log.d(TAG, "reportError(): device=" + device + " error=" + error);
         }
@@ -706,7 +726,6 @@
     }
 
     synchronized String getUserAppName() {
-        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
         if (mUserUid < Process.FIRST_APPLICATION_UID) {
             return "";
         }
@@ -720,6 +739,9 @@
             Log.d(TAG, "start()");
         }
 
+        mDatabaseManager = Objects.requireNonNull(AdapterService.getAdapterService().getDatabase(),
+                "DatabaseManager cannot be null when HidDeviceService starts");
+
         mHandler = new HidDeviceServiceHandler();
         mHidDeviceNativeInterface = HidDeviceNativeInterface.getInstance();
         mHidDeviceNativeInterface.init();
@@ -790,7 +812,6 @@
      * {@link BluetoothProfile#STATE_DISCONNECTING}
      */
     public int getConnectionState(BluetoothDevice device) {
-        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
         if (mHidDevice != null && mHidDevice.equals(device)) {
             return mHidDeviceState;
         }
@@ -798,7 +819,6 @@
     }
 
     List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
-        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
         List<BluetoothDevice> inputDevices = new ArrayList<BluetoothDevice>();
 
         if (mHidDevice != null) {
@@ -922,7 +942,7 @@
         intent.putExtra(BluetoothProfile.EXTRA_STATE, newState);
         intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
         intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
-        sendBroadcast(intent, BLUETOOTH_PERM);
+        sendBroadcast(intent, BLUETOOTH_CONNECT, Utils.getTempAllowlistBroadcastOptions());
     }
 
     private static int convertHalState(int halState) {
diff --git a/src/com/android/bluetooth/hid/HidHostService.java b/src/com/android/bluetooth/hid/HidHostService.java
index 10d414d..f1e9746 100644
--- a/src/com/android/bluetooth/hid/HidHostService.java
+++ b/src/com/android/bluetooth/hid/HidHostService.java
@@ -16,14 +16,18 @@
 
 package com.android.bluetooth.hid;
 
-import static com.android.bluetooth.Utils.enforceBluetoothAdminPermission;
-import static com.android.bluetooth.Utils.enforceBluetoothPermission;
+import static android.Manifest.permission.BLUETOOTH_CONNECT;
+
 import static com.android.bluetooth.Utils.enforceBluetoothPrivilegedPermission;
 
+import android.annotation.RequiresPermission;
+import android.app.ActivityThread;
 import android.bluetooth.BluetoothDevice;
 import android.bluetooth.BluetoothHidHost;
 import android.bluetooth.BluetoothProfile;
 import android.bluetooth.IBluetoothHidHost;
+import android.content.Attributable;
+import android.content.AttributionSource;
 import android.content.Intent;
 import android.os.Bundle;
 import android.os.Handler;
@@ -38,12 +42,14 @@
 import com.android.bluetooth.btservice.AdapterService;
 import com.android.bluetooth.btservice.MetricsLogger;
 import com.android.bluetooth.btservice.ProfileService;
+import com.android.bluetooth.btservice.storage.DatabaseManager;
 
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 
 /**
  * Provides Bluetooth Hid Host profile, as a service in
@@ -59,6 +65,8 @@
     private static HidHostService sHidHostService;
     private BluetoothDevice mTargetDevice = null;
 
+    private DatabaseManager mDatabaseManager;
+
     private static final int MESSAGE_CONNECT = 1;
     private static final int MESSAGE_DISCONNECT = 2;
     private static final int MESSAGE_CONNECT_STATE_CHANGED = 3;
@@ -69,7 +77,6 @@
     private static final int MESSAGE_GET_REPORT = 8;
     private static final int MESSAGE_ON_GET_REPORT = 9;
     private static final int MESSAGE_SET_REPORT = 10;
-    private static final int MESSAGE_SEND_DATA = 11;
     private static final int MESSAGE_ON_VIRTUAL_UNPLUG = 12;
     private static final int MESSAGE_ON_HANDSHAKE = 13;
     private static final int MESSAGE_GET_IDLE_TIME = 14;
@@ -87,6 +94,9 @@
 
     @Override
     protected boolean start() {
+        mDatabaseManager = Objects.requireNonNull(AdapterService.getAdapterService().getDatabase(),
+                "DatabaseManager cannot be null when HidHostService starts");
+
         mInputDevices = Collections.synchronizedMap(new HashMap<BluetoothDevice, Integer>());
         initializeNative();
         mNativeAvailable = true;
@@ -151,6 +161,8 @@
             switch (msg.what) {
                 case MESSAGE_CONNECT: {
                     BluetoothDevice device = (BluetoothDevice) msg.obj;
+                    Attributable.setAttributionSource(device,
+                            ActivityThread.currentAttributionSource());
                     if (!connectHidNative(Utils.getByteAddress(device))) {
                         broadcastConnectionState(device, BluetoothProfile.STATE_DISCONNECTING);
                         broadcastConnectionState(device, BluetoothProfile.STATE_DISCONNECTED);
@@ -161,6 +173,8 @@
                 break;
                 case MESSAGE_DISCONNECT: {
                     BluetoothDevice device = (BluetoothDevice) msg.obj;
+                    Attributable.setAttributionSource(device,
+                            ActivityThread.currentAttributionSource());
                     if (!disconnectHidNative(Utils.getByteAddress(device))) {
                         broadcastConnectionState(device, BluetoothProfile.STATE_DISCONNECTING);
                         broadcastConnectionState(device, BluetoothProfile.STATE_DISCONNECTED);
@@ -169,7 +183,9 @@
                 }
                 break;
                 case MESSAGE_CONNECT_STATE_CHANGED: {
-                    BluetoothDevice device = getDevice((byte[]) msg.obj);
+                    BluetoothDevice device = getAnonymousDevice((byte[]) msg.obj);
+                    Attributable.setAttributionSource(device,
+                            ActivityThread.currentAttributionSource());
                     int halState = msg.arg1;
                     Integer prevStateInteger = mInputDevices.get(device);
                     int prevState =
@@ -201,6 +217,8 @@
                 break;
                 case MESSAGE_GET_PROTOCOL_MODE: {
                     BluetoothDevice device = (BluetoothDevice) msg.obj;
+                    Attributable.setAttributionSource(device,
+                            ActivityThread.currentAttributionSource());
                     if (!getProtocolModeNative(Utils.getByteAddress(device))) {
                         Log.e(TAG, "Error: get protocol mode native returns false");
                     }
@@ -208,13 +226,15 @@
                 break;
 
                 case MESSAGE_ON_GET_PROTOCOL_MODE: {
-                    BluetoothDevice device = getDevice((byte[]) msg.obj);
+                    BluetoothDevice device = getAnonymousDevice((byte[]) msg.obj);
                     int protocolMode = msg.arg1;
                     broadcastProtocolMode(device, protocolMode);
                 }
                 break;
                 case MESSAGE_VIRTUAL_UNPLUG: {
                     BluetoothDevice device = (BluetoothDevice) msg.obj;
+                    Attributable.setAttributionSource(device,
+                            ActivityThread.currentAttributionSource());
                     if (!virtualUnPlugNative(Utils.getByteAddress(device))) {
                         Log.e(TAG, "Error: virtual unplug native returns false");
                     }
@@ -222,6 +242,8 @@
                 break;
                 case MESSAGE_SET_PROTOCOL_MODE: {
                     BluetoothDevice device = (BluetoothDevice) msg.obj;
+                    Attributable.setAttributionSource(device,
+                            ActivityThread.currentAttributionSource());
                     byte protocolMode = (byte) msg.arg1;
                     Log.d(TAG, "sending set protocol mode(" + protocolMode + ")");
                     if (!setProtocolModeNative(Utils.getByteAddress(device), protocolMode)) {
@@ -231,6 +253,8 @@
                 break;
                 case MESSAGE_GET_REPORT: {
                     BluetoothDevice device = (BluetoothDevice) msg.obj;
+                    Attributable.setAttributionSource(device,
+                            ActivityThread.currentAttributionSource());
                     Bundle data = msg.getData();
                     byte reportType = data.getByte(BluetoothHidHost.EXTRA_REPORT_TYPE);
                     byte reportId = data.getByte(BluetoothHidHost.EXTRA_REPORT_ID);
@@ -242,7 +266,7 @@
                 }
                 break;
                 case MESSAGE_ON_GET_REPORT: {
-                    BluetoothDevice device = getDevice((byte[]) msg.obj);
+                    BluetoothDevice device = getAnonymousDevice((byte[]) msg.obj);
                     Bundle data = msg.getData();
                     byte[] report = data.getByteArray(BluetoothHidHost.EXTRA_REPORT);
                     int bufferSize = data.getInt(BluetoothHidHost.EXTRA_REPORT_BUFFER_SIZE);
@@ -250,13 +274,15 @@
                 }
                 break;
                 case MESSAGE_ON_HANDSHAKE: {
-                    BluetoothDevice device = getDevice((byte[]) msg.obj);
+                    BluetoothDevice device = getAnonymousDevice((byte[]) msg.obj);
                     int status = msg.arg1;
                     broadcastHandshake(device, status);
                 }
                 break;
                 case MESSAGE_SET_REPORT: {
                     BluetoothDevice device = (BluetoothDevice) msg.obj;
+                    Attributable.setAttributionSource(device,
+                            ActivityThread.currentAttributionSource());
                     Bundle data = msg.getData();
                     byte reportType = data.getByte(BluetoothHidHost.EXTRA_REPORT_TYPE);
                     String report = data.getString(BluetoothHidHost.EXTRA_REPORT);
@@ -265,36 +291,31 @@
                     }
                 }
                 break;
-                case MESSAGE_SEND_DATA: {
-                    BluetoothDevice device = (BluetoothDevice) msg.obj;
-                    Bundle data = msg.getData();
-                    String report = data.getString(BluetoothHidHost.EXTRA_REPORT);
-                    if (!sendDataNative(Utils.getByteAddress(device), report)) {
-                        Log.e(TAG, "Error: send data native returns false");
-                    }
-                }
-                break;
                 case MESSAGE_ON_VIRTUAL_UNPLUG: {
-                    BluetoothDevice device = getDevice((byte[]) msg.obj);
+                    BluetoothDevice device = getAnonymousDevice((byte[]) msg.obj);
                     int status = msg.arg1;
                     broadcastVirtualUnplugStatus(device, status);
                 }
                 break;
                 case MESSAGE_GET_IDLE_TIME: {
                     BluetoothDevice device = (BluetoothDevice) msg.obj;
+                    Attributable.setAttributionSource(device,
+                            ActivityThread.currentAttributionSource());
                     if (!getIdleTimeNative(Utils.getByteAddress(device))) {
                         Log.e(TAG, "Error: get idle time native returns false");
                     }
                 }
                 break;
                 case MESSAGE_ON_GET_IDLE_TIME: {
-                    BluetoothDevice device = getDevice((byte[]) msg.obj);
+                    BluetoothDevice device = getAnonymousDevice((byte[]) msg.obj);
                     int idleTime = msg.arg1;
                     broadcastIdleTime(device, idleTime);
                 }
                 break;
                 case MESSAGE_SET_IDLE_TIME: {
                     BluetoothDevice device = (BluetoothDevice) msg.obj;
+                    Attributable.setAttributionSource(device,
+                            ActivityThread.currentAttributionSource());
                     Bundle data = msg.getData();
                     byte idleTime = data.getByte(BluetoothHidHost.EXTRA_IDLE_TIME);
                     if (!setIdleTimeNative(Utils.getByteAddress(device), idleTime)) {
@@ -322,22 +343,20 @@
             mService = null;
         }
 
-        private HidHostService getService() {
-            if (!Utils.checkCaller()) {
-                Log.w(TAG, "InputDevice call not allowed for non-active user");
+        @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
+        private HidHostService getService(AttributionSource source) {
+            if (!Utils.checkCallerIsSystemOrActiveUser(TAG)
+                    || !Utils.checkServiceAvailable(mService, TAG)
+                    || !Utils.checkConnectPermissionForDataDelivery(mService, source, TAG)) {
                 return null;
             }
-
-            if (mService != null && mService.isAvailable()) {
-                return mService;
-            }
-            Log.w(TAG, "Service is null");
-            return null;
+            return mService;
         }
 
         @Override
-        public boolean connect(BluetoothDevice device) {
-            HidHostService service = getService();
+        public boolean connect(BluetoothDevice device, AttributionSource source) {
+            Attributable.setAttributionSource(device, source);
+            HidHostService service = getService(source);
             if (service == null) {
                 return false;
             }
@@ -346,8 +365,9 @@
         }
 
         @Override
-        public boolean disconnect(BluetoothDevice device) {
-            HidHostService service = getService();
+        public boolean disconnect(BluetoothDevice device, AttributionSource source) {
+            Attributable.setAttributionSource(device, source);
+            HidHostService service = getService(source);
             if (service == null) {
                 return false;
             }
@@ -356,38 +376,37 @@
         }
 
         @Override
-        public int getConnectionState(BluetoothDevice device) {
-            HidHostService service = getService();
+        public int getConnectionState(BluetoothDevice device, AttributionSource source) {
+            Attributable.setAttributionSource(device, source);
+            HidHostService service = getService(source);
             if (service == null) {
                 return BluetoothHidHost.STATE_DISCONNECTED;
             }
-            enforceBluetoothPermission(service);
             return service.getConnectionState(device);
         }
 
         @Override
-        public List<BluetoothDevice> getConnectedDevices() {
-            HidHostService service = getService();
-            if (service == null) {
-                return new ArrayList<>();
-            }
-            enforceBluetoothPermission(service);
-            return getDevicesMatchingConnectionStates(new int[]{BluetoothProfile.STATE_CONNECTED});
+        public List<BluetoothDevice> getConnectedDevices(AttributionSource source) {
+            return getDevicesMatchingConnectionStates(new int[] {
+                    BluetoothProfile.STATE_CONNECTED
+            }, source);
         }
 
         @Override
-        public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
-            HidHostService service = getService();
+        public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states,
+                AttributionSource source) {
+            HidHostService service = getService(source);
             if (service == null) {
                 return new ArrayList<BluetoothDevice>(0);
             }
-            enforceBluetoothPermission(service);
             return service.getDevicesMatchingConnectionStates(states);
         }
 
         @Override
-        public boolean setConnectionPolicy(BluetoothDevice device, int connectionPolicy) {
-            HidHostService service = getService();
+        public boolean setConnectionPolicy(BluetoothDevice device, int connectionPolicy,
+                AttributionSource source) {
+            Attributable.setAttributionSource(device, source);
+            HidHostService service = getService(source);
             if (service == null) {
                 return false;
             }
@@ -396,8 +415,9 @@
         }
 
         @Override
-        public int getConnectionPolicy(BluetoothDevice device) {
-            HidHostService service = getService();
+        public int getConnectionPolicy(BluetoothDevice device, AttributionSource source) {
+            Attributable.setAttributionSource(device, source);
+            HidHostService service = getService(source);
             if (service == null) {
                 return BluetoothProfile.CONNECTION_POLICY_UNKNOWN;
             }
@@ -407,83 +427,86 @@
 
         /* The following APIs regarding test app for compliance */
         @Override
-        public boolean getProtocolMode(BluetoothDevice device) {
-            HidHostService service = getService();
+        public boolean getProtocolMode(BluetoothDevice device, AttributionSource source) {
+            Attributable.setAttributionSource(device, source);
+            HidHostService service = getService(source);
             if (service == null) {
                 return false;
             }
-            enforceBluetoothAdminPermission(service);
             return service.getProtocolMode(device);
         }
 
         @Override
-        public boolean virtualUnplug(BluetoothDevice device) {
-            HidHostService service = getService();
+        public boolean virtualUnplug(BluetoothDevice device, AttributionSource source) {
+            Attributable.setAttributionSource(device, source);
+            HidHostService service = getService(source);
             if (service == null) {
                 return false;
             }
-            enforceBluetoothAdminPermission(service);
             return service.virtualUnplug(device);
         }
 
         @Override
-        public boolean setProtocolMode(BluetoothDevice device, int protocolMode) {
-            HidHostService service = getService();
+        public boolean setProtocolMode(BluetoothDevice device, int protocolMode,
+                AttributionSource source) {
+            Attributable.setAttributionSource(device, source);
+            HidHostService service = getService(source);
             if (service == null) {
                 return false;
             }
-            enforceBluetoothAdminPermission(service);
             return service.setProtocolMode(device, protocolMode);
         }
 
         @Override
         public boolean getReport(BluetoothDevice device, byte reportType, byte reportId,
-                int bufferSize) {
-            HidHostService service = getService();
+                int bufferSize, AttributionSource source) {
+            Attributable.setAttributionSource(device, source);
+            HidHostService service = getService(source);
             if (service == null) {
                 return false;
             }
-            enforceBluetoothAdminPermission(service);
             return service.getReport(device, reportType, reportId, bufferSize);
         }
 
         @Override
-        public boolean setReport(BluetoothDevice device, byte reportType, String report) {
-            HidHostService service = getService();
+        public boolean setReport(BluetoothDevice device, byte reportType, String report,
+                AttributionSource source) {
+            Attributable.setAttributionSource(device, source);
+            HidHostService service = getService(source);
             if (service == null) {
                 return false;
             }
-            enforceBluetoothAdminPermission(service);
             return service.setReport(device, reportType, report);
         }
 
         @Override
-        public boolean sendData(BluetoothDevice device, String report) {
-            HidHostService service = getService();
+        public boolean sendData(BluetoothDevice device, String report, AttributionSource source) {
+            Attributable.setAttributionSource(device, source);
+            HidHostService service = getService(source);
             if (service == null) {
                 return false;
             }
-            enforceBluetoothAdminPermission(service);
             return service.sendData(device, report);
         }
 
         @Override
-        public boolean setIdleTime(BluetoothDevice device, byte idleTime) {
-            HidHostService service = getService();
+        public boolean setIdleTime(BluetoothDevice device, byte idleTime,
+                AttributionSource source) {
+            Attributable.setAttributionSource(device, source);
+            HidHostService service = getService(source);
             if (service == null) {
                 return false;
             }
-            enforceBluetoothAdminPermission(service);
             return service.setIdleTime(device, idleTime);
         }
 
         @Override
-        public boolean getIdleTime(BluetoothDevice device) {
-            HidHostService service = getService();
+        public boolean getIdleTime(BluetoothDevice device, AttributionSource source) {
+            Attributable.setAttributionSource(device, source);
+            HidHostService service = getService(source);
             if (service == null) {
                 return false;
             }
-            enforceBluetoothAdminPermission(service);
             return service.getIdleTime(device);
         }
     }
@@ -496,7 +519,7 @@
      * Connects the hid host profile for the passed in device
      *
      * @param device is the device with which to connect the hid host profile
-     * @return true if connection is successful, false otherwise
+     * @return true if connection request is passed down to mHandler.
      */
     public boolean connect(BluetoothDevice device) {
         if (DBG) Log.d(TAG, "connect: " + device.getAddress());
@@ -579,8 +602,11 @@
         if (DBG) {
             Log.d(TAG, "setConnectionPolicy: " + device.getAddress());
         }
-        AdapterService.getAdapterService().getDatabase()
-                .setProfileConnectionPolicy(device, BluetoothProfile.HID_HOST, connectionPolicy);
+
+        if (!mDatabaseManager.setProfileConnectionPolicy(device, BluetoothProfile.HID_HOST,
+                  connectionPolicy)) {
+            return false;
+        }
         if (DBG) {
             Log.d(TAG, "Saved connectionPolicy " + device + " = " + connectionPolicy);
         }
@@ -608,7 +634,7 @@
         if (DBG) {
             Log.d(TAG, "getConnectionPolicy: " + device.getAddress());
         }
-        return AdapterService.getAdapterService().getDatabase()
+        return mDatabaseManager
                 .getProfileConnectionPolicy(device, BluetoothProfile.HID_HOST);
     }
 
@@ -624,8 +650,6 @@
         Message msg = mHandler.obtainMessage(MESSAGE_GET_PROTOCOL_MODE, device);
         mHandler.sendMessage(msg);
         return true;
-        /* String objectPath = getObjectPathFromAddress(device.getAddress());
-            return getProtocolModeInputDeviceNative(objectPath);*/
     }
 
     boolean virtualUnplug(BluetoothDevice device) {
@@ -806,7 +830,8 @@
         intent.putExtra(BluetoothProfile.EXTRA_STATE, newState);
         intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
         intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
-        sendBroadcastAsUser(intent, UserHandle.ALL, BLUETOOTH_PERM);
+        sendBroadcastAsUser(intent, UserHandle.ALL, BLUETOOTH_CONNECT,
+                Utils.getTempAllowlistBroadcastOptions());
     }
 
     private void broadcastHandshake(BluetoothDevice device, int status) {
@@ -814,7 +839,7 @@
         intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
         intent.putExtra(BluetoothHidHost.EXTRA_STATUS, status);
         intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
-        sendBroadcast(intent, BLUETOOTH_PERM);
+        sendBroadcast(intent, BLUETOOTH_CONNECT, Utils.getTempAllowlistBroadcastOptions());
     }
 
     private void broadcastProtocolMode(BluetoothDevice device, int protocolMode) {
@@ -822,7 +847,7 @@
         intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
         intent.putExtra(BluetoothHidHost.EXTRA_PROTOCOL_MODE, protocolMode);
         intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
-        sendBroadcast(intent, BLUETOOTH_PERM);
+        sendBroadcast(intent, BLUETOOTH_CONNECT, Utils.getTempAllowlistBroadcastOptions());
         if (DBG) {
             Log.d(TAG, "Protocol Mode (" + device + "): " + protocolMode);
         }
@@ -834,7 +859,7 @@
         intent.putExtra(BluetoothHidHost.EXTRA_REPORT, report);
         intent.putExtra(BluetoothHidHost.EXTRA_REPORT_BUFFER_SIZE, rptSize);
         intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
-        sendBroadcast(intent, BLUETOOTH_PERM);
+        sendBroadcast(intent, BLUETOOTH_CONNECT, Utils.getTempAllowlistBroadcastOptions());
     }
 
     private void broadcastVirtualUnplugStatus(BluetoothDevice device, int status) {
@@ -842,7 +867,7 @@
         intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
         intent.putExtra(BluetoothHidHost.EXTRA_VIRTUAL_UNPLUG_STATUS, status);
         intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
-        sendBroadcast(intent, BLUETOOTH_PERM);
+        sendBroadcast(intent, BLUETOOTH_CONNECT, Utils.getTempAllowlistBroadcastOptions());
     }
 
     private void broadcastIdleTime(BluetoothDevice device, int idleTime) {
@@ -850,7 +875,7 @@
         intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
         intent.putExtra(BluetoothHidHost.EXTRA_IDLE_TIME, idleTime);
         intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
-        sendBroadcast(intent, BLUETOOTH_PERM);
+        sendBroadcast(intent, BLUETOOTH_CONNECT, Utils.getTempAllowlistBroadcastOptions());
         if (DBG) {
             Log.d(TAG, "Idle time (" + device + "): " + idleTime);
         }
diff --git a/src/com/android/bluetooth/le_audio/LeAudioNativeInterface.java b/src/com/android/bluetooth/le_audio/LeAudioNativeInterface.java
new file mode 100644
index 0000000..4aa8a53
--- /dev/null
+++ b/src/com/android/bluetooth/le_audio/LeAudioNativeInterface.java
@@ -0,0 +1,217 @@
+/*
+ * Copyright 2020 HIMSA II K/S - www.himsa.com.
+ * Represented by EHIMA - www.ehima.com
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * Defines the native interface that is used by state machine/service to
+ * send or receive messages from the native stack. This file is registered
+ * for the native methods in the corresponding JNI C++ file.
+ */
+package com.android.bluetooth.le_audio;
+
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.util.Log;
+
+import com.android.bluetooth.Utils;
+import com.android.internal.annotations.GuardedBy;
+
+/**
+ * LeAudio Native Interface to/from JNI.
+ */
+public class LeAudioNativeInterface {
+    private static final String TAG = "LeAudioNativeInterface";
+    private static final boolean DBG = true;
+    private BluetoothAdapter mAdapter;
+
+    @GuardedBy("INSTANCE_LOCK")
+    private static LeAudioNativeInterface sInstance;
+    private static final Object INSTANCE_LOCK = new Object();
+
+    static {
+        classInitNative();
+    }
+
+    private LeAudioNativeInterface() {
+        mAdapter = BluetoothAdapter.getDefaultAdapter();
+        if (mAdapter == null) {
+            Log.wtfStack(TAG, "No Bluetooth Adapter Available");
+        }
+    }
+
+    /**
+     * Get singleton instance.
+     */
+    public static LeAudioNativeInterface getInstance() {
+        synchronized (INSTANCE_LOCK) {
+            if (sInstance == null) {
+                sInstance = new LeAudioNativeInterface();
+            }
+            return sInstance;
+        }
+    }
+
+    private byte[] getByteAddress(BluetoothDevice device) {
+        if (device == null) {
+            return Utils.getBytesFromAddress("00:00:00:00:00:00");
+        }
+        return Utils.getBytesFromAddress(device.getAddress());
+    }
+
+    private void sendMessageToService(LeAudioStackEvent event) {
+        LeAudioService service = LeAudioService.getLeAudioService();
+        if (service != null) {
+            service.messageFromNative(event);
+        } else {
+            Log.e(TAG, "Event ignored, service not available: " + event);
+        }
+    }
+
+    private BluetoothDevice getDevice(byte[] address) {
+        return mAdapter.getRemoteDevice(address);
+    }
+
+    // Callbacks from the native stack back into the Java framework.
+    // All callbacks are routed via the Service which will disambiguate which
+    // state machine the message should be routed to.
+    private void onConnectionStateChanged(int state, byte[] address) {
+        LeAudioStackEvent event =
+                new LeAudioStackEvent(LeAudioStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED);
+        event.device = getDevice(address);
+        event.valueInt1 = state;
+
+        if (DBG) {
+            Log.d(TAG, "onConnectionStateChanged: " + event);
+        }
+        sendMessageToService(event);
+    }
+
+    // Callbacks from the native stack back into the Java framework.
+    // All callbacks are routed via the Service which will disambiguate which
+    // state machine the message should be routed to.
+    private void onSetMemberAvailable(byte[] address, int groupId) {
+        LeAudioStackEvent event =
+                new LeAudioStackEvent(LeAudioStackEvent.EVENT_TYPE_SET_MEMBER_AVAILABLE);
+        event.device = getDevice(address);
+        event.valueInt1 = groupId;
+        if (DBG) {
+            Log.d(TAG, "onSetMemberAvailable: " + event);
+        }
+        sendMessageToService(event);
+    }
+
+    private void onGroupStatus(int groupId, int groupStatus, int groupFlags) {
+        LeAudioStackEvent event =
+                new LeAudioStackEvent(LeAudioStackEvent.EVENT_TYPE_GROUP_STATUS_CHANGED);
+        event.valueInt1 = groupId;
+        event.valueInt2 = groupStatus;
+        event.valueInt3 = groupFlags;
+        event.device = null;
+
+        if (DBG) {
+            Log.d(TAG, "onGroupStatus: " + event);
+        }
+        sendMessageToService(event);
+    }
+
+    private void onAudioConf(int direction, int groupId, int sinkAudioLocation,
+                             int sourceAudioLocation, byte[] address) {
+        LeAudioStackEvent event =
+                new LeAudioStackEvent(LeAudioStackEvent.EVENT_TYPE_AUDIO_CONF_CHANGED);
+        event.valueInt1 = direction;
+        event.valueInt2 = groupId;
+        event.valueInt3 = sinkAudioLocation;
+        event.valueInt4 = sourceAudioLocation;
+        event.device = getDevice(address);
+
+        if (DBG) {
+            Log.d(TAG, "onAudioConf: " + event);
+        }
+        sendMessageToService(event);
+    }
+
+    /**
+     * Initializes the native interface.
+     *
+     * priorities to configure.
+     */
+    public void init() {
+        initNative();
+    }
+
+    /**
+     * Cleanup the native interface.
+     */
+    public void cleanup() {
+        cleanupNative();
+    }
+
+    /**
+     * Initiates LeAudio connection to a remote device.
+     *
+     * @param device the remote device
+     * @return true on success, otherwise false.
+     */
+    public boolean connectLeAudio(BluetoothDevice device) {
+        return connectLeAudioNative(getByteAddress(device));
+    }
+
+    /**
+     * Disconnects LeAudio from a remote device.
+     *
+     * @param device the remote device
+     * @return true on success, otherwise false.
+     */
+    public boolean disconnectLeAudio(BluetoothDevice device) {
+        return disconnectLeAudioNative(getByteAddress(device));
+    }
+
+    /**
+     * Enable content streaming.
+     * @param groupId group identifier
+     * @param contentType type of content to stream
+     */
+    public void groupStream(int groupId, int contentType) {
+        groupStreamNative(groupId, contentType);
+    }
+
+    /**
+     * Suspend content streaming.
+     * @param groupId  group identifier
+     */
+    public void groupSuspend(int groupId) {
+        groupSuspendNative(groupId);
+    }
+
+    /**
+     * Stop all content streaming.
+     * @param groupId  group identifier
+     * TODO: Maybe we should use also pass the content type argument
+     */
+    public void groupStop(int groupId) {
+        groupStopNative(groupId);
+    }
+
+    // Native methods that call into the JNI interface
+    private static native void classInitNative();
+    private native void initNative();
+    private native void cleanupNative();
+    private native boolean connectLeAudioNative(byte[] address);
+    private native boolean disconnectLeAudioNative(byte[] address);
+    private native void groupStreamNative(int groupId, int contentType);
+    private native void groupSuspendNative(int groupId);
+    private native void groupStopNative(int groupId);
+}
diff --git a/src/com/android/bluetooth/le_audio/LeAudioService.java b/src/com/android/bluetooth/le_audio/LeAudioService.java
new file mode 100644
index 0000000..c9bf90c
--- /dev/null
+++ b/src/com/android/bluetooth/le_audio/LeAudioService.java
@@ -0,0 +1,844 @@
+/*
+ * Copyright 2020 HIMSA II K/S - www.himsa.com.
+ * Represented by EHIMA - www.ehima.com
+ *
+ * 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.le_audio;
+
+import static android.bluetooth.IBluetoothLeAudio.LE_AUDIO_GROUP_ID_INVALID;
+
+import android.annotation.RequiresPermission;
+import android.annotation.SuppressLint;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothLeAudio;
+import android.bluetooth.BluetoothProfile;
+import android.bluetooth.BluetoothUuid;
+import android.bluetooth.IBluetoothLeAudio;
+import android.content.Attributable;
+import android.content.AttributionSource;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.media.AudioManager;
+import android.os.HandlerThread;
+import android.os.ParcelUuid;
+import android.util.Log;
+
+import com.android.bluetooth.Utils;
+import com.android.bluetooth.btservice.AdapterService;
+import com.android.bluetooth.btservice.ProfileService;
+import com.android.bluetooth.btservice.storage.DatabaseManager;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.ArrayUtils;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * Provides Bluetooth LeAudio profile, as a service in the Bluetooth application.
+ * @hide
+ */
+public class LeAudioService extends ProfileService {
+    private static final boolean DBG = true;
+    private static final String TAG = "LeAudioService";
+
+    // Upper limit of all LeAudio devices: Bonded or Connected
+    private static final int MAX_LE_AUDIO_STATE_MACHINES = 10;
+    private static LeAudioService sLeAudioService;
+
+    private AdapterService mAdapterService;
+    private DatabaseManager mDatabaseManager;
+    private HandlerThread mStateMachinesThread;
+    private BluetoothDevice mPreviousAudioDevice;
+
+    LeAudioNativeInterface mLeAudioNativeInterface;
+    AudioManager mAudioManager;
+
+    private final Map<BluetoothDevice, LeAudioStateMachine> mStateMachines = new HashMap<>();
+
+    private final Map<BluetoothDevice, Integer> mDeviceGroupIdMap = new ConcurrentHashMap<>();
+    private final Map<Integer, Boolean> mGroupIdConnectedMap = new HashMap<>();
+    private int mActiveDeviceGroupId = LE_AUDIO_GROUP_ID_INVALID;
+
+    private BroadcastReceiver mBondStateChangedReceiver;
+    private BroadcastReceiver mConnectionStateChangedReceiver;
+
+    @Override
+    protected IProfileServiceBinder initBinder() {
+        return new BluetoothLeAudioBinder(this);
+    }
+
+    @Override
+    protected void create() {
+        Log.i(TAG, "create()");
+    }
+
+    @Override
+    protected boolean start() {
+        Log.i(TAG, "start()");
+        if (sLeAudioService != null) {
+            throw new IllegalStateException("start() called twice");
+        }
+
+        mAdapterService = Objects.requireNonNull(AdapterService.getAdapterService(),
+                "AdapterService cannot be null when LeAudioService starts");
+        mLeAudioNativeInterface = Objects.requireNonNull(LeAudioNativeInterface.getInstance(),
+                "LeAudioNativeInterface cannot be null when LeAudioService starts");
+        mDatabaseManager = Objects.requireNonNull(mAdapterService.getDatabase(),
+                "DatabaseManager cannot be null when A2dpService starts");
+        mAudioManager = getSystemService(AudioManager.class);
+        Objects.requireNonNull(mAudioManager,
+                "AudioManager cannot be null when LeAudioService starts");
+
+        // Start handler thread for state machines
+        mStateMachines.clear();
+        mStateMachinesThread = new HandlerThread("LeAudioService.StateMachines");
+        mStateMachinesThread.start();
+
+        mDeviceGroupIdMap.clear();
+        mGroupIdConnectedMap.clear();
+
+        // Setup broadcast receivers
+        IntentFilter filter = new IntentFilter();
+        filter.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED);
+        mBondStateChangedReceiver = new BondStateChangedReceiver();
+        registerReceiver(mBondStateChangedReceiver, filter);
+        filter = new IntentFilter();
+        filter.addAction(BluetoothLeAudio.ACTION_LE_AUDIO_CONNECTION_STATE_CHANGED);
+        mConnectionStateChangedReceiver = new ConnectionStateChangedReceiver();
+        registerReceiver(mConnectionStateChangedReceiver, filter);
+
+        // Mark service as started
+        setLeAudioService(this);
+
+        mLeAudioNativeInterface.init();
+
+        return true;
+    }
+
+    @Override
+    protected boolean stop() {
+        Log.i(TAG, "stop()");
+        if (sLeAudioService == null) {
+            Log.w(TAG, "stop() called before start()");
+            return true;
+        }
+
+        // Cleanup native interfaces
+        mLeAudioNativeInterface.cleanup();
+        mLeAudioNativeInterface = null;
+
+        // Set the service and BLE devices as inactive
+        setLeAudioService(null);
+
+        // Unregister broadcast receivers
+        unregisterReceiver(mBondStateChangedReceiver);
+        mBondStateChangedReceiver = null;
+        unregisterReceiver(mConnectionStateChangedReceiver);
+        mConnectionStateChangedReceiver = null;
+
+        // Destroy state machines and stop handler thread
+        synchronized (mStateMachines) {
+            for (LeAudioStateMachine sm : mStateMachines.values()) {
+                sm.doQuit();
+                sm.cleanup();
+            }
+            mStateMachines.clear();
+        }
+
+        mDeviceGroupIdMap.clear();
+        mGroupIdConnectedMap.clear();
+
+        if (mStateMachinesThread != null) {
+            mStateMachinesThread.quitSafely();
+            mStateMachinesThread = null;
+        }
+
+        mAudioManager = null;
+        mAdapterService = null;
+        return true;
+    }
+
+    @Override
+    protected void cleanup() {
+        Log.i(TAG, "cleanup()");
+    }
+
+    public static synchronized LeAudioService getLeAudioService() {
+        if (sLeAudioService == null) {
+            Log.w(TAG, "getLeAudioService(): service is NULL");
+            return null;
+        }
+        if (!sLeAudioService.isAvailable()) {
+            Log.w(TAG, "getLeAudioService(): service is not available");
+            return null;
+        }
+        return sLeAudioService;
+    }
+
+    private static synchronized void setLeAudioService(LeAudioService instance) {
+        if (DBG) {
+            Log.d(TAG, "setLeAudioService(): set to: " + instance);
+        }
+        sLeAudioService = instance;
+    }
+
+    public boolean connect(BluetoothDevice device) {
+        if (DBG) {
+            Log.d(TAG, "connect(): " + device);
+        }
+
+        if (getConnectionPolicy(device) == BluetoothProfile.CONNECTION_POLICY_FORBIDDEN) {
+            Log.e(TAG, "Cannot connect to " + device + " : CONNECTION_POLICY_FORBIDDEN");
+            return false;
+        }
+        ParcelUuid[] featureUuids = mAdapterService.getRemoteUuids(device);
+        if (!ArrayUtils.contains(featureUuids, BluetoothUuid.LE_AUDIO)) {
+            Log.e(TAG, "Cannot connect to " + device + " : Remote does not have LE_AUDIO UUID");
+            return false;
+        }
+
+        int groupId = getGroupId(device);
+
+        //TODO: disconnect active device if it's not in groupId
+
+        if (DBG) {
+            Log.d(TAG, "connect(): " + device + "group id: " + groupId);
+        }
+
+        synchronized (mStateMachines) {
+            LeAudioStateMachine sm = getOrCreateStateMachine(device);
+            if (sm == null) {
+                Log.e(TAG, "Ignored connect request for " + device + " : no state machine");
+                return false;
+            }
+            sm.sendMessage(LeAudioStateMachine.CONNECT, groupId);
+        }
+
+        // Connect other devices from this group
+        if (groupId != LE_AUDIO_GROUP_ID_INVALID) {
+            for (BluetoothDevice storedDevice : mDeviceGroupIdMap.keySet()) {
+                if (device.equals(storedDevice)) {
+                    continue;
+                }
+                if (getGroupId(storedDevice) != groupId) {
+                    continue;
+                }
+                synchronized (mStateMachines) {
+                    LeAudioStateMachine sm = getOrCreateStateMachine(storedDevice);
+                    if (sm == null) {
+                        Log.e(TAG, "Ignored connect request for " + storedDevice
+                                + " : no state machine");
+                        continue;
+                    }
+                    sm.sendMessage(LeAudioStateMachine.CONNECT, groupId);
+                }
+            }
+        }
+        return true;
+    }
+
+    /**
+     * Disconnects LE Audio for the remote bluetooth device
+     *
+     * @param device is the device with which we would like to disconnect LE Audio
+     * @return true if profile disconnected, false if device not connected over LE Audio
+     */
+    public boolean disconnect(BluetoothDevice device) {
+        if (DBG) {
+            Log.d(TAG, "disconnect(): " + device);
+        }
+
+        // Disconnect this device
+        synchronized (mStateMachines) {
+            LeAudioStateMachine sm = mStateMachines.get(device);
+            if (sm == null) {
+                Log.e(TAG, "Ignored disconnect request for " + device
+                        + " : no state machine");
+                return false;
+            }
+            sm.sendMessage(LeAudioStateMachine.DISCONNECT);
+        }
+
+        // Disconnect other devices from this group
+        int groupId = getGroupId(device);
+        if (groupId != LE_AUDIO_GROUP_ID_INVALID) {
+            for (BluetoothDevice storedDevice : mDeviceGroupIdMap.keySet()) {
+                if (device.equals(storedDevice)) {
+                    continue;
+                }
+                if (getGroupId(storedDevice) != groupId) {
+                    continue;
+                }
+                synchronized (mStateMachines) {
+                    LeAudioStateMachine sm = mStateMachines.get(storedDevice);
+                    if (sm == null) {
+                        Log.e(TAG, "Ignored disconnect request for " + storedDevice
+                                + " : no state machine");
+                        continue;
+                    }
+                    sm.sendMessage(LeAudioStateMachine.DISCONNECT);
+                }
+            }
+        }
+        return true;
+    }
+
+    List<BluetoothDevice> getConnectedDevices() {
+        synchronized (mStateMachines) {
+            List<BluetoothDevice> devices = new ArrayList<>();
+            for (LeAudioStateMachine sm : mStateMachines.values()) {
+                if (sm.isConnected()) {
+                    devices.add(sm.getDevice());
+                }
+            }
+            return devices;
+        }
+    }
+
+    List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
+        ArrayList<BluetoothDevice> devices = new ArrayList<>();
+        if (states == null) {
+            return devices;
+        }
+        final BluetoothDevice[] bondedDevices = mAdapterService.getBondedDevices();
+        if (bondedDevices == null) {
+            return devices;
+        }
+        synchronized (mStateMachines) {
+            for (BluetoothDevice device : bondedDevices) {
+                final ParcelUuid[] featureUuids = device.getUuids();
+                if (!ArrayUtils.contains(featureUuids, BluetoothUuid.LE_AUDIO)) {
+                    continue;
+                }
+                int connectionState = BluetoothProfile.STATE_DISCONNECTED;
+                LeAudioStateMachine sm = mStateMachines.get(device);
+                if (sm != null) {
+                    connectionState = sm.getConnectionState();
+                }
+                for (int state : states) {
+                    if (connectionState == state) {
+                        devices.add(device);
+                        break;
+                    }
+                }
+            }
+            return devices;
+        }
+    }
+
+    /**
+     * Get the list of devices that have state machines.
+     *
+     * @return the list of devices that have state machines
+     */
+    @VisibleForTesting
+    List<BluetoothDevice> getDevices() {
+        List<BluetoothDevice> devices = new ArrayList<>();
+        synchronized (mStateMachines) {
+            for (LeAudioStateMachine sm : mStateMachines.values()) {
+                devices.add(sm.getDevice());
+            }
+            return devices;
+        }
+    }
+
+    /**
+     * Get the current connection state of the profile
+     *
+     * @param device is the remote bluetooth device
+     * @return {@link BluetoothProfile#STATE_DISCONNECTED} if this profile is disconnected,
+     * {@link BluetoothProfile#STATE_CONNECTING} if this profile is being connected,
+     * {@link BluetoothProfile#STATE_CONNECTED} if this profile is connected, or
+     * {@link BluetoothProfile#STATE_DISCONNECTING} if this profile is being disconnected
+     */
+    public int getConnectionState(BluetoothDevice device) {
+        synchronized (mStateMachines) {
+            LeAudioStateMachine sm = mStateMachines.get(device);
+            if (sm == null) {
+                return BluetoothProfile.STATE_DISCONNECTED;
+            }
+            return sm.getConnectionState();
+        }
+    }
+
+    /**
+     * Set the active device.
+     *
+     * @param device the new active device
+     * @return true on success, otherwise false
+     */
+    public boolean setActiveDevice(BluetoothDevice device) {
+        if (DBG) {
+            Log.d(TAG, "setActiveDevice:" + device);
+        }
+
+        return false;
+    }
+
+    /**
+     * Get the connected physical LeAudio devices that are active.
+     *
+     * @return the list of active devices.
+     */
+    List<BluetoothDevice> getActiveDevices() {
+        if (DBG) {
+            Log.d(TAG, "getActiveDevices");
+        }
+        ArrayList<BluetoothDevice> activeDevices = new ArrayList<>();
+        return activeDevices;
+    }
+
+    // Suppressed since this is part of a local process
+    @SuppressLint("AndroidFrameworkRequiresPermission")
+    void messageFromNative(LeAudioStackEvent stackEvent) {
+        Log.d(TAG, "Message from native: " + stackEvent);
+        BluetoothDevice device = stackEvent.device;
+
+        if (stackEvent.type == LeAudioStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED) {
+        // Some events require device state machine
+            synchronized (mStateMachines) {
+                LeAudioStateMachine sm = mStateMachines.get(device);
+                if (sm == null) {
+                    switch (stackEvent.valueInt1) {
+                        case LeAudioStackEvent.CONNECTION_STATE_CONNECTED:
+                        case LeAudioStackEvent.CONNECTION_STATE_CONNECTING:
+                            sm = getOrCreateStateMachine(device);
+                            break;
+                        default:
+                            break;
+                    }
+                }
+
+                if (sm == null) {
+                    Log.e(TAG, "Cannot process stack event: no state machine: " + stackEvent);
+                    return;
+                }
+
+                sm.sendMessage(LeAudioStateMachine.STACK_EVENT, stackEvent);
+                return;
+            }
+        }
+
+        // Some events do not require device state machine
+        if (stackEvent.type == LeAudioStackEvent.EVENT_TYPE_GROUP_STATUS_CHANGED) {
+            int group_id = stackEvent.valueInt1;
+            int group_status = stackEvent.valueInt2;
+            int group_flags = stackEvent.valueInt3;
+
+            // TODO: Handle Stream events
+            switch (group_status) {
+                case LeAudioStackEvent.GROUP_STATUS_IDLE:
+                case LeAudioStackEvent.GROUP_STATUS_RECONFIGURED:
+                case LeAudioStackEvent.GROUP_STATUS_DESTROYED:
+                case LeAudioStackEvent.GROUP_STATUS_SUSPENDED:
+                    setActiveDevice(null);
+                    // TODO: Get all devices with a group and Unassign? or they are already unasigned
+                    // This event may come after removing all the nodes from a certain group but only that.
+                    break;
+                case LeAudioStackEvent.GROUP_STATUS_STREAMING:
+                    BluetoothDevice streaming_device = getConnectedPeerDevices(group_id).get(0);
+                    setActiveDevice(streaming_device);
+                    break;
+                default:
+                    break;
+            }
+
+        }
+    }
+
+    private LeAudioStateMachine getOrCreateStateMachine(BluetoothDevice device) {
+        if (device == null) {
+            Log.e(TAG, "getOrCreateStateMachine failed: device cannot be null");
+            return null;
+        }
+        synchronized (mStateMachines) {
+            LeAudioStateMachine sm = mStateMachines.get(device);
+            if (sm != null) {
+                return sm;
+            }
+            // Limit the maximum number of state machines to avoid DoS attack
+            if (mStateMachines.size() >= MAX_LE_AUDIO_STATE_MACHINES) {
+                Log.e(TAG, "Maximum number of LeAudio state machines reached: "
+                        + MAX_LE_AUDIO_STATE_MACHINES);
+                return null;
+            }
+            if (DBG) {
+                Log.d(TAG, "Creating a new state machine for " + device);
+            }
+            sm = LeAudioStateMachine.make(device, this,
+                    mLeAudioNativeInterface, mStateMachinesThread.getLooper());
+            mStateMachines.put(device, sm);
+            return sm;
+        }
+    }
+
+    // Remove state machine if the bonding for a device is removed
+    private class BondStateChangedReceiver extends BroadcastReceiver {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            if (!BluetoothDevice.ACTION_BOND_STATE_CHANGED.equals(intent.getAction())) {
+                return;
+            }
+            int state = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE,
+                                           BluetoothDevice.ERROR);
+            BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
+            Objects.requireNonNull(device, "ACTION_BOND_STATE_CHANGED with no EXTRA_DEVICE");
+            bondStateChanged(device, state);
+        }
+    }
+
+    /**
+     * Process a change in the bonding state for a device.
+     *
+     * @param device the device whose bonding state has changed
+     * @param bondState the new bond state for the device. Possible values are:
+     * {@link BluetoothDevice#BOND_NONE},
+     * {@link BluetoothDevice#BOND_BONDING},
+     * {@link BluetoothDevice#BOND_BONDED}.
+     */
+    @VisibleForTesting
+    void bondStateChanged(BluetoothDevice device, int bondState) {
+        if (DBG) {
+            Log.d(TAG, "Bond state changed for device: " + device + " state: " + bondState);
+        }
+        // Remove state machine if the bonding for a device is removed
+        if (bondState != BluetoothDevice.BOND_NONE) {
+            return;
+        }
+        mDeviceGroupIdMap.remove(device);
+        synchronized (mStateMachines) {
+            LeAudioStateMachine sm = mStateMachines.get(device);
+            if (sm == null) {
+                return;
+            }
+            if (sm.getConnectionState() != BluetoothProfile.STATE_DISCONNECTED) {
+                return;
+            }
+            removeStateMachine(device);
+        }
+    }
+
+    private void removeStateMachine(BluetoothDevice device) {
+        synchronized (mStateMachines) {
+            LeAudioStateMachine sm = mStateMachines.get(device);
+            if (sm == null) {
+                Log.w(TAG, "removeStateMachine: device " + device
+                        + " does not have a state machine");
+                return;
+            }
+            Log.i(TAG, "removeStateMachine: removing state machine for device: " + device);
+            sm.doQuit();
+            sm.cleanup();
+            mStateMachines.remove(device);
+        }
+    }
+
+    private List<BluetoothDevice> getConnectedPeerDevices(int groupId) {
+        List<BluetoothDevice> result = new ArrayList<>();
+        for (BluetoothDevice peerDevice : getConnectedDevices()) {
+            if (getGroupId(peerDevice) == groupId) {
+                result.add(peerDevice);
+            }
+        }
+        return result;
+    }
+
+    @VisibleForTesting
+    synchronized void connectionStateChanged(BluetoothDevice device, int fromState,
+                                                     int toState) {
+        if ((device == null) || (fromState == toState)) {
+            Log.e(TAG, "connectionStateChanged: unexpected invocation. device=" + device
+                    + " fromState=" + fromState + " toState=" + toState);
+            return;
+        }
+        if (toState == BluetoothProfile.STATE_CONNECTED) {
+            int myGroupId = getGroupId(device);
+            if (myGroupId == LE_AUDIO_GROUP_ID_INVALID
+                    || getConnectedPeerDevices(myGroupId).size() == 1) {
+                // Log LE Audio connection event if we are the first device in a set
+                // Or when the GroupId has not been found
+                // MetricsLogger.logProfileConnectionEvent(
+                //         BluetoothMetricsProto.ProfileId.LE_AUDIO);
+            }
+            if (!mGroupIdConnectedMap.getOrDefault(myGroupId, false)) {
+                mGroupIdConnectedMap.put(myGroupId, true);
+            }
+        }
+        if (fromState == BluetoothProfile.STATE_CONNECTED && getConnectedDevices().isEmpty()) {
+            setActiveDevice(null);
+            int myGroupId = getGroupId(device);
+            mGroupIdConnectedMap.put(myGroupId, false);
+        }
+        // Check if the device is disconnected - if unbond, remove the state machine
+        if (toState == BluetoothProfile.STATE_DISCONNECTED) {
+            int bondState = mAdapterService.getBondState(device);
+            if (bondState == BluetoothDevice.BOND_NONE) {
+                if (DBG) {
+                    Log.d(TAG, device + " is unbond. Remove state machine");
+                }
+                removeStateMachine(device);
+            }
+        }
+    }
+
+    private class ConnectionStateChangedReceiver extends BroadcastReceiver {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            if (!BluetoothLeAudio.ACTION_LE_AUDIO_CONNECTION_STATE_CHANGED.equals(intent.getAction())) {
+                return;
+            }
+            BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
+            int toState = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, -1);
+            int fromState = intent.getIntExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, -1);
+            connectionStateChanged(device, fromState, toState);
+        }
+    }
+
+   /**
+     * Check whether can connect to a peer device.
+     * The check considers a number of factors during the evaluation.
+     *
+     * @param device the peer device to connect to
+     * @return true if connection is allowed, otherwise false
+     */
+    public boolean okToConnect(BluetoothDevice device) {
+        // Check if this is an incoming connection in Quiet mode.
+        if (mAdapterService.isQuietModeEnabled()) {
+            Log.e(TAG, "okToConnect: cannot connect to " + device + " : quiet mode enabled");
+            return false;
+        }
+        // Check 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 (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;
+    }
+
+    /**
+     * 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 the remote device
+     * @param connectionPolicy is the connection policy to set to for this profile
+     * @return true on success, otherwise false
+     */
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED)
+    public boolean setConnectionPolicy(BluetoothDevice device, int connectionPolicy) {
+        enforceCallingOrSelfPermission(BLUETOOTH_PRIVILEGED,
+                "Need BLUETOOTH_PRIVILEGED permission");
+        if (DBG) {
+            Log.d(TAG, "Saved connectionPolicy " + device + " = " + connectionPolicy);
+        }
+
+        if (!mDatabaseManager.setProfileConnectionPolicy(device, BluetoothProfile.LE_AUDIO,
+                  connectionPolicy)) {
+            return false;
+        }
+        if (connectionPolicy == BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
+            connect(device);
+        } else if (connectionPolicy == BluetoothProfile.CONNECTION_POLICY_FORBIDDEN) {
+            disconnect(device);
+        }
+        return true;
+    }
+
+    /**
+     * 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 mDatabaseManager
+                .getProfileConnectionPolicy(device, BluetoothProfile.LE_AUDIO);
+    }
+
+    /**
+     * Get device group id. Devices with same group id belong to same group (i.e left and right
+     * earbud)
+     * @param device LE Audio capable device
+     * @return group id that this device currently belongs to
+     */
+    public int getGroupId(BluetoothDevice device) {
+        if (device == null) {
+            return LE_AUDIO_GROUP_ID_INVALID;
+        }
+        //TODO: implement
+        return LE_AUDIO_GROUP_ID_INVALID;
+    }
+
+    /**
+     * Binder object: must be a static class or memory leak may occur
+     */
+    @VisibleForTesting
+    static class BluetoothLeAudioBinder extends IBluetoothLeAudio.Stub
+            implements IProfileServiceBinder {
+        private LeAudioService mService;
+
+        @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
+        private LeAudioService getService(AttributionSource source) {
+            if (!Utils.checkCallerIsSystemOrActiveUser(TAG)
+                    || !Utils.checkServiceAvailable(mService, TAG)
+                    || !Utils.checkConnectPermissionForDataDelivery(mService, source, TAG)) {
+                return null;
+            }
+            return mService;
+        }
+
+        BluetoothLeAudioBinder(LeAudioService svc) {
+            mService = svc;
+        }
+
+        @Override
+        public void cleanup() {
+            mService = null;
+        }
+
+        @Override
+        public boolean connect(BluetoothDevice device, AttributionSource source) {
+            Attributable.setAttributionSource(device, source);
+            LeAudioService service = getService(source);
+            if (service == null) {
+                return false;
+            }
+            return service.connect(device);
+        }
+
+        @Override
+        public boolean disconnect(BluetoothDevice device, AttributionSource source) {
+            Attributable.setAttributionSource(device, source);
+            LeAudioService service = getService(source);
+            if (service == null) {
+                return false;
+            }
+            return service.disconnect(device);
+        }
+
+        @Override
+        public List<BluetoothDevice> getConnectedDevices(AttributionSource source) {
+            LeAudioService service = getService(source);
+            if (service == null) {
+                return new ArrayList<>(0);
+            }
+            return service.getConnectedDevices();
+        }
+
+        @Override
+        public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states,
+                AttributionSource source) {
+            LeAudioService service = getService(source);
+            if (service == null) {
+                return new ArrayList<>(0);
+            }
+            return service.getDevicesMatchingConnectionStates(states);
+        }
+
+        @Override
+        public int getConnectionState(BluetoothDevice device, AttributionSource source) {
+            Attributable.setAttributionSource(device, source);
+            LeAudioService service = getService(source);
+            if (service == null) {
+                return BluetoothProfile.STATE_DISCONNECTED;
+            }
+            return service.getConnectionState(device);
+        }
+
+        @Override
+        public boolean setActiveDevice(BluetoothDevice device, AttributionSource source) {
+            Attributable.setAttributionSource(device, source);
+            LeAudioService service = getService(source);
+            if (service == null) {
+                return false;
+            }
+            return service.setActiveDevice(device);
+        }
+
+        @Override
+        public List<BluetoothDevice> getActiveDevices(AttributionSource source) {
+            LeAudioService service = getService(source);
+            if (service == null) {
+                return new ArrayList<>();
+            }
+            return service.getActiveDevices();
+        }
+
+        @Override
+        public boolean setConnectionPolicy(BluetoothDevice device, int connectionPolicy,
+                AttributionSource source) {
+            Attributable.setAttributionSource(device, source);
+            LeAudioService service = getService(source);
+            if (service == null) {
+                return false;
+            }
+            return service.setConnectionPolicy(device, connectionPolicy);
+        }
+
+        @Override
+        public int getConnectionPolicy(BluetoothDevice device, AttributionSource source) {
+            Attributable.setAttributionSource(device, source);
+            LeAudioService service = getService(source);
+            if (service == null) {
+                return BluetoothProfile.CONNECTION_POLICY_UNKNOWN;
+            }
+            return service.getConnectionPolicy(device);
+        }
+
+        @Override
+        public int getGroupId(BluetoothDevice device, AttributionSource source) {
+            Attributable.setAttributionSource(device, source);
+            LeAudioService service = getService(source);
+            if (service == null) {
+                return LE_AUDIO_GROUP_ID_INVALID;
+            }
+
+            return service.getGroupId(device);
+        }
+    }
+
+    @Override
+    public void dump(StringBuilder sb) {
+        super.dump(sb);
+        // TODO: Dump all state machines
+    }
+}
diff --git a/src/com/android/bluetooth/le_audio/LeAudioStackEvent.java b/src/com/android/bluetooth/le_audio/LeAudioStackEvent.java
new file mode 100644
index 0000000..d451b35
--- /dev/null
+++ b/src/com/android/bluetooth/le_audio/LeAudioStackEvent.java
@@ -0,0 +1,168 @@
+/*
+ * Copyright 2020 HIMSA II K/S - www.himsa.com.
+ * Represented by EHIMA - www.ehima.com
+ *
+ * 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.le_audio;
+
+import android.bluetooth.BluetoothDevice;
+
+/**
+ * Stack event sent via a callback from JNI to Java, or generated
+ * internally by the LeAudio State Machine.
+ */
+public class LeAudioStackEvent {
+    // Event types for STACK_EVENT message (coming from native in bt_le_audio.h)
+    private static final int EVENT_TYPE_NONE = 0;
+    public static final int EVENT_TYPE_CONNECTION_STATE_CHANGED = 1;
+    public static final int EVENT_TYPE_GROUP_STATUS_CHANGED = 2;
+    public static final int EVENT_TYPE_AUDIO_CONF_CHANGED = 4;
+    public static final int EVENT_TYPE_SET_MEMBER_AVAILABLE = 5;
+    // -------- DO NOT PUT ANY NEW UNICAST EVENTS BELOW THIS LINE-------------
+    public static final int EVENT_TYPE_UNICAST_MAX = 7;
+
+    // Do not modify without updating the HAL bt_le_audio.h files.
+    // Match up with GroupStatus enum of bt_le_audio.h
+    static final int CONNECTION_STATE_DISCONNECTED = 0;
+    static final int CONNECTION_STATE_CONNECTING = 1;
+    static final int CONNECTION_STATE_CONNECTED = 2;
+    static final int CONNECTION_STATE_DISCONNECTING = 3;
+
+    static final int GROUP_STATUS_IDLE = 0;
+    static final int GROUP_STATUS_STREAMING = 1;
+    static final int GROUP_STATUS_SUSPENDED = 2;
+    static final int GROUP_STATUS_RECONFIGURED = 3;
+    static final int GROUP_STATUS_DESTROYED = 4;
+
+    public int type = EVENT_TYPE_NONE;
+    public BluetoothDevice device;
+    public int valueInt1 = 0;
+    public int valueInt2 = 0;
+    public int valueInt3 = 0;
+    public int valueInt4 = 0;
+
+    LeAudioStackEvent(int type) {
+        this.type = type;
+    }
+
+    @Override
+    public String toString() {
+        // event dump
+        StringBuilder result = new StringBuilder();
+        result.append("LeAudioStackEvent {type:" + eventTypeToString(type));
+        result.append(", device:" + device);
+        result.append(", value1:" + eventTypeValue1ToString(type, valueInt1));
+        result.append(", value2:" + eventTypeValue2ToString(type, valueInt2));
+        result.append(", value3:" + eventTypeValue3ToString(type, valueInt3));
+        result.append(", value4:" + eventTypeValue4ToString(type, valueInt4));
+        result.append("}");
+        return result.toString();
+    }
+
+    private static String eventTypeToString(int type) {
+        switch (type) {
+            case EVENT_TYPE_NONE:
+                return "EVENT_TYPE_NONE";
+            case EVENT_TYPE_CONNECTION_STATE_CHANGED:
+                return "EVENT_TYPE_CONNECTION_STATE_CHANGED";
+            case EVENT_TYPE_GROUP_STATUS_CHANGED:
+                return "EVENT_TYPE_GROUP_STATUS_CHANGED";
+            case EVENT_TYPE_AUDIO_CONF_CHANGED:
+                return "EVENT_TYPE_AUDIO_CONF_CHANGED";
+            case EVENT_TYPE_SET_MEMBER_AVAILABLE:
+                return "EVENT_TYPE_SET_MEMBER_AVAILABLE";
+            default:
+                return "EVENT_TYPE_UNKNOWN:" + type;
+        }
+    }
+
+    private static String eventTypeValue1ToString(int type, int value) {
+        switch (type) {
+            case EVENT_TYPE_CONNECTION_STATE_CHANGED:
+                switch (value) {
+                    case CONNECTION_STATE_DISCONNECTED:
+                        return  "CONNECTION_STATE_DISCONNECTED";
+                    case CONNECTION_STATE_CONNECTING:
+                        return  "CONNECTION_STATE_CONNECTING";
+                    case CONNECTION_STATE_CONNECTED:
+                        return  "CONNECTION_STATE_CONNECTED";
+                    case CONNECTION_STATE_DISCONNECTING:
+                        return  "CONNECTION_STATE_DISCONNECTING";
+                    default:
+                        return "UNKNOWN";
+                }
+            case EVENT_TYPE_GROUP_STATUS_CHANGED:
+                // same as EVENT_TYPE_GROUP_STATUS_CHANGED
+            case EVENT_TYPE_SET_MEMBER_AVAILABLE:
+                // same as EVENT_TYPE_GROUP_STATUS_CHANGED
+            case EVENT_TYPE_AUDIO_CONF_CHANGED:
+                // FIXME: It should have proper direction names here
+                return "{direction:" + value + "}";
+            default:
+                break;
+        }
+        return Integer.toString(value);
+    }
+
+    private static String eventTypeValue2ToString(int type, int value) {
+        switch (type) {
+            case EVENT_TYPE_GROUP_STATUS_CHANGED:
+                switch (value) {
+                    case GROUP_STATUS_IDLE:
+                        return "GROUP_STATUS_IDLE";
+                    case GROUP_STATUS_STREAMING:
+                        return "GROUP_STATUS_STREAMING";
+                    case GROUP_STATUS_SUSPENDED:
+                        return "GROUP_STATUS_SUSPENDED";
+                    case GROUP_STATUS_RECONFIGURED:
+                        return "GROUP_STATUS_RECONFIGURED";
+                    case GROUP_STATUS_DESTROYED:
+                        return "GROUP_STATUS_DESTROYED";
+                    default:
+                        break;
+                }
+                break;
+            case EVENT_TYPE_AUDIO_CONF_CHANGED:
+                return "{group_id:" + Integer.toString(value) + "}";
+            default:
+                break;
+        }
+        return Integer.toString(value);
+    }
+
+    private static String eventTypeValue3ToString(int type, int value) {
+        switch (type) {
+            case EVENT_TYPE_GROUP_STATUS_CHANGED:
+                return "{group_flags:" + Integer.toString(value) + "}";
+            case EVENT_TYPE_AUDIO_CONF_CHANGED:
+                // FIXME: It should have proper location names here
+                return "{snk_audio_loc:" + value + "}";
+            default:
+                break;
+        }
+        return Integer.toString(value);
+    }
+
+    private static String eventTypeValue4ToString(int type, int value) {
+        switch (type) {
+            case EVENT_TYPE_AUDIO_CONF_CHANGED:
+                // FIXME: It should have proper location names here
+                return "{src_audio_loc:" + value + "}";
+            default:
+                break;
+        }
+        return Integer.toString(value);
+    }
+}
diff --git a/src/com/android/bluetooth/le_audio/LeAudioStateMachine.java b/src/com/android/bluetooth/le_audio/LeAudioStateMachine.java
new file mode 100644
index 0000000..19e8c2d
--- /dev/null
+++ b/src/com/android/bluetooth/le_audio/LeAudioStateMachine.java
@@ -0,0 +1,571 @@
+/*
+ * Copyright 2020 HIMSA II K/S - www.himsa.com.
+ * Represented by EHIMA - www.ehima.com
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Bluetooth LeAudio StateMachine. There is one instance per remote device's ASE.
+ *  - "Disconnected" and "Connected" are steady states.
+ *  - "Connecting" and "Disconnecting" are transient states until the
+ *     connection / disconnection is completed.
+ *
+ *
+ *                        (Disconnected)
+ *                           |       ^
+ *                   CONNECT |       | DISCONNECTED
+ *                           V       |
+ *                 (Connecting)<--->(Disconnecting)
+ *                           |       ^
+ *                 CONNECTED |       | DISCONNECT
+ *                           V       |
+ *                          (Connected)
+ * NOTES:
+ *  - If state machine is in "Connecting" state and the remote device sends
+ *    DISCONNECT request, the state machine transitions to "Disconnecting" state.
+ *  - Similarly, if the state machine is in "Disconnecting" state and the remote device
+ *    sends CONNECT request, the state machine transitions to "Connecting" state.
+ *
+ *                    DISCONNECT
+ *    (Connecting) ---------------> (Disconnecting)
+ *                 <---------------
+ *                      CONNECT
+ *
+ */
+
+package com.android.bluetooth.le_audio;
+
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothLeAudio;
+import android.bluetooth.BluetoothProfile;
+import android.content.Intent;
+import android.os.Looper;
+import android.os.Message;
+import android.util.Log;
+import static android.Manifest.permission.BLUETOOTH_CONNECT;
+
+import android.annotation.RequiresPermission;
+
+import com.android.bluetooth.Utils;
+import com.android.bluetooth.btservice.ProfileService;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.State;
+import com.android.internal.util.StateMachine;
+
+final class LeAudioStateMachine extends StateMachine {
+    private static final boolean DBG = false;
+    private static final String TAG = "LeAudioStateMachine";
+
+    static final int CONNECT = 1;
+    static final int DISCONNECT = 2;
+    @VisibleForTesting
+    static final int STACK_EVENT = 101;
+    private static final int CONNECT_TIMEOUT = 201;
+
+    @VisibleForTesting
+    static int sConnectTimeoutMs = 30000;        // 30s
+
+    private Disconnected mDisconnected;
+    private Connecting mConnecting;
+    private Disconnecting mDisconnecting;
+    private Connected mConnected;
+
+    private int mLastConnectionState = -1;
+
+    private LeAudioService mService;
+    private LeAudioNativeInterface mNativeInterface;
+
+    private final BluetoothDevice mDevice;
+
+    LeAudioStateMachine(BluetoothDevice device, LeAudioService svc,
+            LeAudioNativeInterface nativeInterface, Looper looper) {
+        super(TAG, looper);
+        mDevice = device;
+        mService = svc;
+        mNativeInterface = nativeInterface;
+
+        mDisconnected = new Disconnected();
+        mConnecting = new Connecting();
+        mDisconnecting = new Disconnecting();
+        mConnected = new Connected();
+
+        addState(mDisconnected);
+        addState(mConnecting);
+        addState(mDisconnecting);
+        addState(mConnected);
+
+        setInitialState(mDisconnected);
+    }
+
+    static LeAudioStateMachine make(BluetoothDevice device, LeAudioService svc,
+            LeAudioNativeInterface nativeInterface, Looper looper) {
+        Log.i(TAG, "make for device");
+        LeAudioStateMachine LeAudioSm = new LeAudioStateMachine(device, svc, nativeInterface, looper);
+        LeAudioSm.start();
+        return LeAudioSm;
+    }
+
+    public void doQuit() {
+        log("doQuit for device " + mDevice);
+        quitNow();
+    }
+
+    public void cleanup() {
+        log("cleanup for device " + mDevice);
+    }
+
+    @VisibleForTesting
+    class Disconnected extends State {
+        @Override
+        public void enter() {
+            Log.i(TAG, "Enter Disconnected(" + mDevice + "): " + messageWhatToString(
+                    getCurrentMessage().what));
+
+            removeDeferredMessages(DISCONNECT);
+
+            if (mLastConnectionState != -1) {
+                // Don't broadcast during startup
+                broadcastConnectionState(BluetoothProfile.STATE_DISCONNECTED,
+                        mLastConnectionState);
+            }
+        }
+
+        @Override
+        public void exit() {
+            log("Exit Disconnected(" + mDevice + "): " + messageWhatToString(
+                    getCurrentMessage().what));
+            mLastConnectionState = BluetoothProfile.STATE_DISCONNECTED;
+        }
+
+        @Override
+        public boolean processMessage(Message message) {
+            log("Disconnected process message(" + mDevice + "): " + messageWhatToString(
+                    message.what));
+
+            switch (message.what) {
+                case CONNECT:
+                    int groupId = message.arg1;
+                    log("Connecting to " + mDevice + " group " + groupId);
+                    if (!mNativeInterface.connectLeAudio(mDevice)) {
+                        Log.e(TAG, "Disconnected: error connecting to " + mDevice);
+                        break;
+                    }
+                    if (mService.okToConnect(mDevice)) {
+                        transitionTo(mConnecting);
+                    } else {
+                        // Reject the request and stay in Disconnected state
+                        Log.w(TAG, "Outgoing LeAudio Connecting request rejected: " + mDevice);
+                    }
+                    break;
+                case DISCONNECT:
+                    Log.d(TAG, "Disconnected: " + mDevice);
+                    mNativeInterface.disconnectLeAudio(mDevice);
+                    break;
+                case STACK_EVENT:
+                    LeAudioStackEvent event = (LeAudioStackEvent) message.obj;
+                    if (DBG) {
+                        Log.d(TAG, "Disconnected: stack event: " + event);
+                    }
+                    if (!mDevice.equals(event.device)) {
+                        Log.wtfStack(TAG, "Device(" + mDevice + "): event mismatch: " + event);
+                    }
+                    switch (event.type) {
+                        case LeAudioStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED:
+                            processConnectionEvent(event.valueInt1, event.valueInt2);
+                            break;
+                        default:
+                            Log.e(TAG, "Disconnected: ignoring stack event: " + event);
+                            break;
+                    }
+                    break;
+                default:
+                    return NOT_HANDLED;
+            }
+            return HANDLED;
+        }
+
+        // in Disconnected state
+        private void processConnectionEvent(int state, int groupId) {
+            switch (state) {
+                case LeAudioStackEvent.CONNECTION_STATE_DISCONNECTED:
+                    Log.w(TAG, "Ignore LeAudio DISCONNECTED event: " + mDevice);
+                    break;
+                case LeAudioStackEvent.CONNECTION_STATE_CONNECTING:
+                    if (mService.okToConnect(mDevice)) {
+                        Log.i(TAG, "Incoming LeAudio Connecting request accepted: " + mDevice);
+                        transitionTo(mConnecting);
+                    } else {
+                        // Reject the connection and stay in Disconnected state itself
+                        Log.w(TAG, "Incoming LeAudio Connecting request rejected: " + mDevice);
+                        mNativeInterface.disconnectLeAudio(mDevice);
+                    }
+                    break;
+                case LeAudioStackEvent.CONNECTION_STATE_CONNECTED:
+                    Log.w(TAG, "LeAudio Connected from Disconnected state: " + mDevice);
+                    if (mService.okToConnect(mDevice)) {
+                        Log.i(TAG, "Incoming LeAudio Connected request accepted: " + mDevice);
+                        transitionTo(mConnected);
+                    } else {
+                        // Reject the connection and stay in Disconnected state itself
+                        Log.w(TAG, "Incoming LeAudio Connected request rejected: " + mDevice);
+                        mNativeInterface.disconnectLeAudio(mDevice);
+                    }
+                    break;
+                case LeAudioStackEvent.CONNECTION_STATE_DISCONNECTING:
+                    Log.w(TAG, "Ignore LeAudio DISCONNECTING event: " + mDevice);
+                    break;
+                default:
+                    Log.e(TAG, "Incorrect state: " + state + " device: " + mDevice);
+                    break;
+            }
+        }
+    }
+
+    @VisibleForTesting
+    class Connecting extends State {
+        @Override
+        public void enter() {
+            Log.i(TAG, "Enter Connecting(" + mDevice + "): "
+                    + messageWhatToString(getCurrentMessage().what));
+            sendMessageDelayed(CONNECT_TIMEOUT, sConnectTimeoutMs);
+            broadcastConnectionState(BluetoothProfile.STATE_CONNECTING, mLastConnectionState);
+        }
+
+        @Override
+        public void exit() {
+            log("Exit Connecting(" + mDevice + "): "
+                    + messageWhatToString(getCurrentMessage().what));
+            mLastConnectionState = BluetoothProfile.STATE_CONNECTING;
+            removeMessages(CONNECT_TIMEOUT);
+        }
+
+        @Override
+        public boolean processMessage(Message message) {
+            log("Connecting process message(" + mDevice + "): "
+                    + messageWhatToString(message.what));
+
+            switch (message.what) {
+                case CONNECT:
+                    deferMessage(message);
+                    break;
+                case CONNECT_TIMEOUT:
+                    Log.w(TAG, "Connecting connection timeout: " + mDevice);
+                    mNativeInterface.disconnectLeAudio(mDevice);
+                    LeAudioStackEvent disconnectEvent =
+                            new LeAudioStackEvent(
+                                    LeAudioStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED);
+                    disconnectEvent.device = mDevice;
+                    disconnectEvent.valueInt1 = LeAudioStackEvent.CONNECTION_STATE_DISCONNECTED;
+                    sendMessage(STACK_EVENT, disconnectEvent);
+                    break;
+                case DISCONNECT:
+                    log("Connecting: connection canceled to " + mDevice);
+                    mNativeInterface.disconnectLeAudio(mDevice);
+                    transitionTo(mDisconnected);
+                    break;
+                case STACK_EVENT:
+                    LeAudioStackEvent event = (LeAudioStackEvent) message.obj;
+                    log("Connecting: stack event: " + event);
+                    if (!mDevice.equals(event.device)) {
+                        Log.wtfStack(TAG, "Device(" + mDevice + "): event mismatch: " + event);
+                    }
+                    switch (event.type) {
+                        case LeAudioStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED:
+                            processConnectionEvent(event.valueInt1, event.valueInt2);
+                            break;
+                        default:
+                            Log.e(TAG, "Connecting: ignoring stack event: " + event);
+                            break;
+                    }
+                    break;
+                default:
+                    return NOT_HANDLED;
+            }
+            return HANDLED;
+        }
+
+        // in Connecting state
+        private void processConnectionEvent(int state, int groupId) {
+            switch (state) {
+                case LeAudioStackEvent.CONNECTION_STATE_DISCONNECTED:
+                    Log.w(TAG, "Connecting device disconnected: " + mDevice);
+                    transitionTo(mDisconnected);
+                    break;
+                case LeAudioStackEvent.CONNECTION_STATE_CONNECTED:
+                    transitionTo(mConnected);
+                    break;
+                case LeAudioStackEvent.CONNECTION_STATE_CONNECTING:
+                    break;
+                case LeAudioStackEvent.CONNECTION_STATE_DISCONNECTING:
+                    Log.w(TAG, "Connecting interrupted: device is disconnecting: " + mDevice);
+                    transitionTo(mDisconnecting);
+                    break;
+                default:
+                    Log.e(TAG, "Incorrect state: " + state);
+                    break;
+            }
+        }
+    }
+
+    @VisibleForTesting
+    class Disconnecting extends State {
+        @Override
+        public void enter() {
+            Log.i(TAG, "Enter Disconnecting(" + mDevice + "): "
+                    + messageWhatToString(getCurrentMessage().what));
+            sendMessageDelayed(CONNECT_TIMEOUT, sConnectTimeoutMs);
+            broadcastConnectionState(BluetoothProfile.STATE_DISCONNECTING, mLastConnectionState);
+        }
+
+        @Override
+        public void exit() {
+            log("Exit Disconnecting(" + mDevice + "): "
+                    + messageWhatToString(getCurrentMessage().what));
+            mLastConnectionState = BluetoothProfile.STATE_DISCONNECTING;
+            removeMessages(CONNECT_TIMEOUT);
+        }
+
+        @Override
+        public boolean processMessage(Message message) {
+            log("Disconnecting process message(" + mDevice + "): "
+                    + messageWhatToString(message.what));
+
+            switch (message.what) {
+                case CONNECT:
+                    deferMessage(message);
+                    break;
+                case CONNECT_TIMEOUT: {
+                    Log.w(TAG, "Disconnecting connection timeout: " + mDevice);
+                    mNativeInterface.disconnectLeAudio(mDevice);
+                    LeAudioStackEvent disconnectEvent =
+                            new LeAudioStackEvent(
+                                    LeAudioStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED);
+                    disconnectEvent.device = mDevice;
+                    disconnectEvent.valueInt1 = LeAudioStackEvent.CONNECTION_STATE_DISCONNECTED;
+                    sendMessage(STACK_EVENT, disconnectEvent);
+                    break;
+                }
+                case DISCONNECT:
+                    deferMessage(message);
+                    break;
+                case STACK_EVENT:
+                    LeAudioStackEvent event = (LeAudioStackEvent) message.obj;
+                    log("Disconnecting: stack event: " + event);
+                    if (!mDevice.equals(event.device)) {
+                        Log.wtfStack(TAG, "Device(" + mDevice + "): event mismatch: " + event);
+                    }
+                    switch (event.type) {
+                        case LeAudioStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED:
+                            processConnectionEvent(event.valueInt1, event.valueInt2);
+                            break;
+                        default:
+                            Log.e(TAG, "Disconnecting: ignoring stack event: " + event);
+                            break;
+                    }
+                    break;
+                default:
+                    return NOT_HANDLED;
+            }
+            return HANDLED;
+        }
+
+        // in Disconnecting state
+        private void processConnectionEvent(int state, int groupId) {
+            switch (state) {
+                case LeAudioStackEvent.CONNECTION_STATE_DISCONNECTED:
+                    Log.i(TAG, "Disconnected: " + mDevice);
+                    transitionTo(mDisconnected);
+                    break;
+                case LeAudioStackEvent.CONNECTION_STATE_CONNECTED:
+                    if (mService.okToConnect(mDevice)) {
+                        Log.w(TAG, "Disconnecting interrupted: device is connected: " + mDevice);
+                        transitionTo(mConnected);
+                    } else {
+                        // Reject the connection and stay in Disconnecting state
+                        Log.w(TAG, "Incoming LeAudio Connected request rejected: " + mDevice);
+                        mNativeInterface.disconnectLeAudio(mDevice);
+                    }
+                    break;
+                case LeAudioStackEvent.CONNECTION_STATE_CONNECTING:
+                    if (mService.okToConnect(mDevice)) {
+                        Log.i(TAG, "Disconnecting interrupted: try to reconnect: " + mDevice);
+                        transitionTo(mConnecting);
+                    } else {
+                        // Reject the connection and stay in Disconnecting state
+                        Log.w(TAG, "Incoming LeAudio Connecting request rejected: " + mDevice);
+                        mNativeInterface.disconnectLeAudio(mDevice);
+                    }
+                    break;
+                case LeAudioStackEvent.CONNECTION_STATE_DISCONNECTING:
+                    break;
+                default:
+                    Log.e(TAG, "Incorrect state: " + state);
+                    break;
+            }
+        }
+    }
+
+    @VisibleForTesting
+    class Connected extends State {
+        @Override
+        public void enter() {
+            Log.i(TAG, "Enter Connected(" + mDevice + "): "
+                    + messageWhatToString(getCurrentMessage().what));
+            removeDeferredMessages(CONNECT);
+            broadcastConnectionState(BluetoothProfile.STATE_CONNECTED, mLastConnectionState);
+        }
+
+        @Override
+        public void exit() {
+            log("Exit Connected(" + mDevice + "): "
+                    + messageWhatToString(getCurrentMessage().what));
+            mLastConnectionState = BluetoothProfile.STATE_CONNECTED;
+        }
+
+        @Override
+        public boolean processMessage(Message message) {
+            log("Connected process message(" + mDevice + "): "
+                    + messageWhatToString(message.what));
+
+            switch (message.what) {
+                case CONNECT:
+                    Log.w(TAG, "Connected: CONNECT ignored: " + mDevice);
+                    break;
+                case DISCONNECT:
+                    log("Disconnecting from " + mDevice);
+                    if (!mNativeInterface.disconnectLeAudio(mDevice)) {
+                        // If error in the native stack, transition directly to Disconnected state.
+                        Log.e(TAG, "Connected: error disconnecting from " + mDevice);
+                        transitionTo(mDisconnected);
+                        break;
+                    }
+                    transitionTo(mDisconnecting);
+                    break;
+                case STACK_EVENT:
+                    LeAudioStackEvent event = (LeAudioStackEvent) message.obj;
+                    log("Connected: stack event: " + event);
+                    if (!mDevice.equals(event.device)) {
+                        Log.wtfStack(TAG, "Device(" + mDevice + "): event mismatch: " + event);
+                    }
+                    switch (event.type) {
+                        case LeAudioStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED:
+                            processConnectionEvent(event.valueInt1, event.valueInt2);
+                            break;
+                        default:
+                            Log.e(TAG, "Connected: ignoring stack event: " + event);
+                            break;
+                    }
+                    break;
+                default:
+                    return NOT_HANDLED;
+            }
+            return HANDLED;
+        }
+
+        // in Connected state
+        private void processConnectionEvent(int state, int groupId) {
+            switch (state) {
+                case LeAudioStackEvent.CONNECTION_STATE_DISCONNECTED:
+                    Log.i(TAG, "Disconnected from " + mDevice);
+                    transitionTo(mDisconnected);
+                    break;
+                case LeAudioStackEvent.CONNECTION_STATE_DISCONNECTING:
+                    Log.i(TAG, "Disconnecting from " + mDevice);
+                    transitionTo(mDisconnecting);
+                    break;
+                default:
+                    Log.e(TAG, "Connection State Device: " + mDevice + " bad state: " + state);
+                    break;
+            }
+        }
+    }
+
+    int getConnectionState() {
+        String currentState = getCurrentState().getName();
+        switch (currentState) {
+            case "Disconnected":
+                return BluetoothProfile.STATE_DISCONNECTED;
+            case "Connecting":
+                return BluetoothProfile.STATE_CONNECTING;
+            case "Connected":
+                return BluetoothProfile.STATE_CONNECTED;
+            case "Disconnecting":
+                return BluetoothProfile.STATE_DISCONNECTING;
+            default:
+                Log.e(TAG, "Bad currentState: " + currentState);
+                return BluetoothProfile.STATE_DISCONNECTED;
+        }
+    }
+
+    BluetoothDevice getDevice() {
+        return mDevice;
+    }
+
+    synchronized boolean isConnected() {
+        return getCurrentState() == mConnected;
+    }
+
+    // This method does not check for error condition (newState == prevState)
+    private void broadcastConnectionState(int newState, int prevState) {
+        log("Connection state " + mDevice + ": " + profileStateToString(prevState)
+                    + "->" + profileStateToString(newState));
+
+        Intent intent = new Intent(BluetoothLeAudio.ACTION_LE_AUDIO_CONNECTION_STATE_CHANGED);
+        intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, prevState);
+        intent.putExtra(BluetoothProfile.EXTRA_STATE, newState);
+        intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mDevice);
+        intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT
+                        | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
+        mService.sendBroadcast(intent, BLUETOOTH_CONNECT, Utils.getTempAllowlistBroadcastOptions());
+    }
+
+    private static String messageWhatToString(int what) {
+        switch (what) {
+            case CONNECT:
+                return "CONNECT";
+            case DISCONNECT:
+                return "DISCONNECT";
+            case STACK_EVENT:
+                return "STACK_EVENT";
+            case CONNECT_TIMEOUT:
+                return "CONNECT_TIMEOUT";
+            default:
+                break;
+        }
+        return Integer.toString(what);
+    }
+
+    private static String profileStateToString(int state) {
+        switch (state) {
+            case BluetoothProfile.STATE_DISCONNECTED:
+                return "DISCONNECTED";
+            case BluetoothProfile.STATE_CONNECTING:
+                return "CONNECTING";
+            case BluetoothProfile.STATE_CONNECTED:
+                return "CONNECTED";
+            case BluetoothProfile.STATE_DISCONNECTING:
+                return "DISCONNECTING";
+            default:
+                break;
+        }
+        return Integer.toString(state);
+    }
+
+    @Override
+    protected void log(String msg) {
+        if (DBG) {
+            super.log(msg);
+        }
+    }
+}
diff --git a/src/com/android/bluetooth/map/BluetoothMapContent.java b/src/com/android/bluetooth/map/BluetoothMapContent.java
index 09894e8..3b525d1 100644
--- a/src/com/android/bluetooth/map/BluetoothMapContent.java
+++ b/src/com/android/bluetooth/map/BluetoothMapContent.java
@@ -3810,8 +3810,6 @@
      * @param message the bMessage object to add the information to
      */
     private void extractMmsParts(long id, BluetoothMapbMessageMime message) {
-        /* Handling of filtering out non-text parts for exclude
-         * attachments is handled within the bMessage object. */
         final String[] projection = null;
         String selection = new String(Mms.Part.MSG_ID + "=" + id);
         String uriStr = new String(Mms.CONTENT_URI + "/" + id + "/part");
@@ -3847,6 +3845,38 @@
                     part.mContentLocation = cl;
                     part.mContentDisposition = cdisp;
 
+                    // Filtering out non-text parts (e.g., an image) when attachments are to be
+                    // excluded is currently handled within the "message" object's encoding
+                    // function (c.f., BluetoothMapbMessageMime.encodeMime()), where the
+                    // attachment is replaced with a text string containing the part name or
+                    // filename.
+                    // However, replacing with text during encoding is too late, as charset
+                    // information does not get properly set and propagated. For example, if a MMS
+                    // consists only of a GIF, it's mimetype is "image/gif" and not "text", so
+                    // according to spec, "charset" should not be set. However, if the attachment
+                    // is replaced with a text string, the bMessage now contains text and should
+                    // have charset set to UTF-8 according to spec.
+                    if (!part.mContentType.toUpperCase().contains("TEXT")
+                            && !message.getIncludeAttachments()) {
+                        StringBuilder sb = new StringBuilder();
+                        try {
+                            part.encodePlainText(sb);
+                            // Each time {@code encodePlainText} is called, it adds {@code "\r\n"}
+                            // to the string. {@code encodePlainText} is called here to replace
+                            // an image with a string, but later on, when we encode the entire
+                            // bMessage in {@link BluetoothMapbMessageMime#encode()},
+                            // {@code encodePlainText} will be called again on this {@code
+                            // MimePart} (as text this time), adding a second {@code "\r\n"}. So
+                            // we remove the extra newline from the end.
+                            int newlineIndex = sb.lastIndexOf("\r\n");
+                            if (newlineIndex != -1) sb.delete(newlineIndex, newlineIndex + 4);
+                            text = sb.toString();
+                            part.mContentType = "text";
+                        } catch (UnsupportedEncodingException e) {
+                            Log.d(TAG, "extractMmsParts", e);
+                        }
+                    }
+
                     try {
                         if (text != null) {
                             part.mData = text.getBytes("UTF-8");
diff --git a/src/com/android/bluetooth/map/BluetoothMapContentObserver.java b/src/com/android/bluetooth/map/BluetoothMapContentObserver.java
index 2c900af..8451373 100644
--- a/src/com/android/bluetooth/map/BluetoothMapContentObserver.java
+++ b/src/com/android/bluetooth/map/BluetoothMapContentObserver.java
@@ -2858,8 +2858,11 @@
                 sentIntent.putExtra(EXTRA_MESSAGE_SENT_TRANSPARENT, transparent);
                 sentIntent.putExtra(EXTRA_MESSAGE_SENT_RETRY, retry);
                 //sentIntent.setDataAndNormalize(btMmsUri);
+                // TODO(b/171825892) Please replace FLAG_MUTABLE_UNAUDITED below
+                // with either FLAG_IMMUTABLE (recommended) or FLAG_MUTABLE.
                 PendingIntent pendingSendIntent =
-                        PendingIntent.getBroadcast(mContext, 0, sentIntent, 0);
+                        PendingIntent.getBroadcast(mContext, 0, sentIntent,
+                                PendingIntent.FLAG_IMMUTABLE);
                 SmsManager.getDefault()
                         .sendMultimediaMessage(mContext, btMmsUri, null/*locationUrl*/,
                                 null/*configOverrides*/,
@@ -3112,14 +3115,15 @@
 
         values.clear();
         values.put(Mms.Addr.CONTACT_ID, "null");
-        values.put(Mms.Addr.ADDRESS, String.join(",", toAddress));
         values.put(Mms.Addr.TYPE, BluetoothMapContent.MMS_TO);
         values.put(Mms.Addr.CHARSET, 106);
-
-        uri = Uri.parse(Mms.CONTENT_URI + "/" + handle + "/addr");
-        uri = mResolver.insert(uri, values);
-        if (uri != null && V) {
-            Log.v(TAG, " NEW URI " + uri.toString());
+        for (String address : toAddress) {
+            values.put(Mms.Addr.ADDRESS, address);
+            uri = Uri.parse(Mms.CONTENT_URI + "/" + handle + "/addr");
+            uri = mResolver.insert(uri, values);
+            if (uri != null && V) {
+                Log.v(TAG, " NEW URI " + uri.toString());
+            }
         }
         return handle;
     }
@@ -3206,10 +3210,11 @@
                         "message/" + Long.toString(msgInfo.id) + msgInfo.timestamp + i);
                 intentDelivery.putExtra(EXTRA_MESSAGE_SENT_HANDLE, msgInfo.id);
                 intentDelivery.putExtra(EXTRA_MESSAGE_SENT_TIMESTAMP, msgInfo.timestamp);
+                // TODO(b/171825892) Please replace FLAG_MUTABLE_UNAUDITED below
+                // with either FLAG_IMMUTABLE (recommended) or FLAG_MUTABLE.
                 PendingIntent pendingIntentDelivery =
                         PendingIntent.getBroadcast(mContext, 0, intentDelivery,
-                                PendingIntent.FLAG_UPDATE_CURRENT);
-
+                                PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
                 intentSent = new Intent(ACTION_MESSAGE_SENT, null);
                 /* Add msgId and part number to ensure the intents are different, and we
                  * thereby get an intent for each msg part.
@@ -3221,10 +3226,11 @@
                 intentSent.putExtra(EXTRA_MESSAGE_SENT_RETRY, msgInfo.retry);
                 intentSent.putExtra(EXTRA_MESSAGE_SENT_TRANSPARENT, msgInfo.transparent);
 
+                // TODO(b/171825892) Please replace FLAG_MUTABLE_UNAUDITED below
+                // with either FLAG_IMMUTABLE (recommended) or FLAG_MUTABLE.
                 PendingIntent pendingIntentSent =
                         PendingIntent.getBroadcast(mContext, 0, intentSent,
-                                PendingIntent.FLAG_UPDATE_CURRENT);
-
+                                PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
                 // We use the same pending intent for all parts, but do not set the one shot flag.
                 deliveryIntents.add(pendingIntentDelivery);
                 sentIntents.add(pendingIntentSent);
@@ -3544,9 +3550,8 @@
 
     public static void actionSmsSentDisconnected(Context context, Intent intent, int result) {
         /* Check permission for message deletion. */
-        if ((Binder.getCallingPid() != Process.myPid()) || (
-                context.checkCallingOrSelfPermission("android.Manifest.permission.WRITE_SMS")
-                        != PackageManager.PERMISSION_GRANTED)) {
+        if ((Binder.getCallingPid() != Process.myPid())
+                || !Utils.checkCallerHasWriteSmsPermission(context)) {
             Log.w(TAG, "actionSmsSentDisconnected: Not allowed to delete SMS/MMS messages");
             return;
         }
diff --git a/src/com/android/bluetooth/map/BluetoothMapMasInstance.java b/src/com/android/bluetooth/map/BluetoothMapMasInstance.java
index 4755dfd..c2b75a5 100644
--- a/src/com/android/bluetooth/map/BluetoothMapMasInstance.java
+++ b/src/com/android/bluetooth/map/BluetoothMapMasInstance.java
@@ -106,7 +106,7 @@
             new HashMap<Long, BluetoothMapConvoListingElement>();
 
     private int mRemoteFeatureMask = BluetoothMapUtils.MAP_FEATURE_DEFAULT_BITMASK;
-    private static int sFeatureMask = SDP_MAP_MAS_FEATURES_1_2;
+    private static int sFeatureMask = SDP_MAP_MAS_FEATURES_1_4;
 
     public static final String TYPE_SMS_MMS_STR = "SMS/MMS";
     public static final String TYPE_EMAIL_STR = "EMAIL";
@@ -347,7 +347,7 @@
             }
         }
 
-        final String currentValue = SystemProperties.get(BLUETOOTH_MAP_VERSION_PROPERTY, "map12");
+        final String currentValue = SystemProperties.get(BLUETOOTH_MAP_VERSION_PROPERTY, "map14");
         int masVersion;
 
         switch (currentValue) {
@@ -364,8 +364,8 @@
                 sFeatureMask = SDP_MAP_MAS_FEATURES_1_4;
                 break;
             default:
-                masVersion = SDP_MAP_MAS_VERSION_1_2;
-                sFeatureMask = SDP_MAP_MAS_FEATURES_1_2;
+                masVersion = SDP_MAP_MAS_VERSION_1_4;
+                sFeatureMask = SDP_MAP_MAS_FEATURES_1_4;
         }
 
         return SdpManager.getDefaultManager()
diff --git a/src/com/android/bluetooth/map/BluetoothMapService.java b/src/com/android/bluetooth/map/BluetoothMapService.java
index 6e321a9..a2141b4 100644
--- a/src/com/android/bluetooth/map/BluetoothMapService.java
+++ b/src/com/android/bluetooth/map/BluetoothMapService.java
@@ -15,8 +15,13 @@
 
 package com.android.bluetooth.map;
 
+import static android.Manifest.permission.BLUETOOTH_CONNECT;
+
 import static com.android.bluetooth.Utils.enforceBluetoothPrivilegedPermission;
 
+import android.annotation.RequiresPermission;
+import android.app.Activity;
+import android.app.ActivityThread;
 import android.app.AlarmManager;
 import android.app.PendingIntent;
 import android.bluetooth.BluetoothAdapter;
@@ -26,6 +31,8 @@
 import android.bluetooth.BluetoothUuid;
 import android.bluetooth.IBluetoothMap;
 import android.bluetooth.SdpMnsRecord;
+import android.content.Attributable;
+import android.content.AttributionSource;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
@@ -49,12 +56,14 @@
 import com.android.bluetooth.btservice.AdapterService;
 import com.android.bluetooth.btservice.MetricsLogger;
 import com.android.bluetooth.btservice.ProfileService;
+import com.android.bluetooth.btservice.storage.DatabaseManager;
 import com.android.internal.annotations.VisibleForTesting;
 
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
+import java.util.Objects;
 import java.util.Set;
 
 public class BluetoothMapService extends ProfileService {
@@ -93,9 +102,6 @@
     static final int MSG_MNS_SDP_SEARCH = 5007;
     static final int MSG_OBSERVER_REGISTRATION = 5008;
 
-    private static final String BLUETOOTH_PERM = android.Manifest.permission.BLUETOOTH;
-    private static final String BLUETOOTH_ADMIN_PERM = android.Manifest.permission.BLUETOOTH_ADMIN;
-
     private static final int START_LISTENER = 1;
     private static final int USER_TIMEOUT = 2;
     private static final int DISCONNECT_MAP = 3;
@@ -112,7 +118,8 @@
 
     private static final int MAS_ID_SMS_MMS = 0;
 
-    private BluetoothAdapter mAdapter;
+    private AdapterService mAdapterService;
+    private DatabaseManager mDatabaseManager;
 
     private BluetoothMnsObexClient mBluetoothMnsObexClient = null;
 
@@ -370,7 +377,8 @@
                         intent.putExtra(BluetoothDevice.EXTRA_DEVICE, sRemoteDevice);
                         intent.putExtra(BluetoothDevice.EXTRA_ACCESS_REQUEST_TYPE,
                                 BluetoothDevice.REQUEST_TYPE_MESSAGE_ACCESS);
-                        sendBroadcast(intent);
+                        sendBroadcast(intent, BLUETOOTH_CONNECT,
+                                Utils.getTempAllowlistBroadcastOptions());
                         cancelUserTimeoutAlarm();
                         mIsWaitingAuthorization = false;
                         stopObexServerSessions(-1);
@@ -385,7 +393,10 @@
                     // handled elsewhere
                     break;
                 case DISCONNECT_MAP:
-                    disconnectMap((BluetoothDevice) msg.obj);
+                    BluetoothDevice device = (BluetoothDevice) msg.obj;
+                    Attributable.setAttributionSource(device,
+                            ActivityThread.currentAttributionSource());
+                    disconnectMap(device);
                     break;
                 case SHUTDOWN:
                     // Call close from this handler to avoid starting because of pending messages
@@ -508,7 +519,7 @@
             intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, prevState);
             intent.putExtra(BluetoothProfile.EXTRA_STATE, mState);
             intent.putExtra(BluetoothDevice.EXTRA_DEVICE, sRemoteDevice);
-            sendBroadcast(intent, BLUETOOTH_PERM);
+            sendBroadcast(intent, BLUETOOTH_CONNECT, Utils.getTempAllowlistBroadcastOptions());
         }
     }
 
@@ -550,7 +561,7 @@
 
     List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
         List<BluetoothDevice> deviceList = new ArrayList<>();
-        Set<BluetoothDevice> bondedDevices = mAdapter.getBondedDevices();
+        BluetoothDevice[] bondedDevices = mAdapterService.getBondedDevices();
         if (bondedDevices == null) {
             return deviceList;
         }
@@ -588,14 +599,35 @@
         }
     }
 
+    /**
+     * Set connection policy of the profile and tries to disconnect 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
+     */
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED)
     boolean setConnectionPolicy(BluetoothDevice device, int connectionPolicy) {
         enforceCallingOrSelfPermission(BLUETOOTH_PRIVILEGED,
                 "Need BLUETOOTH_PRIVILEGED permission");
         if (VERBOSE) {
             Log.v(TAG, "Saved connectionPolicy " + device + " = " + connectionPolicy);
         }
-        AdapterService.getAdapterService().getDatabase()
-                .setProfileConnectionPolicy(device, BluetoothProfile.MAP, connectionPolicy);
+
+        if (!mDatabaseManager.setProfileConnectionPolicy(device, BluetoothProfile.MAP,
+                  connectionPolicy)) {
+            return false;
+        }
+        if (connectionPolicy == BluetoothProfile.CONNECTION_POLICY_FORBIDDEN) {
+            disconnect(device);
+        }
         return true;
     }
 
@@ -611,10 +643,11 @@
      * @return connection policy of the device
      * @hide
      */
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED)
     int getConnectionPolicy(BluetoothDevice device) {
         enforceCallingOrSelfPermission(BLUETOOTH_PRIVILEGED,
                 "Need BLUETOOTH_PRIVILEGED permission");
-        return AdapterService.getAdapterService().getDatabase()
+        return mDatabaseManager
                 .getProfileConnectionPolicy(device, BluetoothProfile.MAP);
     }
 
@@ -628,6 +661,10 @@
         if (DEBUG) {
             Log.d(TAG, "start()");
         }
+
+        mDatabaseManager = Objects.requireNonNull(AdapterService.getAdapterService().getDatabase(),
+                "DatabaseManager cannot be null when MapService starts");
+
         HandlerThread thread = new HandlerThread("BluetoothMapHandler");
         thread.start();
         Looper looper = thread.getLooper();
@@ -653,7 +690,7 @@
             registerReceiver(mMapReceiver, filterMessageSent);
             mRegisteredMapReceiver = true;
         }
-        mAdapter = BluetoothAdapter.getDefaultAdapter();
+        mAdapterService = AdapterService.getAdapterService();
         mAppObserver = new BluetoothMapAppObserver(this, this);
 
         TelephonyManager tm = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE);
@@ -762,7 +799,7 @@
             mMasInstances.append(masId, newInst);
             mMasInstanceMap.put(account, newInst);
             // Start the new instance
-            if (mAdapter.isEnabled()) {
+            if (mAdapterService.isEnabled()) {
                 newInst.startSocketListeners();
             }
         }
@@ -866,7 +903,7 @@
         synchronized (this) {
             if (sRemoteDevice == null) {
                 sRemoteDevice = remoteDevice;
-                sRemoteDeviceName = sRemoteDevice.getName();
+                sRemoteDeviceName = Utils.getName(sRemoteDevice);
                 // In case getRemoteName failed and return null
                 if (TextUtils.isEmpty(sRemoteDeviceName)) {
                     sRemoteDeviceName = getString(R.string.defaultname);
@@ -885,7 +922,7 @@
                 }
             } else if (!sRemoteDevice.equals(remoteDevice)) {
                 Log.w(TAG, "Unexpected connection from a second Remote Device received. name: " + (
-                        (remoteDevice == null) ? "unknown" : remoteDevice.getName()));
+                        (remoteDevice == null) ? "unknown" : Utils.getName(remoteDevice)));
                 return false;
             } // Else second connection to same device, just continue
         }
@@ -897,7 +934,9 @@
             intent.putExtra(BluetoothDevice.EXTRA_ACCESS_REQUEST_TYPE,
                     BluetoothDevice.REQUEST_TYPE_MESSAGE_ACCESS);
             intent.putExtra(BluetoothDevice.EXTRA_DEVICE, sRemoteDevice);
-            sendOrderedBroadcast(intent, BLUETOOTH_ADMIN_PERM);
+            sendOrderedBroadcast(intent, BLUETOOTH_CONNECT,
+                    Utils.getTempAllowlistBroadcastOptions(), null, null,
+                    Activity.RESULT_OK, null, null);
 
             if (VERBOSE) {
                 Log.v(TAG, "waiting for authorization for connection from: " + sRemoteDeviceName);
@@ -923,7 +962,8 @@
         }
         mRemoveTimeoutMsg = true;
         Intent timeoutIntent = new Intent(USER_CONFIRM_TIMEOUT_ACTION);
-        PendingIntent pIntent = PendingIntent.getBroadcast(this, 0, timeoutIntent, 0);
+        PendingIntent pIntent = PendingIntent.getBroadcast(this, 0, timeoutIntent,
+                PendingIntent.FLAG_IMMUTABLE);
         mAlarmManager.set(AlarmManager.RTC_WAKEUP,
                 System.currentTimeMillis() + USER_CONFIRM_TIMEOUT_VALUE, pIntent);
     }
@@ -933,7 +973,8 @@
             Log.d(TAG, "cancelUserTimeOutAlarm()");
         }
         Intent timeoutIntent = new Intent(USER_CONFIRM_TIMEOUT_ACTION);
-        PendingIntent pIntent = PendingIntent.getBroadcast(this, 0, timeoutIntent, 0);
+        PendingIntent pIntent = PendingIntent.getBroadcast(this, 0, timeoutIntent,
+                PendingIntent.FLAG_IMMUTABLE);
         pIntent.cancel();
 
         AlarmManager alarmManager = (AlarmManager) this.getSystemService(Context.ALARM_SERVICE);
@@ -990,7 +1031,7 @@
         // Pending messages are no longer valid. To speed up things, simply delete them.
         if (mRemoveTimeoutMsg) {
             Intent timeoutIntent = new Intent(USER_CONFIRM_TIMEOUT_ACTION);
-            sendBroadcast(timeoutIntent, BLUETOOTH_PERM);
+            sendBroadcast(timeoutIntent, null, Utils.getTempAllowlistBroadcastOptions());
             mIsWaitingAuthorization = false;
             cancelUserTimeoutAlarm();
         }
@@ -1164,18 +1205,14 @@
             implements IProfileServiceBinder {
         private BluetoothMapService mService;
 
-        private BluetoothMapService getService() {
-            if (!Utils.checkCaller()) {
-                Log.w(TAG, "MAP call not allowed for non-active user");
+        @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
+        private BluetoothMapService getService(AttributionSource source) {
+            if (!Utils.checkCallerIsSystemOrActiveUser(TAG)
+                    || !Utils.checkServiceAvailable(mService, TAG)
+                    || !Utils.checkConnectPermissionForDataDelivery(mService, source, TAG)) {
                 return null;
             }
-
-            if (mService != null && mService.isAvailable()) {
-                mService.enforceCallingOrSelfPermission(BLUETOOTH_PERM,
-                        "Need BLUETOOTH permission");
-                return mService;
-            }
-            return null;
+            return mService;
         }
 
         BluetoothMapBinder(BluetoothMapService service) {
@@ -1191,23 +1228,23 @@
         }
 
         @Override
-        public int getState() {
+        public int getState(AttributionSource source) {
             if (VERBOSE) {
                 Log.v(TAG, "getState()");
             }
-            BluetoothMapService service = getService();
+            BluetoothMapService service = getService(source);
             if (service == null) {
                 return BluetoothMap.STATE_DISCONNECTED;
             }
-            return getService().getState();
+            return service.getState();
         }
 
         @Override
-        public BluetoothDevice getClient() {
+        public BluetoothDevice getClient(AttributionSource source) {
             if (VERBOSE) {
                 Log.v(TAG, "getClient()");
             }
-            BluetoothMapService service = getService();
+            BluetoothMapService service = getService(source);
             if (service == null) {
                 return null;
             }
@@ -1219,21 +1256,23 @@
         }
 
         @Override
-        public boolean isConnected(BluetoothDevice device) {
+        public boolean isConnected(BluetoothDevice device, AttributionSource source) {
             if (VERBOSE) {
                 Log.v(TAG, "isConnected()");
             }
-            BluetoothMapService service = getService();
+            Attributable.setAttributionSource(device, source);
+            BluetoothMapService service = getService(source);
             return service != null && service.getState() == BluetoothMap.STATE_CONNECTED
                     && BluetoothMapService.getRemoteDevice().equals(device);
         }
 
         @Override
-        public boolean disconnect(BluetoothDevice device) {
+        public boolean disconnect(BluetoothDevice device, AttributionSource source) {
             if (VERBOSE) {
                 Log.v(TAG, "disconnect()");
             }
-            BluetoothMapService service = getService();
+            Attributable.setAttributionSource(device, source);
+            BluetoothMapService service = getService(source);
             if (service == null) {
                 return false;
             }
@@ -1242,11 +1281,11 @@
         }
 
         @Override
-        public List<BluetoothDevice> getConnectedDevices() {
+        public List<BluetoothDevice> getConnectedDevices(AttributionSource source) {
             if (VERBOSE) {
                 Log.v(TAG, "getConnectedDevices()");
             }
-            BluetoothMapService service = getService();
+            BluetoothMapService service = getService(source);
             if (service == null) {
                 return new ArrayList<>(0);
             }
@@ -1255,11 +1294,12 @@
         }
 
         @Override
-        public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
+        public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states,
+                AttributionSource source) {
             if (VERBOSE) {
                 Log.v(TAG, "getDevicesMatchingConnectionStates()");
             }
-            BluetoothMapService service = getService();
+            BluetoothMapService service = getService(source);
             if (service == null) {
                 return new ArrayList<>(0);
             }
@@ -1267,11 +1307,12 @@
         }
 
         @Override
-        public int getConnectionState(BluetoothDevice device) {
+        public int getConnectionState(BluetoothDevice device, AttributionSource source) {
             if (VERBOSE) {
                 Log.v(TAG, "getConnectionState()");
             }
-            BluetoothMapService service = getService();
+            Attributable.setAttributionSource(device, source);
+            BluetoothMapService service = getService(source);
             if (service == null) {
                 return BluetoothProfile.STATE_DISCONNECTED;
             }
@@ -1279,8 +1320,10 @@
         }
 
         @Override
-        public boolean setConnectionPolicy(BluetoothDevice device, int connectionPolicy) {
-            BluetoothMapService service = getService();
+        public boolean setConnectionPolicy(BluetoothDevice device, int connectionPolicy,
+                AttributionSource source) {
+            Attributable.setAttributionSource(device, source);
+            BluetoothMapService service = getService(source);
             if (service == null) {
                 return false;
             }
@@ -1288,8 +1331,9 @@
         }
 
         @Override
-        public int getConnectionPolicy(BluetoothDevice device) {
-            BluetoothMapService service = getService();
+        public int getConnectionPolicy(BluetoothDevice device, AttributionSource source) {
+            Attributable.setAttributionSource(device, source);
+            BluetoothMapService service = getService(source);
             if (service == null) {
                 return BluetoothProfile.CONNECTION_POLICY_UNKNOWN;
             }
diff --git a/src/com/android/bluetooth/map/BluetoothMapbMessageMime.java b/src/com/android/bluetooth/map/BluetoothMapbMessageMime.java
index 2dbdd1d..1d466c5 100644
--- a/src/com/android/bluetooth/map/BluetoothMapbMessageMime.java
+++ b/src/com/android/bluetooth/map/BluetoothMapbMessageMime.java
@@ -46,7 +46,7 @@
                                             * jpeg data or the text.getBytes("utf-8") */
 
 
-        String getDataAsString() {
+        public String getDataAsString() {
             String result = null;
             String charset = mCharsetName;
             // Figure out if we support the charset, else fall back to UTF-8, as this is what
diff --git a/src/com/android/bluetooth/mapclient/MapClientContent.java b/src/com/android/bluetooth/mapclient/MapClientContent.java
new file mode 100644
index 0000000..af06803
--- /dev/null
+++ b/src/com/android/bluetooth/mapclient/MapClientContent.java
@@ -0,0 +1,580 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.bluetooth.mapclient;
+
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothMapClient;
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.content.Context;
+import android.database.ContentObserver;
+import android.database.Cursor;
+import android.net.Uri;
+import android.provider.Telephony;
+import android.provider.Telephony.Mms;
+import android.provider.Telephony.MmsSms;
+import android.provider.Telephony.Sms;
+import android.provider.Telephony.Threads;
+import android.telephony.PhoneNumberUtils;
+import android.telephony.SubscriptionInfo;
+import android.telephony.SubscriptionManager;
+import android.util.ArraySet;
+import android.util.Log;
+
+import com.android.bluetooth.Utils;
+import com.android.bluetooth.map.BluetoothMapbMessageMime;
+import com.android.bluetooth.map.BluetoothMapbMessageMime.MimePart;
+import com.android.vcard.VCardConstants;
+import com.android.vcard.VCardEntry;
+import com.android.vcard.VCardProperty;
+
+import com.google.android.mms.pdu.PduHeaders;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Set;
+
+class MapClientContent {
+
+    private static final String INBOX_PATH = "telecom/msg/inbox";
+    private static final String TAG = "MapClientContent";
+    private static final int DEFAULT_CHARSET = 106;
+    private static final int ORIGINATOR_ADDRESS_TYPE = 137;
+    private static final int RECIPIENT_ADDRESS_TYPE = 151;
+
+    final BluetoothDevice mDevice;
+    private final Context mContext;
+    private final Callbacks mCallbacks;
+    private final ContentResolver mResolver;
+    ContentObserver mContentObserver;
+    String mPhoneNumber = null;
+    private int mSubscriptionId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
+    private SubscriptionManager mSubscriptionManager;
+    private HashMap<String, Uri> mHandleToUriMap = new HashMap<>();
+    private HashMap<Uri, MessageStatus> mUriToHandleMap = new HashMap<>();
+
+    /**
+     * Callbacks
+     * API to notify about statusChanges as observed from the content provider
+     */
+    interface Callbacks {
+        void onMessageStatusChanged(String handle, int status);
+    }
+
+    /**
+     * MapClientContent manages all interactions between Bluetooth and the messaging provider.
+     *
+     * Changes to the database are mirrored between the remote and local providers, specifically new
+     * messages, changes to read status, and removal of messages.
+     *
+     * context: the context that all content provider interactions are conducted
+     * MceStateMachine:  the interface to send outbound updates such as when a message is read
+     * locally
+     * device: the associated Bluetooth device used for associating messages with a subscription
+     */
+    MapClientContent(Context context, Callbacks callbacks,
+            BluetoothDevice device) {
+        mContext = context;
+        mDevice = device;
+        mCallbacks = callbacks;
+        mResolver = mContext.getContentResolver();
+
+        mSubscriptionManager = (SubscriptionManager) mContext
+                .getSystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE);
+        mSubscriptionManager
+                .addSubscriptionInfoRecord(mDevice.getAddress(), Utils.getName(mDevice), 0,
+                        SubscriptionManager.SUBSCRIPTION_TYPE_REMOTE_SIM);
+        SubscriptionInfo info = mSubscriptionManager
+                .getActiveSubscriptionInfoForIcc(mDevice.getAddress());
+        if (info != null) {
+            mSubscriptionId = info.getSubscriptionId();
+            mSubscriptionManager.setDisplayNumber(mPhoneNumber, mSubscriptionId);
+        }
+
+        mContentObserver = new ContentObserver(null) {
+            @Override
+            public boolean deliverSelfNotifications() {
+                return false;
+            }
+
+            @Override
+            public void onChange(boolean selfChange) {
+                logV("onChange");
+                findChangeInDatabase();
+            }
+
+            @Override
+            public void onChange(boolean selfChange, Uri uri) {
+                logV("onChange" + uri.toString());
+                findChangeInDatabase();
+            }
+        };
+
+        clearMessages(mContext, mSubscriptionId);
+        mResolver.registerContentObserver(Sms.CONTENT_URI, true, mContentObserver);
+        mResolver.registerContentObserver(Mms.CONTENT_URI, true, mContentObserver);
+        mResolver.registerContentObserver(MmsSms.CONTENT_URI, true, mContentObserver);
+    }
+
+    static void clearAllContent(Context context) {
+        SubscriptionManager subscriptionManager = (SubscriptionManager) context
+                .getSystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE);
+        List<SubscriptionInfo> subscriptions = subscriptionManager.getActiveSubscriptionInfoList();
+        for (SubscriptionInfo info : subscriptions) {
+            if (info.getSubscriptionType() == SubscriptionManager.SUBSCRIPTION_TYPE_REMOTE_SIM) {
+                clearMessages(context, info.getSubscriptionId());
+                try {
+                    subscriptionManager.removeSubscriptionInfoRecord(info.getIccId(),
+                            SubscriptionManager.SUBSCRIPTION_TYPE_REMOTE_SIM);
+                } catch (Exception e) {
+                    Log.w(TAG, "cleanUp failed: " + e.toString());
+                }
+            }
+        }
+    }
+
+    private static void logD(String message) {
+        if (MapClientService.DBG) {
+            Log.d(TAG, message);
+        }
+    }
+
+    private static void logV(String message) {
+        if (MapClientService.VDBG) {
+            Log.v(TAG, message);
+        }
+    }
+
+    /**
+     * parseLocalNumber
+     *
+     * Determine the connected phone's number by extracting it from an inbound or outbound mms
+     * message.  This number is necessary such that group messages can be displayed correctly.
+     */
+    void parseLocalNumber(Bmessage message) {
+        if (mPhoneNumber != null) {
+            return;
+        }
+        if (INBOX_PATH.equals(message.getFolder())) {
+            ArrayList<VCardEntry> recipients = message.getRecipients();
+            if (recipients != null && !recipients.isEmpty()) {
+                mPhoneNumber = PhoneNumberUtils.extractNetworkPortion(
+                        getFirstRecipientNumber(message));
+            }
+        } else {
+            mPhoneNumber = PhoneNumberUtils.extractNetworkPortion(getOriginatorNumber(message));
+        }
+
+        logV("Found phone number: " + mPhoneNumber);
+    }
+
+    /**
+     * storeMessage
+     *
+     * Store a message in database with the associated handle and timestamp.
+     * The handle is used to associate the local message with the remote message.
+     */
+    void storeMessage(Bmessage message, String handle, Long timestamp) {
+        switch (message.getType()) {
+            case MMS:
+                storeMms(message, handle, timestamp);
+                return;
+            case SMS_CDMA:
+            case SMS_GSM:
+                storeSms(message, handle, timestamp);
+                return;
+            default:
+                logD("Request to store unsupported message type: " + message.getType());
+        }
+    }
+
+    private void storeSms(Bmessage message, String handle, Long timestamp) {
+        logD("storeSms");
+        logV(message.toString());
+        VCardEntry originator = message.getOriginator();
+        String recipients;
+        if (INBOX_PATH.equals(message.getFolder())) {
+            recipients = getOriginatorNumber(message);
+        } else {
+            recipients = getFirstRecipientNumber(message);
+            if (recipients == null) {
+                logD("invalid recipients");
+                return;
+            }
+        }
+        logV("Received SMS from Number " + recipients);
+        String messageContent;
+
+        Uri contentUri = INBOX_PATH.equalsIgnoreCase(message.getFolder()) ? Sms.Inbox.CONTENT_URI
+                : Sms.Sent.CONTENT_URI;
+        ContentValues values = new ContentValues();
+        long threadId = getThreadId(message);
+        int readStatus = message.getStatus() == Bmessage.Status.READ ? 1 : 0;
+
+        values.put(Sms.THREAD_ID, threadId);
+        values.put(Sms.ADDRESS, recipients);
+        values.put(Sms.BODY, message.getBodyContent());
+        values.put(Sms.SUBSCRIPTION_ID, mSubscriptionId);
+        values.put(Sms.DATE, timestamp);
+        values.put(Sms.READ, readStatus);
+
+        Uri results = mResolver.insert(contentUri, values);
+        mHandleToUriMap.put(handle, results);
+        mUriToHandleMap.put(results, new MessageStatus(handle, readStatus));
+        logD("Map InsertedThread" + results);
+    }
+
+    /**
+     * deleteMessage
+     * remove a message from the local provider based on a remote change
+     */
+    void deleteMessage(String handle) {
+        logD("deleting handle" + handle);
+        Uri messageToChange = mHandleToUriMap.get(handle);
+        if (messageToChange != null) {
+            mResolver.delete(messageToChange, null);
+        }
+    }
+
+
+    /**
+     * markRead
+     * mark a message read in the local provider based on a remote change
+     */
+    void markRead(String handle) {
+        logD("marking read " + handle);
+        Uri messageToChange = mHandleToUriMap.get(handle);
+        if (messageToChange != null) {
+            ContentValues values = new ContentValues();
+            values.put(Sms.READ, 1);
+            mResolver.update(messageToChange, values, null);
+        }
+    }
+
+    /**
+     * findChangeInDatabase
+     * compare the current state of the local content provider to the expected state and propagate
+     * changes to the remote.
+     */
+    private void findChangeInDatabase() {
+        HashMap<Uri, MessageStatus> originalUriToHandleMap;
+        HashMap<Uri, MessageStatus> duplicateUriToHandleMap;
+
+        originalUriToHandleMap = mUriToHandleMap;
+        duplicateUriToHandleMap = new HashMap<>(originalUriToHandleMap);
+        for (Uri uri : new Uri[]{Mms.CONTENT_URI, Sms.CONTENT_URI}) {
+            Cursor cursor = mResolver.query(uri, null, null, null, null);
+            while (cursor.moveToNext()) {
+                Uri index = Uri
+                        .withAppendedPath(uri, cursor.getString(cursor.getColumnIndex("_id")));
+                int readStatus = cursor.getInt(cursor.getColumnIndex(Sms.READ));
+                MessageStatus currentMessage = duplicateUriToHandleMap.remove(index);
+                if (currentMessage != null && currentMessage.mRead != readStatus) {
+                    logV(currentMessage.mHandle);
+                    currentMessage.mRead = readStatus;
+                    mCallbacks.onMessageStatusChanged(currentMessage.mHandle,
+                            BluetoothMapClient.READ);
+                }
+            }
+        }
+        for (HashMap.Entry record : duplicateUriToHandleMap.entrySet()) {
+            logV("Deleted " + ((MessageStatus) record.getValue()).mHandle);
+            originalUriToHandleMap.remove(record.getKey());
+            mCallbacks.onMessageStatusChanged(((MessageStatus) record.getValue()).mHandle,
+                    BluetoothMapClient.DELETED);
+        }
+    }
+
+    private void storeMms(Bmessage message, String handle, Long timestamp) {
+        logD("storeMms");
+        logV(message.toString());
+        try {
+            parseLocalNumber(message);
+            ContentValues values = new ContentValues();
+            long threadId = getThreadId(message);
+            BluetoothMapbMessageMime mmsBmessage = new BluetoothMapbMessageMime();
+            mmsBmessage.parseMsgPart(message.getBodyContent());
+            int read = message.getStatus() == Bmessage.Status.READ ? 1 : 0;
+            Uri contentUri;
+            int messageBox;
+            if (INBOX_PATH.equalsIgnoreCase(message.getFolder())) {
+                contentUri = Mms.Inbox.CONTENT_URI;
+                messageBox = Mms.MESSAGE_BOX_INBOX;
+            } else {
+                contentUri = Mms.Sent.CONTENT_URI;
+                messageBox = Mms.MESSAGE_BOX_SENT;
+            }
+            logD("Parsed");
+            values.put(Mms.SUBSCRIPTION_ID, mSubscriptionId);
+            values.put(Mms.THREAD_ID, threadId);
+            values.put(Mms.DATE, timestamp / 1000L);
+            values.put(Mms.TEXT_ONLY, true);
+            values.put(Mms.MESSAGE_BOX, messageBox);
+            values.put(Mms.READ, read);
+            values.put(Mms.SEEN, 0);
+            values.put(Mms.MESSAGE_TYPE, PduHeaders.MESSAGE_TYPE_SEND_REQ);
+            values.put(Mms.MMS_VERSION, PduHeaders.CURRENT_MMS_VERSION);
+            values.put(Mms.PRIORITY, PduHeaders.PRIORITY_NORMAL);
+            values.put(Mms.READ_REPORT, PduHeaders.VALUE_NO);
+            values.put(Mms.TRANSACTION_ID, "T" + Long.toHexString(System.currentTimeMillis()));
+            values.put(Mms.DELIVERY_REPORT, PduHeaders.VALUE_NO);
+            values.put(Mms.LOCKED, 0);
+            values.put(Mms.CONTENT_TYPE, "application/vnd.wap.multipart.related");
+            values.put(Mms.MESSAGE_CLASS, PduHeaders.MESSAGE_CLASS_PERSONAL_STR);
+            values.put(Mms.MESSAGE_SIZE, mmsBmessage.getSize());
+
+            Uri results = mResolver.insert(contentUri, values);
+            mHandleToUriMap.put(handle, results);
+            mUriToHandleMap.put(results, new MessageStatus(handle, read));
+
+            logD("Map InsertedThread" + results);
+
+            for (MimePart part : mmsBmessage.getMimeParts()) {
+                storeMmsPart(part, results);
+            }
+
+            storeAddressPart(message, results);
+
+            String messageContent = mmsBmessage.getMessageAsText();
+
+            values.put(Mms.Part.CONTENT_TYPE, "plain/text");
+            values.put(Mms.SUBSCRIPTION_ID, mSubscriptionId);
+        } catch (Exception e) {
+            Log.e(TAG, e.toString());
+            throw e;
+        }
+    }
+
+    private Uri storeMmsPart(MimePart messagePart, Uri messageUri) {
+        ContentValues values = new ContentValues();
+        values.put(Mms.Part.CONTENT_TYPE, "text/plain");
+        values.put(Mms.Part.CHARSET, DEFAULT_CHARSET);
+        values.put(Mms.Part.FILENAME, "text_1.txt");
+        values.put(Mms.Part.NAME, "text_1.txt");
+        values.put(Mms.Part.CONTENT_ID, messagePart.mContentId);
+        values.put(Mms.Part.CONTENT_LOCATION, messagePart.mContentLocation);
+        values.put(Mms.Part.TEXT, messagePart.getDataAsString());
+
+        Uri contentUri = Uri.parse(messageUri.toString() + "/part");
+        Uri results = mResolver.insert(contentUri, values);
+        logD("Inserted" + results);
+        return results;
+    }
+
+    private void storeAddressPart(Bmessage message, Uri messageUri) {
+        ContentValues values = new ContentValues();
+        Uri contentUri = Uri.parse(messageUri.toString() + "/addr");
+        String originator = getOriginatorNumber(message);
+        values.put(Mms.Addr.CHARSET, DEFAULT_CHARSET);
+
+        values.put(Mms.Addr.ADDRESS, originator);
+        values.put(Mms.Addr.TYPE, ORIGINATOR_ADDRESS_TYPE);
+        mResolver.insert(contentUri, values);
+
+        Set<String> messageContacts = new ArraySet<>();
+        getRecipientsFromMessage(message, messageContacts);
+        for (String recipient : messageContacts) {
+            values.put(Mms.Addr.ADDRESS, recipient);
+            values.put(Mms.Addr.TYPE, RECIPIENT_ADDRESS_TYPE);
+            mResolver.insert(contentUri, values);
+        }
+    }
+
+    private Uri insertIntoMmsTable(String subject) {
+        ContentValues mmsValues = new ContentValues();
+        mmsValues.put(Mms.TEXT_ONLY, 1);
+        mmsValues.put(Mms.MESSAGE_TYPE, 128);
+        mmsValues.put(Mms.SUBJECT, subject);
+        return mResolver.insert(Mms.CONTENT_URI, mmsValues);
+    }
+
+    /**
+     * cleanUp
+     * clear the subscription info and content on shutdown
+     */
+    void cleanUp() {
+        mResolver.unregisterContentObserver(mContentObserver);
+        clearMessages(mContext, mSubscriptionId);
+        try {
+            mSubscriptionManager.removeSubscriptionInfoRecord(mDevice.getAddress(),
+                    SubscriptionManager.SUBSCRIPTION_TYPE_REMOTE_SIM);
+        } catch (Exception e) {
+            Log.w(TAG, "cleanUp failed: " + e.toString());
+        }
+    }
+
+    /**
+     * clearMessages
+     * clean up the content provider on startup
+     */
+    private static void clearMessages(Context context, int subscriptionId) {
+        ContentResolver resolver = context.getContentResolver();
+        String threads = new String();
+
+        Uri uri = Threads.CONTENT_URI.buildUpon().appendQueryParameter("simple", "true").build();
+        Cursor threadCursor = resolver.query(uri, null, null, null, null);
+        while (threadCursor.moveToNext()) {
+            threads += threadCursor.getInt(threadCursor.getColumnIndex(Threads._ID)) + ", ";
+        }
+
+        resolver.delete(Sms.CONTENT_URI, Sms.SUBSCRIPTION_ID + " =? ",
+                new String[]{Integer.toString(subscriptionId)});
+        resolver.delete(Mms.CONTENT_URI, Mms.SUBSCRIPTION_ID + " =? ",
+                new String[]{Integer.toString(subscriptionId)});
+        if (threads.length() > 2) {
+            threads = threads.substring(0, threads.length() - 2);
+            resolver.delete(Threads.CONTENT_URI, Threads._ID + " IN (" + threads + ")", null);
+        }
+    }
+
+    /**
+     * getThreadId
+     * utilize the originator and recipients to obtain the thread id
+     */
+    private long getThreadId(Bmessage message) {
+
+        Set<String> messageContacts = new ArraySet<>();
+        String originator = PhoneNumberUtils.extractNetworkPortion(getOriginatorNumber(message));
+        if (originator != null) {
+            messageContacts.add(originator);
+        }
+        getRecipientsFromMessage(message, messageContacts);
+        // If there is only one contact don't remove it.
+        if (messageContacts.isEmpty()) {
+            return Telephony.Threads.COMMON_THREAD;
+        } else if (messageContacts.size() > 1) {
+            messageContacts.removeIf(number -> (PhoneNumberUtils.compareLoosely(number,
+                    mPhoneNumber)));
+        }
+
+        logV("Contacts = " + messageContacts.toString());
+        return Telephony.Threads.getOrCreateThreadId(mContext, messageContacts);
+    }
+
+    private void getRecipientsFromMessage(Bmessage message, Set<String> messageContacts) {
+        List<VCardEntry> recipients = message.getRecipients();
+        for (VCardEntry recipient : recipients) {
+            List<VCardEntry.PhoneData> phoneData = recipient.getPhoneList();
+            if (phoneData != null && !phoneData.isEmpty()) {
+                messageContacts
+                        .add(PhoneNumberUtils.extractNetworkPortion(phoneData.get(0).getNumber()));
+            }
+        }
+    }
+
+    private String getOriginatorNumber(Bmessage message) {
+        VCardEntry originator = message.getOriginator();
+        if (originator == null) {
+            return null;
+        }
+
+        List<VCardEntry.PhoneData> phoneData = originator.getPhoneList();
+        if (phoneData == null || phoneData.isEmpty()) {
+            return null;
+        }
+
+        return PhoneNumberUtils.extractNetworkPortion(phoneData.get(0).getNumber());
+    }
+
+    private String getFirstRecipientNumber(Bmessage message) {
+        List<VCardEntry> recipients = message.getRecipients();
+        if (recipients == null || recipients.isEmpty()) {
+            return null;
+        }
+
+        List<VCardEntry.PhoneData> phoneData = recipients.get(0).getPhoneList();
+        if (phoneData == null || phoneData.isEmpty()) {
+            return null;
+        }
+
+        return phoneData.get(0).getNumber();
+    }
+
+    /**
+     * addThreadContactToEntries
+     * utilizing the thread id fill in the appropriate fields of bmsg with the intended recipients
+     */
+    boolean addThreadContactsToEntries(Bmessage bmsg, String thread) {
+        String threadId = Uri.parse(thread).getLastPathSegment();
+
+        logD("MATCHING THREAD" + threadId);
+        logD(MmsSms.CONTENT_CONVERSATIONS_URI + threadId + "/recipients");
+
+        Cursor cursor = mResolver
+                .query(Uri.withAppendedPath(MmsSms.CONTENT_CONVERSATIONS_URI,
+                        threadId + "/recipients"),
+                        null, null,
+                        null, null);
+
+        if (cursor.moveToNext()) {
+            logD("Columns" + Arrays.toString(cursor.getColumnNames()));
+            logV("CONTACT LIST: " + cursor.getString(cursor.getColumnIndex("recipient_ids")));
+            addRecipientsToEntries(bmsg,
+                    cursor.getString(cursor.getColumnIndex("recipient_ids")).split(" "));
+            return true;
+        } else {
+            Log.w(TAG, "Thread Not Found");
+            return false;
+        }
+    }
+
+
+    private void addRecipientsToEntries(Bmessage bmsg, String[] recipients) {
+        logV("CONTACT LIST: " + Arrays.toString(recipients));
+        for (String recipient : recipients) {
+            Cursor cursor = mResolver
+                    .query(Uri.parse("content://mms-sms/canonical-address/" + recipient), null,
+                            null, null,
+                            null);
+            while (cursor.moveToNext()) {
+                String number = cursor.getString(cursor.getColumnIndex(Mms.Addr.ADDRESS));
+                logV("CONTACT number: " + number);
+                VCardEntry destEntry = new VCardEntry();
+                VCardProperty destEntryPhone = new VCardProperty();
+                destEntryPhone.setName(VCardConstants.PROPERTY_TEL);
+                destEntryPhone.addValues(number);
+                destEntry.addProperty(destEntryPhone);
+                bmsg.addRecipient(destEntry);
+            }
+        }
+    }
+
+    /**
+     * MessageStatus
+     *
+     * Helper class to store associations between remote and local provider based on message handle
+     * and read status
+     */
+    class MessageStatus {
+
+        String mHandle;
+        int mRead;
+
+        MessageStatus(String handle, int read) {
+            mHandle = handle;
+            mRead = read;
+        }
+
+        @Override
+        public boolean equals(Object other) {
+            return ((other instanceof MessageStatus) && ((MessageStatus) other).mHandle
+                    .equals(mHandle));
+        }
+    }
+}
diff --git a/src/com/android/bluetooth/mapclient/MapClientService.java b/src/com/android/bluetooth/mapclient/MapClientService.java
index de75c0c..f1c6c40 100644
--- a/src/com/android/bluetooth/mapclient/MapClientService.java
+++ b/src/com/android/bluetooth/mapclient/MapClientService.java
@@ -17,6 +17,7 @@
 package com.android.bluetooth.mapclient;
 
 import android.Manifest;
+import android.annotation.RequiresPermission;
 import android.app.PendingIntent;
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothDevice;
@@ -24,6 +25,8 @@
 import android.bluetooth.BluetoothUuid;
 import android.bluetooth.IBluetoothMapClient;
 import android.bluetooth.SdpMasRecord;
+import android.content.Attributable;
+import android.content.AttributionSource;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
@@ -35,6 +38,7 @@
 import com.android.bluetooth.Utils;
 import com.android.bluetooth.btservice.AdapterService;
 import com.android.bluetooth.btservice.ProfileService;
+import com.android.bluetooth.btservice.storage.DatabaseManager;
 import com.android.internal.annotations.VisibleForTesting;
 
 import java.util.ArrayList;
@@ -42,7 +46,7 @@
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
-import java.util.Set;
+import java.util.Objects;
 import java.util.concurrent.ConcurrentHashMap;
 
 public class MapClientService extends ProfileService {
@@ -53,11 +57,11 @@
 
     static final int MAXIMUM_CONNECTED_DEVICES = 4;
 
-    private static final String BLUETOOTH_PERM = android.Manifest.permission.BLUETOOTH;
-
     private Map<BluetoothDevice, MceStateMachine> mMapInstanceMap = new ConcurrentHashMap<>(1);
     private MnsService mMnsServer;
-    private BluetoothAdapter mAdapter;
+
+    private AdapterService mAdapterService;
+    private DatabaseManager mDatabaseManager;
     private static MapClientService sMapClientService;
     private MapBroadcastReceiver mMapReceiver;
 
@@ -91,6 +95,7 @@
      * @param device
      * @return true if connection is successful, false otherwise.
      */
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED)
     public synchronized boolean connect(BluetoothDevice device) {
         enforceCallingOrSelfPermission(BLUETOOTH_PRIVILEGED,
                 "Need BLUETOOTH_PRIVILEGED permission");
@@ -160,6 +165,7 @@
         mMapInstanceMap.put(device, mapStateMachine);
     }
 
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED)
     public synchronized boolean disconnect(BluetoothDevice device) {
         enforceCallingOrSelfPermission(BLUETOOTH_PRIVILEGED,
                 "Need BLUETOOTH_PRIVILEGED permission");
@@ -200,7 +206,7 @@
     public synchronized List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
         if (DBG) Log.d(TAG, "getDevicesMatchingConnectionStates" + Arrays.toString(states));
         List<BluetoothDevice> deviceList = new ArrayList<>();
-        Set<BluetoothDevice> bondedDevices = mAdapter.getBondedDevices();
+        BluetoothDevice[] bondedDevices = mAdapterService.getBondedDevices();
         int connectionState;
         for (BluetoothDevice device : bondedDevices) {
             connectionState = getConnectionState(device);
@@ -237,14 +243,18 @@
      * @param connectionPolicy is the connection policy to set to for this profile
      * @return true if connectionPolicy is set, false on error
      */
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED)
     public boolean setConnectionPolicy(BluetoothDevice device, int connectionPolicy) {
         if (VDBG) {
             Log.v(TAG, "Saved connectionPolicy " + device + " = " + connectionPolicy);
         }
         enforceCallingOrSelfPermission(BLUETOOTH_PRIVILEGED,
                 "Need BLUETOOTH_PRIVILEGED permission");
-        AdapterService.getAdapterService().getDatabase()
-                .setProfileConnectionPolicy(device, BluetoothProfile.MAP_CLIENT, connectionPolicy);
+
+        if (!mDatabaseManager.setProfileConnectionPolicy(device, BluetoothProfile.MAP_CLIENT,
+                  connectionPolicy)) {
+            return false;
+        }
         if (connectionPolicy == BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
             connect(device);
         } else if (connectionPolicy == BluetoothProfile.CONNECTION_POLICY_FORBIDDEN) {
@@ -265,10 +275,11 @@
      * @return connection policy of the device
      * @hide
      */
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED)
     public int getConnectionPolicy(BluetoothDevice device) {
         enforceCallingOrSelfPermission(BLUETOOTH_PRIVILEGED,
                 "Need BLUETOOTH_PRIVILEGED permission");
-        return AdapterService.getAdapterService().getDatabase()
+        return mDatabaseManager
                 .getProfileConnectionPolicy(device, BluetoothProfile.MAP_CLIENT);
     }
 
@@ -280,7 +291,7 @@
     }
 
     @Override
-    protected IProfileServiceBinder initBinder() {
+    public IProfileServiceBinder initBinder() {
         return new Binder(this);
     }
 
@@ -288,6 +299,10 @@
     protected synchronized boolean start() {
         Log.e(TAG, "start()");
 
+        mAdapterService = AdapterService.getAdapterService();
+        mDatabaseManager = Objects.requireNonNull(AdapterService.getAdapterService().getDatabase(),
+                "DatabaseManager cannot be null when MapClientService starts");
+
         if (mMnsServer == null) {
             mMnsServer = MapUtils.newMnsServiceInstance(this);
             if (mMnsServer == null) {
@@ -297,14 +312,13 @@
             }
         }
 
-        mAdapter = BluetoothAdapter.getDefaultAdapter();
-
         mMapReceiver = new MapBroadcastReceiver();
         IntentFilter filter = new IntentFilter();
         filter.addAction(BluetoothDevice.ACTION_SDP_RECORD);
         filter.addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED);
         registerReceiver(mMapReceiver, filter);
         removeUncleanAccounts();
+        MapClientContent.clearAllContent(this);
         setMapClientService(this);
         return true;
     }
@@ -414,6 +428,14 @@
         return mapStateMachine.getSupportedFeatures();
     }
 
+    public synchronized boolean setMessageStatus(BluetoothDevice device, String handle, int status) {
+        MceStateMachine mapStateMachine = mMapInstanceMap.get(device);
+        if (mapStateMachine == null) {
+            return false;
+        }
+        return mapStateMachine.setMessageStatus(handle, status);
+    }
+
     @Override
     public void dump(StringBuilder sb) {
         super.dump(sb);
@@ -438,18 +460,14 @@
             mService = service;
         }
 
-        private MapClientService getService() {
-            if (!Utils.checkCaller()) {
-                Log.w(TAG, "MAP call not allowed for non-active user");
+        @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
+        private MapClientService getService(AttributionSource source) {
+            if (!(MapUtils.isSystemUser() || Utils.checkCallerIsSystemOrActiveUser(TAG))
+                    || !Utils.checkServiceAvailable(mService, TAG)
+                    || !Utils.checkConnectPermissionForDataDelivery(mService, source, TAG)) {
                 return null;
             }
-
-            if (mService != null && mService.isAvailable()) {
-                mService.enforceCallingOrSelfPermission(BLUETOOTH_PERM,
-                        "Need BLUETOOTH permission");
-                return mService;
-            }
-            return null;
+            return mService;
         }
 
         @Override
@@ -458,11 +476,12 @@
         }
 
         @Override
-        public boolean isConnected(BluetoothDevice device) {
+        public boolean isConnected(BluetoothDevice device, AttributionSource source) {
             if (VDBG) {
                 Log.v(TAG, "isConnected()");
             }
-            MapClientService service = getService();
+            Attributable.setAttributionSource(device, source);
+            MapClientService service = getService(source);
             if (service == null) {
                 return false;
             }
@@ -470,11 +489,12 @@
         }
 
         @Override
-        public boolean connect(BluetoothDevice device) {
+        public boolean connect(BluetoothDevice device, AttributionSource source) {
             if (VDBG) {
                 Log.v(TAG, "connect()");
             }
-            MapClientService service = getService();
+            Attributable.setAttributionSource(device, source);
+            MapClientService service = getService(source);
             if (service == null) {
                 return false;
             }
@@ -482,11 +502,12 @@
         }
 
         @Override
-        public boolean disconnect(BluetoothDevice device) {
+        public boolean disconnect(BluetoothDevice device, AttributionSource source) {
             if (VDBG) {
                 Log.v(TAG, "disconnect()");
             }
-            MapClientService service = getService();
+            Attributable.setAttributionSource(device, source);
+            MapClientService service = getService(source);
             if (service == null) {
                 return false;
             }
@@ -494,11 +515,11 @@
         }
 
         @Override
-        public List<BluetoothDevice> getConnectedDevices() {
+        public List<BluetoothDevice> getConnectedDevices(AttributionSource source) {
             if (VDBG) {
                 Log.v(TAG, "getConnectedDevices()");
             }
-            MapClientService service = getService();
+            MapClientService service = getService(source);
             if (service == null) {
                 return new ArrayList<BluetoothDevice>(0);
             }
@@ -506,11 +527,12 @@
         }
 
         @Override
-        public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
+        public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states,
+                AttributionSource source) {
             if (VDBG) {
                 Log.v(TAG, "getDevicesMatchingConnectionStates()");
             }
-            MapClientService service = getService();
+            MapClientService service = getService(source);
             if (service == null) {
                 return new ArrayList<BluetoothDevice>(0);
             }
@@ -518,11 +540,12 @@
         }
 
         @Override
-        public int getConnectionState(BluetoothDevice device) {
+        public int getConnectionState(BluetoothDevice device, AttributionSource source) {
             if (VDBG) {
                 Log.v(TAG, "getConnectionState()");
             }
-            MapClientService service = getService();
+            Attributable.setAttributionSource(device, source);
+            MapClientService service = getService(source);
             if (service == null) {
                 return BluetoothProfile.STATE_DISCONNECTED;
             }
@@ -530,8 +553,10 @@
         }
 
         @Override
-        public boolean setConnectionPolicy(BluetoothDevice device, int connectionPolicy) {
-            MapClientService service = getService();
+        public boolean setConnectionPolicy(BluetoothDevice device, int connectionPolicy,
+                AttributionSource source) {
+            Attributable.setAttributionSource(device, source);
+            MapClientService service = getService(source);
             if (service == null) {
                 return false;
             }
@@ -539,8 +564,9 @@
         }
 
         @Override
-        public int getConnectionPolicy(BluetoothDevice device) {
-            MapClientService service = getService();
+        public int getConnectionPolicy(BluetoothDevice device, AttributionSource source) {
+            Attributable.setAttributionSource(device, source);
+            MapClientService service = getService(source);
             if (service == null) {
                 return BluetoothProfile.CONNECTION_POLICY_UNKNOWN;
             }
@@ -549,8 +575,9 @@
 
         @Override
         public boolean sendMessage(BluetoothDevice device, Uri[] contacts, String message,
-                PendingIntent sentIntent, PendingIntent deliveredIntent) {
-            MapClientService service = getService();
+                PendingIntent sentIntent, PendingIntent deliveredIntent, AttributionSource source) {
+            Attributable.setAttributionSource(device, source);
+            MapClientService service = getService(source);
             if (service == null) {
                 return false;
             }
@@ -562,8 +589,9 @@
         }
 
         @Override
-        public boolean getUnreadMessages(BluetoothDevice device) {
-            MapClientService service = getService();
+        public boolean getUnreadMessages(BluetoothDevice device, AttributionSource source) {
+            Attributable.setAttributionSource(device, source);
+            MapClientService service = getService(source);
             if (service == null) {
                 return false;
             }
@@ -573,8 +601,9 @@
         }
 
         @Override
-        public int getSupportedFeatures(BluetoothDevice device) {
-            MapClientService service = getService();
+        public int getSupportedFeatures(BluetoothDevice device, AttributionSource source) {
+            Attributable.setAttributionSource(device, source);
+            MapClientService service = getService(source);
             if (service == null) {
                 if (DBG) {
                     Log.d(TAG,
@@ -582,10 +611,21 @@
                 }
                 return 0;
             }
-            mService.enforceCallingOrSelfPermission(Manifest.permission.BLUETOOTH,
-                    "Need BLUETOOTH permission");
             return service.getSupportedFeatures(device);
         }
+
+        @Override
+        public boolean setMessageStatus(BluetoothDevice device, String handle, int status,
+                AttributionSource source) {
+            Attributable.setAttributionSource(device, source);
+            MapClientService service = getService(source);
+            if (service == null) {
+                return false;
+            }
+            mService.enforceCallingOrSelfPermission(Manifest.permission.READ_SMS,
+                    "Need READ_SMS permission");
+            return service.setMessageStatus(device, handle, status);
+        }
     }
 
     private class MapBroadcastReceiver extends BroadcastReceiver {
@@ -606,8 +646,7 @@
                 return;
             }
             if (DBG) {
-                Log.d(TAG, "broadcast has device: (" + device.getAddress() + ", "
-                        + device.getName() + ")");
+                Log.d(TAG, "broadcast has device: (" + device.getAddress() + ")");
             }
             MceStateMachine stateMachine = mMapInstanceMap.get(device);
             if (stateMachine == null) {
diff --git a/src/com/android/bluetooth/mapclient/MapUtils.java b/src/com/android/bluetooth/mapclient/MapUtils.java
index c47526a..a26816d 100644
--- a/src/com/android/bluetooth/mapclient/MapUtils.java
+++ b/src/com/android/bluetooth/mapclient/MapUtils.java
@@ -16,6 +16,7 @@
 package com.android.bluetooth.mapclient;
 
 import android.os.SystemProperties;
+import android.os.UserHandle;
 
 import com.android.bluetooth.Utils;
 import com.android.internal.annotations.VisibleForTesting;
@@ -32,6 +33,10 @@
         sMnsService = service;
     }
 
+    static boolean isSystemUser() {
+        return UserHandle.getCallingUserId() == UserHandle.USER_SYSTEM;
+    }
+
     static MnsService newMnsServiceInstance(MapClientService mapClientService) {
         return (sMnsService == null) ? new MnsService(mapClientService) : sMnsService;
     }
diff --git a/src/com/android/bluetooth/mapclient/MceStateMachine.java b/src/com/android/bluetooth/mapclient/MceStateMachine.java
index 5a5f028..7a88841 100644
--- a/src/com/android/bluetooth/mapclient/MceStateMachine.java
+++ b/src/com/android/bluetooth/mapclient/MceStateMachine.java
@@ -40,6 +40,9 @@
  */
 package com.android.bluetooth.mapclient;
 
+import static android.Manifest.permission.BLUETOOTH_CONNECT;
+import static android.Manifest.permission.RECEIVE_SMS;
+
 import android.app.Activity;
 import android.app.PendingIntent;
 import android.bluetooth.BluetoothDevice;
@@ -81,7 +84,7 @@
  * a connection to the Message Access Server is created and a request to enable notification of new
  * messages is sent.
  */
-final class MceStateMachine extends StateMachine {
+class MceStateMachine extends StateMachine {
     // Messages for events handled by the StateMachine
     static final int MSG_MAS_CONNECTED = 1001;
     static final int MSG_MAS_DISCONNECTED = 1002;
@@ -94,10 +97,17 @@
     static final int MSG_NOTIFICATION = 2003;
     static final int MSG_GET_LISTING = 2004;
     static final int MSG_GET_MESSAGE_LISTING = 2005;
+    // Set message status to read or deleted
+    static final int MSG_SET_MESSAGE_STATUS = 2006;
 
-    private static final String TAG = "MceSM";
+    private static final String TAG = "MceStateMachine";
     private static final Boolean DBG = MapClientService.DBG;
-    private static final int TIMEOUT = 10000;
+    // SAVE_OUTBOUND_MESSAGES defaults to true to place the responsibility of managing content on
+    // Bluetooth, to work with the default Car Messenger.  This may need to be set to false if the
+    // messaging app takes that responsibility.
+    private static final Boolean SAVE_OUTBOUND_MESSAGES = true;
+    private static final int DISCONNECT_TIMEOUT = 3000;
+    private static final int CONNECT_TIMEOUT = 10000;
     private static final int MAX_MESSAGES = 20;
     private static final int MSG_CONNECT = 1;
     private static final int MSG_DISCONNECT = 2;
@@ -108,6 +118,7 @@
     private static final String FOLDER_MSG = "msg";
     private static final String FOLDER_OUTBOX = "outbox";
     private static final String FOLDER_INBOX = "inbox";
+    private static final String FOLDER_SENT = "sent";
     private static final String INBOX_PATH = "telecom/msg/inbox";
 
 
@@ -121,6 +132,7 @@
     private final BluetoothDevice mDevice;
     private MapClientService mService;
     private MasClient mMasClient;
+    private MapClientContent mDatabase;
     private HashMap<String, Bmessage> mSentMessageLog = new HashMap<>(MAX_MESSAGES);
     private HashMap<Bmessage, PendingIntent> mSentReceiptRequested = new HashMap<>(MAX_MESSAGES);
     private HashMap<Bmessage, PendingIntent> mDeliveryReceiptRequested =
@@ -187,6 +199,7 @@
         mDisconnecting = new Disconnecting();
         mConnected = new Connected();
 
+
         addState(mDisconnected);
         addState(mConnecting);
         addState(mDisconnecting);
@@ -226,7 +239,7 @@
         intent.putExtra(BluetoothProfile.EXTRA_STATE, state);
         intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mDevice);
         intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
-        mService.sendBroadcast(intent, ProfileService.BLUETOOTH_PERM);
+        mService.sendBroadcast(intent, BLUETOOTH_CONNECT, Utils.getTempAllowlistBroadcastOptions());
     }
 
     public synchronized int getState() {
@@ -270,16 +283,23 @@
 
             for (Uri contact : contacts) {
                 // Who to send the message to.
-                VCardEntry destEntry = new VCardEntry();
-                VCardProperty destEntryPhone = new VCardProperty();
                 if (DBG) {
                     Log.d(TAG, "Scheme " + contact.getScheme());
                 }
                 if (PhoneAccount.SCHEME_TEL.equals(contact.getScheme())) {
-                    destEntryPhone.setName(VCardConstants.PROPERTY_TEL);
-                    destEntryPhone.addValues(contact.getSchemeSpecificPart());
-                    if (DBG) {
-                        Log.d(TAG, "Sending to phone numbers " + destEntryPhone.getValueList());
+                    String path = contact.getPath();
+                    if (path != null && path.contains(Telephony.Threads.CONTENT_URI.toString())) {
+                        mDatabase.addThreadContactsToEntries(bmsg, contact.getLastPathSegment());
+                    } else {
+                        VCardEntry destEntry = new VCardEntry();
+                        VCardProperty destEntryPhone = new VCardProperty();
+                        destEntryPhone.setName(VCardConstants.PROPERTY_TEL);
+                        destEntryPhone.addValues(contact.getSchemeSpecificPart());
+                        destEntry.addProperty(destEntryPhone);
+                        bmsg.addRecipient(destEntry);
+                        if (DBG) {
+                            Log.d(TAG, "Sending to phone numbers " + destEntryPhone.getValueList());
+                        }
                     }
                 } else {
                     if (DBG) {
@@ -287,8 +307,6 @@
                     }
                     return false;
                 }
-                destEntry.addProperty(destEntryPhone);
-                bmsg.addRecipient(destEntry);
             }
 
             // Message of the body.
@@ -336,6 +354,45 @@
         return 0;
     }
 
+    synchronized boolean setMessageStatus(String handle, int status) {
+        if (DBG) {
+            Log.d(TAG, "setMessageStatus(" + handle + ", " + status + ")");
+        }
+        if (this.getCurrentState() == mConnected) {
+            RequestSetMessageStatus.StatusIndicator statusIndicator;
+            byte value;
+            switch (status) {
+                case BluetoothMapClient.UNREAD:
+                    statusIndicator = RequestSetMessageStatus.StatusIndicator.READ;
+                    value = RequestSetMessageStatus.STATUS_NO;
+                    break;
+
+                case BluetoothMapClient.READ:
+                    statusIndicator = RequestSetMessageStatus.StatusIndicator.READ;
+                    value = RequestSetMessageStatus.STATUS_YES;
+                    break;
+
+                case BluetoothMapClient.UNDELETED:
+                    statusIndicator = RequestSetMessageStatus.StatusIndicator.DELETED;
+                    value = RequestSetMessageStatus.STATUS_NO;
+                    break;
+
+                case BluetoothMapClient.DELETED:
+                    statusIndicator = RequestSetMessageStatus.StatusIndicator.DELETED;
+                    value = RequestSetMessageStatus.STATUS_YES;
+                    break;
+
+                default:
+                    Log.e(TAG, "Invalid parameter for status" + status);
+                    return false;
+            }
+            sendMessage(MSG_SET_MESSAGE_STATUS, 0, 0, new RequestSetMessageStatus(
+                    handle, statusIndicator, value));
+            return true;
+        }
+        return false;
+    }
+
     private String getContactURIFromPhone(String number) {
         return PhoneAccount.SCHEME_TEL + ":" + number;
     }
@@ -364,7 +421,7 @@
 
     public void dump(StringBuilder sb) {
         ProfileService.println(sb, "mCurrentDevice: " + mDevice.getAddress() + "("
-                + mDevice.getName() + ") " + this.toString());
+                + Utils.getName(mDevice) + ") " + this.toString());
     }
 
     class Disconnected extends State {
@@ -394,7 +451,7 @@
 
             // When commanded to connect begin SDP to find the MAS server.
             mDevice.sdpSearch(BluetoothUuid.MAS);
-            sendMessageDelayed(MSG_CONNECTING_TIMEOUT, TIMEOUT);
+            sendMessageDelayed(MSG_CONNECTING_TIMEOUT, CONNECT_TIMEOUT);
         }
 
         @Override
@@ -412,7 +469,7 @@
                         SdpMasRecord record = (SdpMasRecord) message.obj;
                         if (record == null) {
                             Log.e(TAG, "Unexpected: SDP record is null for device "
-                                    + mDevice.getName());
+                                    + Utils.getName(mDevice));
                             return NOT_HANDLED;
                         }
                         mMasClient = new MasClient(mDevice, MceStateMachine.this, record);
@@ -461,6 +518,14 @@
             if (DBG) {
                 Log.d(TAG, "Enter Connected: " + getCurrentMessage().what);
             }
+
+            MapClientContent.Callbacks callbacks = new MapClientContent.Callbacks(){
+                @Override
+                public void onMessageStatusChanged(String handle, int status) {
+                    setMessageStatus(handle, status);
+                }
+            };
+            mDatabase = new MapClientContent(mService, callbacks, mDevice);
             onConnectionStateChanged(mPreviousState, BluetoothProfile.STATE_CONNECTED);
             if (Utils.isPtsTestMode()) return;
 
@@ -470,6 +535,8 @@
             mMasClient.makeRequest(new RequestGetFolderListing(0, 0));
             mMasClient.makeRequest(new RequestSetPath(false));
             mMasClient.makeRequest(new RequestSetNotificationRegistration(true));
+            sendMessage(MSG_GET_MESSAGE_LISTING, FOLDER_SENT);
+            sendMessage(MSG_GET_MESSAGE_LISTING, FOLDER_INBOX);
         }
 
         @Override
@@ -510,7 +577,6 @@
                     // Get latest 50 Unread messages in the last week
                     MessagesFilter filter = new MessagesFilter();
                     filter.setMessageType(MapUtils.fetchMessageType());
-                    filter.setReadStatus(MessagesFilter.READ_STATUS_UNREAD);
                     Calendar calendar = Calendar.getInstance();
                     calendar.add(Calendar.DATE, -7);
                     filter.setPeriod(calendar.getTime(), null);
@@ -518,6 +584,12 @@
                             (String) message.obj, 0, filter, 0, 50, 0));
                     break;
 
+                case MSG_SET_MESSAGE_STATUS:
+                    if (message.obj instanceof RequestSetMessageStatus) {
+                        mMasClient.makeRequest((RequestSetMessageStatus) message.obj);
+                    }
+                    break;
+
                 case MSG_MAS_REQUEST_COMPLETED:
                     if (DBG) {
                         Log.d(TAG, "Completed request");
@@ -525,7 +597,8 @@
                     if (message.obj instanceof RequestGetMessage) {
                         processInboundMessage((RequestGetMessage) message.obj);
                     } else if (message.obj instanceof RequestPushMessage) {
-                        String messageHandle = ((RequestPushMessage) message.obj).getMsgHandle();
+                        RequestPushMessage requestPushMessage = (RequestPushMessage) message.obj;
+                        String messageHandle = requestPushMessage.getMsgHandle();
                         if (DBG) {
                             Log.d(TAG, "Message Sent......." + messageHandle);
                         }
@@ -533,11 +606,17 @@
                         // some test devices don't populate messageHandle field.
                         // in such cases, no need to wait up for response for such messages.
                         if (messageHandle != null && messageHandle.length() > 2) {
+                            if (SAVE_OUTBOUND_MESSAGES) {
+                                mDatabase.storeMessage(requestPushMessage.getBMsg(), messageHandle,
+                                        System.currentTimeMillis());
+                            }
                             mSentMessageLog.put(messageHandle.substring(2),
-                                    ((RequestPushMessage) message.obj).getBMsg());
+                                    requestPushMessage.getBMsg());
                         }
                     } else if (message.obj instanceof RequestGetMessagesListing) {
                         processMessageListing((RequestGetMessagesListing) message.obj);
+                    } else if (message.obj instanceof RequestSetMessageStatus) {
+                        processSetMessageStatus((RequestSetMessageStatus) message.obj);
                     }
                     break;
 
@@ -558,6 +637,7 @@
 
         @Override
         public void exit() {
+            mDatabase.cleanUp();
             mPreviousState = BluetoothProfile.STATE_CONNECTED;
         }
 
@@ -587,7 +667,6 @@
                                 + ", Message handle = " + ev.getHandle());
                     }
                     switch (ev.getType()) {
-
                         case NEW_MESSAGE:
                             // Infer the timestamp for this message as 'now' and read status false
                             // instead of getting the message listing data for it
@@ -600,29 +679,34 @@
                             mMasClient.makeRequest(new RequestGetMessage(ev.getHandle(),
                                     MasClient.CharsetType.UTF_8, false));
                             break;
-
                         case DELIVERY_SUCCESS:
                         case SENDING_SUCCESS:
                             notifySentMessageStatus(ev.getHandle(), ev.getType());
                             break;
+                        case READ_STATUS_CHANGED:
+                            mDatabase.markRead(ev.getHandle());
+                            break;
+                        case MESSAGE_DELETED:
+                            mDatabase.deleteMessage(ev.getHandle());
+                            break;
                     }
             }
         }
 
         // Sets the specified message status to "read" (from "unread" status, mostly)
         private void markMessageRead(RequestGetMessage request) {
-            if (DBG) Log.d(TAG, "markMessageRead");
+            if (DBG) Log.d(TAG, "markMessageRead" + request.getHandle());
             MessageMetadata metadata = mMessages.get(request.getHandle());
             metadata.setRead(true);
-            mMasClient.makeRequest(new RequestSetMessageStatus(
-                    request.getHandle(), RequestSetMessageStatus.StatusIndicator.READ));
+            mMasClient.makeRequest(new RequestSetMessageStatus(request.getHandle(),
+                    RequestSetMessageStatus.StatusIndicator.READ, RequestSetMessageStatus.STATUS_YES));
         }
 
         // Sets the specified message status to "deleted"
         private void markMessageDeleted(RequestGetMessage request) {
             if (DBG) Log.d(TAG, "markMessageDeleted");
-            mMasClient.makeRequest(new RequestSetMessageStatus(
-                    request.getHandle(), RequestSetMessageStatus.StatusIndicator.DELETED));
+            mMasClient.makeRequest(new RequestSetMessageStatus(request.getHandle(),
+                    RequestSetMessageStatus.StatusIndicator.DELETED, RequestSetMessageStatus.STATUS_YES));
         }
 
         /**
@@ -653,6 +737,43 @@
             }
         }
 
+        private void processSetMessageStatus(RequestSetMessageStatus request) {
+            if (DBG) {
+                Log.d(TAG, "processSetMessageStatus");
+            }
+            int result = BluetoothMapClient.RESULT_SUCCESS;
+            if (!request.isSuccess()) {
+                Log.e(TAG, "Set message status failed");
+                result = BluetoothMapClient.RESULT_FAILURE;
+            }
+            RequestSetMessageStatus.StatusIndicator status = request.getStatusIndicator();
+            switch (status) {
+                case READ: {
+                    Intent intent = new Intent(
+                            BluetoothMapClient.ACTION_MESSAGE_READ_STATUS_CHANGED);
+                    intent.putExtra(BluetoothMapClient.EXTRA_MESSAGE_READ_STATUS,
+                            request.getValue() == RequestSetMessageStatus.STATUS_YES ? true : false);
+                    intent.putExtra(BluetoothMapClient.EXTRA_MESSAGE_HANDLE, request.getHandle());
+                    intent.putExtra(BluetoothMapClient.EXTRA_RESULT_CODE, result);
+                    mService.sendBroadcast(intent, BLUETOOTH_CONNECT);
+                    break;
+                }
+                case DELETED: {
+                    Intent intent = new Intent(
+                            BluetoothMapClient.ACTION_MESSAGE_DELETED_STATUS_CHANGED);
+                    intent.putExtra(BluetoothMapClient.EXTRA_MESSAGE_DELETED_STATUS,
+                            request.getValue() == RequestSetMessageStatus.STATUS_YES ? true : false);
+                    intent.putExtra(BluetoothMapClient.EXTRA_MESSAGE_HANDLE, request.getHandle());
+                    intent.putExtra(BluetoothMapClient.EXTRA_RESULT_CODE, result);
+                    mService.sendBroadcast(intent, BLUETOOTH_CONNECT);
+                    break;
+                }
+                default:
+                    Log.e(TAG, "Unknown status indicator " + status);
+                    return;
+            }
+        }
+
         /**
          * Given the response of a GetMessage request, will broadcast the bMessage contents on to
          * all registered applications.
@@ -672,6 +793,8 @@
             if (message == null) {
                 return;
             }
+            mDatabase.storeMessage(message, request.getHandle(),
+                    mMessages.get(request.getHandle()).getTimestamp());
             if (!INBOX_PATH.equalsIgnoreCase(message.getFolder())) {
                 if (DBG) {
                     Log.d(TAG, "Ignoring message received in " + message.getFolder() + ".");
@@ -738,7 +861,7 @@
                     if (defaultMessagingPackage != null) {
                         intent.setPackage(defaultMessagingPackage);
                     }
-                    mService.sendBroadcast(intent, android.Manifest.permission.RECEIVE_SMS);
+                    mService.sendBroadcast(intent, RECEIVE_SMS);
                     break;
                 case EMAIL:
                 default:
@@ -820,7 +943,7 @@
             if (mMasClient != null) {
                 mMasClient.makeRequest(new RequestSetNotificationRegistration(false));
                 mMasClient.shutdown();
-                sendMessageDelayed(MSG_DISCONNECTING_TIMEOUT, TIMEOUT);
+                sendMessageDelayed(MSG_DISCONNECTING_TIMEOUT, DISCONNECT_TIMEOUT);
             } else {
                 // MAP was never connected
                 transitionTo(mDisconnected);
diff --git a/src/com/android/bluetooth/mapclient/MnsService.java b/src/com/android/bluetooth/mapclient/MnsService.java
index b1df1ec..c50cf0e 100644
--- a/src/com/android/bluetooth/mapclient/MnsService.java
+++ b/src/com/android/bluetooth/mapclient/MnsService.java
@@ -26,6 +26,7 @@
 import com.android.bluetooth.BluetoothObexTransport;
 import com.android.bluetooth.IObexConnectionHandler;
 import com.android.bluetooth.ObexServerSockets;
+import com.android.bluetooth.Utils;
 import com.android.bluetooth.sdp.SdpManager;
 
 import java.io.IOException;
@@ -128,11 +129,11 @@
             MceStateMachine stateMachine = sContext.getMceStateMachineForDevice(device);
             if (stateMachine == null) {
                 Log.e(TAG, "Error: NO statemachine for device: " + device.getAddress()
-                        + " (name: " + device.getName());
+                        + " (name: " + Utils.getName(device));
                 return false;
             } else if (stateMachine.getState() != BluetoothProfile.STATE_CONNECTED) {
                 Log.e(TAG, "Error: statemachine for device: " + device.getAddress()
-                        + " (name: " + device.getName() + ") is not currently CONNECTED : "
+                        + " (name: " + Utils.getName(device) + ") is not currently CONNECTED : "
                         + stateMachine.getCurrentState());
                 return false;
             }
diff --git a/src/com/android/bluetooth/mapclient/obex/BmessageParser.java b/src/com/android/bluetooth/mapclient/obex/BmessageParser.java
index a556550..28f438b 100644
--- a/src/com/android/bluetooth/mapclient/obex/BmessageParser.java
+++ b/src/com/android/bluetooth/mapclient/obex/BmessageParser.java
@@ -36,7 +36,7 @@
 /* BMessage as defined by MAP_SPEC_V101 Section 3.1.3 Message format (x-bt/message) */
 class BmessageParser {
     private static final String TAG = "BmessageParser";
-    private static final boolean DBG = false;
+    private static final boolean DBG = MapClientService.DBG;
 
     private static final String CRLF = "\r\n";
 
@@ -293,7 +293,7 @@
          * 2020-06-01: we could now expect MMS to be more than text, e.g., image-only, so charset
          * not always UTF-8, downgrading log message from ERROR to DEBUG.
          */
-        if (!"UTF-8".equals(mBmsg.mBbodyCharset)) {
+        if (DBG && !"UTF-8".equals(mBmsg.mBbodyCharset)) {
             Log.d(TAG, "The charset was not set to charset UTF-8: " + mBmsg.mBbodyCharset);
         }
 
diff --git a/src/com/android/bluetooth/mapclient/obex/BmsgTokenizer.java b/src/com/android/bluetooth/mapclient/obex/BmsgTokenizer.java
index 0964ae3..b0ef434 100644
--- a/src/com/android/bluetooth/mapclient/obex/BmsgTokenizer.java
+++ b/src/com/android/bluetooth/mapclient/obex/BmsgTokenizer.java
@@ -23,6 +23,8 @@
 import java.util.regex.Pattern;
 
 public final class BmsgTokenizer {
+    private static final String TAG = "BmsgTokenizer";
+    private static final boolean VDBG = MapClientService.VDBG;
 
     private final String mStr;
 
@@ -89,7 +91,9 @@
             this.name = name;
             this.value = value;
 
-            Log.v("BMSG >> ", toString());
+            if (VDBG) {
+                Log.v(TAG, toString());
+            }
         }
 
         @Override
diff --git a/src/com/android/bluetooth/mapclient/obex/RequestPushMessage.java b/src/com/android/bluetooth/mapclient/obex/RequestPushMessage.java
index bc10939..97170fd 100644
--- a/src/com/android/bluetooth/mapclient/obex/RequestPushMessage.java
+++ b/src/com/android/bluetooth/mapclient/obex/RequestPushMessage.java
@@ -17,6 +17,7 @@
 package com.android.bluetooth.mapclient;
 
 import com.android.bluetooth.mapclient.MasClient.CharsetType;
+import com.android.internal.annotations.VisibleForTesting;
 
 import java.io.IOException;
 import java.math.BigInteger;
@@ -26,7 +27,8 @@
 import javax.obex.ResponseCodes;
 
 /* Place a message into current directory on MSE. */
-final class RequestPushMessage extends Request {
+@VisibleForTesting
+public class RequestPushMessage extends Request {
 
     private static final String TYPE = "x-bt/message";
     private Bmessage mMsg;
diff --git a/src/com/android/bluetooth/mapclient/obex/RequestSetMessageStatus.java b/src/com/android/bluetooth/mapclient/obex/RequestSetMessageStatus.java
index 9b0123e..cfc3b17 100644
--- a/src/com/android/bluetooth/mapclient/obex/RequestSetMessageStatus.java
+++ b/src/com/android/bluetooth/mapclient/obex/RequestSetMessageStatus.java
@@ -16,6 +16,8 @@
 
 package com.android.bluetooth.mapclient;
 
+import android.util.Log;
+
 import java.io.IOException;
 
 import javax.obex.ClientSession;
@@ -23,10 +25,12 @@
 
 final class RequestSetMessageStatus extends Request {
     public enum StatusIndicator { READ, DELETED }
-
+    private static final String TAG = "RequestSetMessageStatus";
     private static final String TYPE = "x-bt/messageStatus";
+    private static StatusIndicator mStatusInd;
+    private static byte mValue;
 
-    public RequestSetMessageStatus(String handle, StatusIndicator statusInd) {
+    public RequestSetMessageStatus(String handle, StatusIndicator statusInd, byte value) {
         mHeaderSet.setHeader(HeaderSet.TYPE, TYPE);
         mHeaderSet.setHeader(HeaderSet.NAME, handle);
 
@@ -34,8 +38,28 @@
         oap.add(OAP_TAGID_STATUS_INDICATOR,
                 statusInd == StatusIndicator.READ ? STATUS_INDICATOR_READ
                                                   : STATUS_INDICATOR_DELETED);
-        oap.add(OAP_TAGID_STATUS_VALUE, STATUS_YES);
+        oap.add(OAP_TAGID_STATUS_VALUE, value == STATUS_YES ? STATUS_YES
+                                                            : STATUS_NO);
         oap.addToHeaderSet(mHeaderSet);
+        mStatusInd = statusInd;
+        mValue = value;
+    }
+
+    public StatusIndicator getStatusIndicator() {
+        return mStatusInd;
+    }
+
+    public byte getValue() {
+        return mValue;
+    }
+
+    public String getHandle() {
+        try {
+            return (String) mHeaderSet.getHeader(HeaderSet.NAME);
+        } catch (IOException e) {
+            Log.e(TAG, "Unexpected exception while reading handle!", e);
+            return null;
+        }
     }
 
     @Override
diff --git a/src/com/android/bluetooth/opp/BluetoothOppBtEnableActivity.java b/src/com/android/bluetooth/opp/BluetoothOppBtEnableActivity.java
index 6c598dc..4cc28f6 100644
--- a/src/com/android/bluetooth/opp/BluetoothOppBtEnableActivity.java
+++ b/src/com/android/bluetooth/opp/BluetoothOppBtEnableActivity.java
@@ -35,7 +35,6 @@
 import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
 
 import android.bluetooth.AlertActivity;
-import android.content.DialogInterface;
 import android.content.Intent;
 import android.os.Bundle;
 import android.view.View;
@@ -47,8 +46,7 @@
 /**
  * This class is designed to show BT enable confirmation dialog;
  */
-public class BluetoothOppBtEnableActivity extends AlertActivity
-        implements DialogInterface.OnClickListener {
+public class BluetoothOppBtEnableActivity extends AlertActivity {
     private BluetoothOppManager mOppManager;
 
     @Override
@@ -63,8 +61,9 @@
         mAlertBuilder.setIconAttribute(android.R.attr.alertDialogIcon);
         mAlertBuilder.setTitle(getString(R.string.bt_enable_title));
         mAlertBuilder.setView(createView());
-        mAlertBuilder.setPositiveButton(R.string.bt_enable_ok, this);
-        mAlertBuilder.setNegativeButton(R.string.bt_enable_cancel, this);
+        mAlertBuilder.setPositiveButton(R.string.bt_enable_ok,
+                (dialog, which) -> onEnableBluetooth());
+        mAlertBuilder.setNegativeButton(R.string.bt_enable_cancel, (dialog, which) -> finish());
         setupAlert();
     }
 
@@ -78,27 +77,18 @@
         return view;
     }
 
-    @Override
-    public void onClick(DialogInterface dialog, int which) {
-        switch (which) {
-            case DialogInterface.BUTTON_POSITIVE:
-                mOppManager.enableBluetooth(); // this is an asyn call
-                mOppManager.mSendingFlag = true;
+    private void onEnableBluetooth() {
+        mOppManager.enableBluetooth(); // this is an asyn call
+        mOppManager.mSendingFlag = true;
 
-                Toast.makeText(this, getString(R.string.enabling_progress_content),
-                        Toast.LENGTH_SHORT).show();
+        Toast.makeText(this, getString(R.string.enabling_progress_content),
+                Toast.LENGTH_SHORT).show();
 
-                Intent in = new Intent(this, BluetoothOppBtEnablingActivity.class);
-                in.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-                this.startActivity(in);
+        Intent in = new Intent(this, BluetoothOppBtEnablingActivity.class);
+        in.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        this.startActivity(in);
 
-                finish();
-                break;
-
-            case DialogInterface.BUTTON_NEGATIVE:
-                finish();
-                break;
-        }
+        finish();
     }
 
     @Override
diff --git a/src/com/android/bluetooth/opp/BluetoothOppBtErrorActivity.java b/src/com/android/bluetooth/opp/BluetoothOppBtErrorActivity.java
index 2d60b5f..102bc5c 100644
--- a/src/com/android/bluetooth/opp/BluetoothOppBtErrorActivity.java
+++ b/src/com/android/bluetooth/opp/BluetoothOppBtErrorActivity.java
@@ -35,7 +35,6 @@
 import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
 
 import android.bluetooth.AlertActivity;
-import android.content.DialogInterface;
 import android.content.Intent;
 import android.os.Bundle;
 import android.view.View;
@@ -46,8 +45,7 @@
 /**
  * This class is designed to show BT error messages;
  */
-public class BluetoothOppBtErrorActivity extends AlertActivity
-        implements DialogInterface.OnClickListener {
+public class BluetoothOppBtErrorActivity extends AlertActivity {
 
     @Override
     protected void onCreate(Bundle savedInstanceState) {
@@ -62,7 +60,7 @@
         mAlertBuilder.setIconAttribute(android.R.attr.alertDialogIcon);
         mAlertBuilder.setTitle(errorTitle);
         mAlertBuilder.setView(createView(errorContent));
-        mAlertBuilder.setPositiveButton(R.string.bt_error_btn_ok, this);
+        mAlertBuilder.setPositiveButton(R.string.bt_error_btn_ok, (dialog, which) -> {});
         setupAlert();
     }
 
@@ -73,11 +71,4 @@
         return view;
     }
 
-    @Override
-    public void onClick(DialogInterface dialog, int which) {
-        switch (which) {
-            case DialogInterface.BUTTON_POSITIVE:
-                break;
-        }
-    }
 }
diff --git a/src/com/android/bluetooth/opp/BluetoothOppHandoverReceiver.java b/src/com/android/bluetooth/opp/BluetoothOppHandoverReceiver.java
index bae8cb9..6205408 100644
--- a/src/com/android/bluetooth/opp/BluetoothOppHandoverReceiver.java
+++ b/src/com/android/bluetooth/opp/BluetoothOppHandoverReceiver.java
@@ -75,16 +75,16 @@
                 }
                 return;
             }
-        } else if (action.equals(Constants.ACTION_WHITELIST_DEVICE)) {
+        } else if (action.equals(Constants.ACTION_ACCEPTLIST_DEVICE)) {
             BluetoothDevice device =
                     (BluetoothDevice) intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
             if (D) {
-                Log.d(TAG, "Adding " + device + " to whitelist");
+                Log.d(TAG, "Adding " + device + " to acceptlist");
             }
             if (device == null) {
                 return;
             }
-            BluetoothOppManager.getInstance(context).addToWhitelist(device.getAddress());
+            BluetoothOppManager.getInstance(context).addToAcceptlist(device.getAddress());
         } else if (action.equals(Constants.ACTION_STOP_HANDOVER)) {
             int id = intent.getIntExtra(Constants.EXTRA_BT_OPP_TRANSFER_ID, -1);
             if (id != -1) {
diff --git a/src/com/android/bluetooth/opp/BluetoothOppIncomingFileConfirmActivity.java b/src/com/android/bluetooth/opp/BluetoothOppIncomingFileConfirmActivity.java
index 28e6b99..dc90d26 100644
--- a/src/com/android/bluetooth/opp/BluetoothOppIncomingFileConfirmActivity.java
+++ b/src/com/android/bluetooth/opp/BluetoothOppIncomingFileConfirmActivity.java
@@ -57,8 +57,7 @@
 /**
  * This class is designed to ask user to confirm if accept incoming file;
  */
-public class BluetoothOppIncomingFileConfirmActivity extends AlertActivity
-        implements DialogInterface.OnClickListener {
+public class BluetoothOppIncomingFileConfirmActivity extends AlertActivity {
     private static final String TAG = "BluetoothIncomingFileConfirmActivity";
     private static final boolean D = Constants.DEBUG;
     private static final boolean V = Constants.VERBOSE;
@@ -110,8 +109,10 @@
 
         mAlertBuilder.setTitle(getString(R.string.incoming_file_confirm_content));
         mAlertBuilder.setView(createView());
-        mAlertBuilder.setPositiveButton(R.string.incoming_file_confirm_ok, this);
-        mAlertBuilder.setNegativeButton(R.string.incoming_file_confirm_cancel, this);
+        mAlertBuilder.setPositiveButton(R.string.incoming_file_confirm_ok,
+                (dialog, which) -> onIncomingFileConfirmOk());
+        mAlertBuilder.setNegativeButton(R.string.incoming_file_confirm_cancel,
+                (dialog, which) -> onIncomingFileConfirmCancel());
 
         setupAlert();
         if (V) {
@@ -140,31 +141,26 @@
         return view;
     }
 
-    @Override
-    public void onClick(DialogInterface dialog, int which) {
-        switch (which) {
-            case DialogInterface.BUTTON_POSITIVE:
-                if (!mTimeout) {
-                    // Update database
-                    mUpdateValues = new ContentValues();
-                    mUpdateValues.put(BluetoothShare.USER_CONFIRMATION,
-                            BluetoothShare.USER_CONFIRMATION_CONFIRMED);
-                    this.getContentResolver().update(mUri, mUpdateValues, null, null);
+    private void onIncomingFileConfirmOk() {
+        if (!mTimeout) {
+            // Update database
+            mUpdateValues = new ContentValues();
+            mUpdateValues.put(BluetoothShare.USER_CONFIRMATION,
+                    BluetoothShare.USER_CONFIRMATION_CONFIRMED);
+            this.getContentResolver().update(mUri, mUpdateValues, null, null);
 
-                    Toast.makeText(this, getString(R.string.bt_toast_1), Toast.LENGTH_SHORT).show();
-                }
-                break;
-
-            case DialogInterface.BUTTON_NEGATIVE:
-                // Update database
-                mUpdateValues = new ContentValues();
-                mUpdateValues.put(BluetoothShare.USER_CONFIRMATION,
-                        BluetoothShare.USER_CONFIRMATION_DENIED);
-                this.getContentResolver().update(mUri, mUpdateValues, null, null);
-                break;
+            Toast.makeText(this, getString(R.string.bt_toast_1), Toast.LENGTH_SHORT).show();
         }
     }
 
+    private void onIncomingFileConfirmCancel() {
+        // Update database
+        mUpdateValues = new ContentValues();
+        mUpdateValues.put(BluetoothShare.USER_CONFIRMATION,
+                BluetoothShare.USER_CONFIRMATION_DENIED);
+        this.getContentResolver().update(mUri, mUpdateValues, null, null);
+    }
+
     @Override
     public boolean onKeyDown(int keyCode, KeyEvent event) {
         if (keyCode == KeyEvent.KEYCODE_BACK) {
diff --git a/src/com/android/bluetooth/opp/BluetoothOppLauncherActivity.java b/src/com/android/bluetooth/opp/BluetoothOppLauncherActivity.java
index b63bc0c..6cc0cd1 100644
--- a/src/com/android/bluetooth/opp/BluetoothOppLauncherActivity.java
+++ b/src/com/android/bluetooth/opp/BluetoothOppLauncherActivity.java
@@ -194,8 +194,7 @@
                 Log.v(TAG, "Get ACTION_OPEN intent: Uri = " + uri);
             }
 
-            Intent intent1 = new Intent();
-            intent1.setAction(action);
+            Intent intent1 = new Intent(Constants.ACTION_OPEN);
             intent1.setClassName(Constants.THIS_PACKAGE_NAME, BluetoothOppReceiver.class.getName());
             intent1.setDataAndNormalize(uri);
             this.sendBroadcast(intent1);
diff --git a/src/com/android/bluetooth/opp/BluetoothOppManager.java b/src/com/android/bluetooth/opp/BluetoothOppManager.java
index 01b9209..a353d72 100644
--- a/src/com/android/bluetooth/opp/BluetoothOppManager.java
+++ b/src/com/android/bluetooth/opp/BluetoothOppManager.java
@@ -112,10 +112,10 @@
 
     // A list of devices that may send files over OPP to this device
     // without user confirmation. Used for connection handover from forex NFC.
-    private List<Pair<String, Long>> mWhitelist = new ArrayList<Pair<String, Long>>();
+    private List<Pair<String, Long>> mAcceptlist = new ArrayList<Pair<String, Long>>();
 
-    // The time for which the whitelist entries remain valid.
-    private static final int WHITELIST_DURATION_MS = 15000;
+    // The time for which the acceptlist entries remain valid.
+    private static final int ACCEPTLIST_DURATION_MS = 15000;
 
     /**
      * Get singleton instance.
@@ -156,37 +156,37 @@
     }
 
 
-    private void cleanupWhitelist() {
+    private void cleanupAcceptlist() {
         // Removes expired entries
         long curTime = SystemClock.elapsedRealtime();
-        for (Iterator<Pair<String, Long>> iter = mWhitelist.iterator(); iter.hasNext(); ) {
+        for (Iterator<Pair<String, Long>> iter = mAcceptlist.iterator(); iter.hasNext(); ) {
             Pair<String, Long> entry = iter.next();
-            if (curTime - entry.second > WHITELIST_DURATION_MS) {
+            if (curTime - entry.second > ACCEPTLIST_DURATION_MS) {
                 if (V) {
-                    Log.v(TAG, "Cleaning out whitelist entry " + entry.first);
+                    Log.v(TAG, "Cleaning out acceptlist entry " + entry.first);
                 }
                 iter.remove();
             }
         }
     }
 
-    public synchronized void addToWhitelist(String address) {
+    public synchronized void addToAcceptlist(String address) {
         if (address == null) {
             return;
         }
         // Remove any existing entries
-        for (Iterator<Pair<String, Long>> iter = mWhitelist.iterator(); iter.hasNext(); ) {
+        for (Iterator<Pair<String, Long>> iter = mAcceptlist.iterator(); iter.hasNext(); ) {
             Pair<String, Long> entry = iter.next();
             if (entry.first.equals(address)) {
                 iter.remove();
             }
         }
-        mWhitelist.add(new Pair<String, Long>(address, SystemClock.elapsedRealtime()));
+        mAcceptlist.add(new Pair<String, Long>(address, SystemClock.elapsedRealtime()));
     }
 
-    public synchronized boolean isWhitelisted(String address) {
-        cleanupWhitelist();
-        for (Pair<String, Long> entry : mWhitelist) {
+    public synchronized boolean isAcceptlisted(String address) {
+        cleanupAcceptlist();
+        for (Pair<String, Long> entry : mAcceptlist) {
             if (entry.first.equals(address)) {
                 return true;
             }
diff --git a/src/com/android/bluetooth/opp/BluetoothOppNotification.java b/src/com/android/bluetooth/opp/BluetoothOppNotification.java
index 5e4ec49..567fa19 100644
--- a/src/com/android/bluetooth/opp/BluetoothOppNotification.java
+++ b/src/com/android/bluetooth/opp/BluetoothOppNotification.java
@@ -49,6 +49,7 @@
 import android.util.Log;
 
 import com.android.bluetooth.R;
+import com.android.bluetooth.Utils;
 
 import java.util.HashMap;
 
@@ -349,7 +350,8 @@
                 intent.putExtra(Constants.EXTRA_BT_OPP_TRANSFER_ID, item.id);
                 intent.putExtra(Constants.EXTRA_BT_OPP_TRANSFER_PROGRESS, progress);
                 intent.putExtra(Constants.EXTRA_BT_OPP_ADDRESS, item.destination);
-                mContext.sendBroadcast(intent, Constants.HANDOVER_STATUS_PERMISSION);
+                mContext.sendBroadcast(intent, Constants.HANDOVER_STATUS_PERMISSION,
+                        Utils.getTempAllowlistBroadcastOptions());
                 continue;
             }
             // Build the notification object
@@ -390,7 +392,8 @@
             intent.setClassName(Constants.THIS_PACKAGE_NAME, BluetoothOppReceiver.class.getName());
             intent.setDataAndNormalize(Uri.parse(BluetoothShare.CONTENT_URI + "/" + item.id));
 
-            b.setContentIntent(PendingIntent.getBroadcast(mContext, 0, intent, 0));
+            b.setContentIntent(PendingIntent.getBroadcast(mContext, 0, intent,
+                        PendingIntent.FLAG_IMMUTABLE));
             mNotificationMgr.notify(NOTIFICATION_ID_PROGRESS, b.build());
         }
     }
@@ -457,10 +460,14 @@
                                             com.android.internal.R.color
                                                     .system_notification_accent_color,
                                             mContext.getTheme()))
+                            // TODO(b/171825892) Please replace FLAG_MUTABLE_UNAUDITED below
+                            // with either FLAG_IMMUTABLE (recommended) or FLAG_MUTABLE.
                             .setContentIntent(
-                                    PendingIntent.getBroadcast(mContext, 0, contentIntent, 0))
+                                    PendingIntent.getBroadcast(mContext, 0, contentIntent,
+                                        PendingIntent.FLAG_IMMUTABLE))
                             .setDeleteIntent(
-                                    PendingIntent.getBroadcast(mContext, 0, deleteIntent, 0))
+                                    PendingIntent.getBroadcast(mContext, 0, deleteIntent,
+                                        PendingIntent.FLAG_IMMUTABLE))
                             .setWhen(timeStamp)
                             .setLocalOnly(true)
                             .build();
@@ -523,10 +530,13 @@
                                             com.android.internal.R.color
                                                     .system_notification_accent_color,
                                             mContext.getTheme()))
+
                             .setContentIntent(
-                                    PendingIntent.getBroadcast(mContext, 0, contentIntent, 0))
+                                    PendingIntent.getBroadcast(mContext, 0, contentIntent,
+                                        PendingIntent.FLAG_IMMUTABLE))
                             .setDeleteIntent(
-                                    PendingIntent.getBroadcast(mContext, 0, deleteIntent, 0))
+                                    PendingIntent.getBroadcast(mContext, 0, deleteIntent,
+                                        PendingIntent.FLAG_IMMUTABLE))
                             .setWhen(timeStamp)
                             .setLocalOnly(true)
                             .build();
@@ -554,6 +564,7 @@
             BluetoothOppTransferInfo info = new BluetoothOppTransferInfo();
             BluetoothOppUtility.fillRecord(mContext, cursor, info);
             Uri contentUri = Uri.parse(BluetoothShare.CONTENT_URI + "/" + info.mID);
+            String fileNameSafe = info.mFileName.replaceAll("\\s", "_");
             Intent baseIntent = new Intent().setDataAndNormalize(contentUri)
                     .setClassName(Constants.THIS_PACKAGE_NAME,
                             BluetoothOppReceiver.class.getName());
@@ -561,14 +572,19 @@
                     new Notification.Action.Builder(Icon.createWithResource(mContext,
                             R.drawable.ic_decline),
                             mContext.getText(R.string.incoming_file_confirm_cancel),
+                            // TODO(b/171825892) Please replace FLAG_MUTABLE_UNAUDITED below
+                            // with either FLAG_IMMUTABLE (recommended) or FLAG_MUTABLE.
                             PendingIntent.getBroadcast(mContext, 0,
                                     new Intent(baseIntent).setAction(Constants.ACTION_DECLINE),
-                                    0)).build();
+                                    PendingIntent.FLAG_IMMUTABLE)).build();
             Notification.Action actionAccept = new Notification.Action.Builder(
                     Icon.createWithResource(mContext,R.drawable.ic_accept),
                     mContext.getText(R.string.incoming_file_confirm_ok),
+                    // TODO(b/171825892) Please replace FLAG_MUTABLE_UNAUDITED below
+                    // with either FLAG_IMMUTABLE (recommended) or FLAG_MUTABLE.
                     PendingIntent.getBroadcast(mContext, 0,
-                            new Intent(baseIntent).setAction(Constants.ACTION_ACCEPT), 0)).build();
+                            new Intent(baseIntent).setAction(Constants.ACTION_ACCEPT),
+                            PendingIntent.FLAG_IMMUTABLE)).build();
             Notification public_n =
                     new Notification.Builder(mContext, OPP_NOTIFICATION_CHANNEL).setOnlyAlertOnce(
                             true)
@@ -576,11 +592,15 @@
                             .setWhen(info.mTimeStamp)
                             .addAction(actionDecline)
                             .addAction(actionAccept)
+                            // TODO(b/171825892) Please replace FLAG_MUTABLE_UNAUDITED below
+                            // with either FLAG_IMMUTABLE (recommended) or FLAG_MUTABLE.
                             .setContentIntent(PendingIntent.getBroadcast(mContext, 0,
                                     new Intent(baseIntent).setAction(
-                                            Constants.ACTION_INCOMING_FILE_CONFIRM), 0))
+                                            Constants.ACTION_INCOMING_FILE_CONFIRM),
+                                    PendingIntent.FLAG_IMMUTABLE))
                             .setDeleteIntent(PendingIntent.getBroadcast(mContext, 0,
-                                    new Intent(baseIntent).setAction(Constants.ACTION_HIDE), 0))
+                                    new Intent(baseIntent).setAction(Constants.ACTION_HIDE),
+                                    PendingIntent.FLAG_IMMUTABLE))
                             .setColor(mContext.getResources()
                                     .getColor(
                                             com.android.internal.R.color
@@ -588,10 +608,10 @@
                                             mContext.getTheme()))
                             .setContentTitle(mContext.getText(
                                     R.string.incoming_file_confirm_Notification_title))
-                            .setContentText(info.mFileName)
+                            .setContentText(fileNameSafe)
                             .setStyle(new Notification.BigTextStyle().bigText(mContext.getString(
                                     R.string.incoming_file_confirm_Notification_content,
-                                    info.mDeviceName, info.mFileName)))
+                                    info.mDeviceName, fileNameSafe)))
                             .setSubText(Formatter.formatFileSize(mContext, info.mTotalBytes))
                             .setSmallIcon(R.drawable.bt_incomming_file_notification)
                             .setLocalOnly(true)
@@ -601,11 +621,15 @@
                             true)
                             .setOngoing(true)
                             .setWhen(info.mTimeStamp)
+                            // TODO(b/171825892) Please replace FLAG_MUTABLE_UNAUDITED below
+                            // with either FLAG_IMMUTABLE (recommended) or FLAG_MUTABLE.
                             .setContentIntent(PendingIntent.getBroadcast(mContext, 0,
                                     new Intent(baseIntent).setAction(
-                                            Constants.ACTION_INCOMING_FILE_CONFIRM), 0))
+                                            Constants.ACTION_INCOMING_FILE_CONFIRM),
+                                    PendingIntent.FLAG_IMMUTABLE))
                             .setDeleteIntent(PendingIntent.getBroadcast(mContext, 0,
-                                    new Intent(baseIntent).setAction(Constants.ACTION_HIDE), 0))
+                                    new Intent(baseIntent).setAction(Constants.ACTION_HIDE),
+                                    PendingIntent.FLAG_IMMUTABLE))
                             .setColor(mContext.getResources()
                                     .getColor(
                                             com.android.internal.R.color
@@ -613,10 +637,10 @@
                                             mContext.getTheme()))
                             .setContentTitle(mContext.getText(
                                     R.string.incoming_file_confirm_Notification_title))
-                            .setContentText(info.mFileName)
+                            .setContentText(fileNameSafe)
                             .setStyle(new Notification.BigTextStyle().bigText(mContext.getString(
                                     R.string.incoming_file_confirm_Notification_content,
-                                    info.mDeviceName, info.mFileName)))
+                                    info.mDeviceName, fileNameSafe)))
                             .setSubText(Formatter.formatFileSize(mContext, info.mTotalBytes))
                             .setSmallIcon(R.drawable.bt_incomming_file_notification)
                             .setLocalOnly(true)
diff --git a/src/com/android/bluetooth/opp/BluetoothOppObexServerSession.java b/src/com/android/bluetooth/opp/BluetoothOppObexServerSession.java
index 4b59b62..5275dd1 100644
--- a/src/com/android/bluetooth/opp/BluetoothOppObexServerSession.java
+++ b/src/com/android/bluetooth/opp/BluetoothOppObexServerSession.java
@@ -46,6 +46,7 @@
 
 import com.android.bluetooth.BluetoothMetricsProto;
 import com.android.bluetooth.BluetoothObexTransport;
+import com.android.bluetooth.Utils;
 import com.android.bluetooth.btservice.MetricsLogger;
 
 import java.io.FileNotFoundException;
@@ -198,8 +199,8 @@
         } else {
             destination = "FF:FF:FF:00:00:00";
         }
-        boolean isWhitelisted =
-                BluetoothOppManager.getInstance(mContext).isWhitelisted(destination);
+        boolean isAcceptlisted =
+                BluetoothOppManager.getInstance(mContext).isAcceptlisted(destination);
 
         HeaderSet request;
         String name, mimeType;
@@ -259,8 +260,8 @@
             mimeType = mimeType.toLowerCase();
         }
 
-        // Reject anything outside the "whitelist" plus unspecified MIME Types.
-        if (mimeType == null || (!isWhitelisted && !Constants.mimeTypeMatches(mimeType,
+        // Reject anything outside the "acceptlist" plus unspecified MIME Types.
+        if (mimeType == null || (!isAcceptlisted && !Constants.mimeTypeMatches(mimeType,
                 Constants.ACCEPTABLE_SHARE_INBOUND_TYPES))) {
             if (D) {
                 Log.w(TAG, "mimeType is null or in unacceptable list, reject the transfer");
@@ -283,7 +284,7 @@
                     BluetoothShare.USER_CONFIRMATION_AUTO_CONFIRMED);
         }
 
-        if (isWhitelisted) {
+        if (isAcceptlisted) {
             values.put(BluetoothShare.USER_CONFIRMATION,
                     BluetoothShare.USER_CONFIRMATION_HANDOVER_CONFIRMED);
         }
@@ -594,7 +595,7 @@
         } else {
             destination = "FF:FF:FF:00:00:00";
         }
-        boolean isHandover = BluetoothOppManager.getInstance(mContext).isWhitelisted(destination);
+        boolean isHandover = BluetoothOppManager.getInstance(mContext).isAcceptlisted(destination);
         if (isHandover) {
             // Notify the handover requester file transfer has started
             Intent intent = new Intent(Constants.ACTION_HANDOVER_STARTED);
@@ -605,7 +606,8 @@
                         Constants.COUNT_HEADER_UNAVAILABLE);
             }
             intent.putExtra(Constants.EXTRA_BT_OPP_ADDRESS, destination);
-            mContext.sendBroadcast(intent, Constants.HANDOVER_STATUS_PERMISSION);
+            mContext.sendBroadcast(intent, Constants.HANDOVER_STATUS_PERMISSION,
+                    Utils.getTempAllowlistBroadcastOptions());
         }
         mTimestamp = System.currentTimeMillis();
         mNumFilesAttemptedToReceive = 0;
diff --git a/src/com/android/bluetooth/opp/BluetoothOppReceiver.java b/src/com/android/bluetooth/opp/BluetoothOppReceiver.java
index f072d51..970b92b 100644
--- a/src/com/android/bluetooth/opp/BluetoothOppReceiver.java
+++ b/src/com/android/bluetooth/opp/BluetoothOppReceiver.java
@@ -45,6 +45,7 @@
 import android.widget.Toast;
 
 import com.android.bluetooth.R;
+import com.android.bluetooth.Utils;
 
 /**
  * Receives and handles: system broadcasts; Intents from other applications;
@@ -248,7 +249,8 @@
                     handoverIntent.putExtra(Constants.EXTRA_BT_OPP_TRANSFER_STATUS,
                             Constants.HANDOVER_TRANSFER_STATUS_FAILURE);
                 }
-                context.sendBroadcast(handoverIntent, Constants.HANDOVER_STATUS_PERMISSION);
+                context.sendBroadcast(handoverIntent, Constants.HANDOVER_STATUS_PERMISSION,
+                        Utils.getTempAllowlistBroadcastOptions());
                 return;
             }
 
diff --git a/src/com/android/bluetooth/opp/BluetoothOppService.java b/src/com/android/bluetooth/opp/BluetoothOppService.java
index d084dd6..529441f 100644
--- a/src/com/android/bluetooth/opp/BluetoothOppService.java
+++ b/src/com/android/bluetooth/opp/BluetoothOppService.java
@@ -57,6 +57,7 @@
 import com.android.bluetooth.BluetoothObexTransport;
 import com.android.bluetooth.IObexConnectionHandler;
 import com.android.bluetooth.ObexServerSockets;
+import com.android.bluetooth.btservice.AdapterService;
 import com.android.bluetooth.btservice.ProfileService;
 import com.android.bluetooth.sdp.SdpManager;
 import com.android.internal.annotations.VisibleForTesting;
@@ -146,7 +147,7 @@
 
     boolean mAcceptNewConnections;
 
-    private BluetoothAdapter mAdapter;
+    private AdapterService mAdapterService;
 
     private static final String INVISIBLE =
             BluetoothShare.VISIBILITY + "=" + BluetoothShare.VISIBILITY_HIDDEN;
@@ -162,8 +163,8 @@
                     + BluetoothShare.USER_CONFIRMATION_PENDING;
 
     private static final String WHERE_INVISIBLE_UNCONFIRMED =
-            "(" + BluetoothShare.STATUS + ">=" + BluetoothShare.STATUS_SUCCESS + " AND " + INVISIBLE
-                    + ") OR (" + WHERE_CONFIRM_PENDING_INBOUND + ")";
+            "(" + BluetoothShare.STATUS + " > " + BluetoothShare.STATUS_SUCCESS + " AND "
+                    + INVISIBLE + ") OR (" + WHERE_CONFIRM_PENDING_INBOUND + ")";
 
     private static BluetoothOppService sBluetoothOppService;
 
@@ -208,12 +209,6 @@
         IntentFilter filter = new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED);
         registerReceiver(mBluetoothReceiver, filter);
 
-        mAdapter = BluetoothAdapter.getDefaultAdapter();
-        synchronized (BluetoothOppService.this) {
-            if (mAdapter == null) {
-                Log.w(TAG, "Local BT device is not enabled");
-            }
-        }
         if (V) {
             BluetoothOppPreference preference = BluetoothOppPreference.getInstance(this);
             if (preference != null) {
@@ -229,6 +224,7 @@
         if (V) {
             Log.v(TAG, "start()");
         }
+        mAdapterService = AdapterService.getAdapterService();
         mObserver = new BluetoothShareContentObserver();
         getContentResolver().registerContentObserver(BluetoothShare.CONTENT_URI, true, mObserver);
         mNotifier = new BluetoothOppNotification(this);
@@ -252,7 +248,7 @@
 
     private void startListener() {
         if (!mListenStarted) {
-            if (mAdapter.isEnabled()) {
+            if (mAdapterService.isEnabled()) {
                 if (V) {
                     Log.v(TAG, "Starting RfcommListener");
                 }
@@ -358,7 +354,7 @@
                     mNotifier.cancelNotifications();
                     break;
                 case START_LISTENER:
-                    if (mAdapter.isEnabled()) {
+                    if (mAdapterService.isEnabled()) {
                         startSocketListener();
                     }
                     break;
@@ -847,8 +843,10 @@
 
         info.mId = cursor.getInt(cursor.getColumnIndexOrThrow(BluetoothShare._ID));
         if (info.mUri != null) {
-            info.mUri =
-                    Uri.parse(stringFromCursor(info.mUri.toString(), cursor, BluetoothShare.URI));
+            String uriString = stringFromCursor(info.mUri.toString(), cursor, BluetoothShare.URI);
+            if (uriString != null) {
+                info.mUri = Uri.parse(uriString);
+            }
         } else {
             Log.w(TAG, "updateShare() called for ID " + info.mId + " with null URI");
         }
@@ -1162,7 +1160,8 @@
     }
 
     private void stopListeners() {
-        if (mAdapter != null && mOppSdpHandle >= 0 && SdpManager.getDefaultManager() != null) {
+        if (mAdapterService != null && mOppSdpHandle >= 0
+                && SdpManager.getDefaultManager() != null) {
             if (D) {
                 Log.d(TAG, "Removing SDP record mOppSdpHandle :" + mOppSdpHandle);
             }
diff --git a/src/com/android/bluetooth/opp/BluetoothOppTransfer.java b/src/com/android/bluetooth/opp/BluetoothOppTransfer.java
index 1633acb..5946759 100644
--- a/src/com/android/bluetooth/opp/BluetoothOppTransfer.java
+++ b/src/com/android/bluetooth/opp/BluetoothOppTransfer.java
@@ -32,12 +32,14 @@
 
 package com.android.bluetooth.opp;
 
+import android.app.ActivityThread;
 import android.app.NotificationManager;
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothDevice;
 import android.bluetooth.BluetoothSocket;
 import android.bluetooth.BluetoothUuid;
 import android.bluetooth.SdpOppOpsRecord;
+import android.content.Attributable;
 import android.content.BroadcastReceiver;
 import android.content.ContentValues;
 import android.content.Context;
@@ -53,6 +55,7 @@
 import android.util.Log;
 
 import com.android.bluetooth.BluetoothObexTransport;
+import com.android.bluetooth.Utils;
 
 import java.io.IOException;
 
@@ -204,7 +207,10 @@
         public void handleMessage(Message msg) {
             switch (msg.what) {
                 case SOCKET_ERROR_RETRY:
-                    mConnectThread = new SocketConnectThread((BluetoothDevice) msg.obj, true);
+                    BluetoothDevice device = (BluetoothDevice) msg.obj;
+                    Attributable.setAttributionSource(device,
+                            ActivityThread.currentAttributionSource());
+                    mConnectThread = new SocketConnectThread(device, true);
 
                     mConnectThread.start();
                     break;
@@ -738,7 +744,8 @@
                 BluetoothObexTransport transport;
                 transport = new BluetoothObexTransport(mBtSocket);
 
-                BluetoothOppPreference.getInstance(mContext).setName(mDevice, mDevice.getName());
+                BluetoothOppPreference.getInstance(mContext).setName(mDevice,
+                        Utils.getName(mDevice));
 
                 if (V) {
                     Log.v(TAG, "Send transport message " + transport.toString());
@@ -804,7 +811,8 @@
                 }
                 BluetoothObexTransport transport;
                 transport = new BluetoothObexTransport(mBtSocket);
-                BluetoothOppPreference.getInstance(mContext).setName(mDevice, mDevice.getName());
+                BluetoothOppPreference.getInstance(mContext).setName(mDevice,
+                        Utils.getName(mDevice));
                 if (V) {
                     Log.v(TAG, "Send transport message " + transport.toString());
                 }
diff --git a/src/com/android/bluetooth/opp/Constants.java b/src/com/android/bluetooth/opp/Constants.java
index 3bf6cde..a058c61 100644
--- a/src/com/android/bluetooth/opp/Constants.java
+++ b/src/com/android/bluetooth/opp/Constants.java
@@ -69,8 +69,8 @@
     static final String ACTION_OPEN_RECEIVED_FILES =
             "android.btopp.intent.action.OPEN_RECEIVED_FILES";
 
-    /** the intent that whitelists a remote bluetooth device for auto-receive confirmation (NFC) */
-    static final String ACTION_WHITELIST_DEVICE = "android.btopp.intent.action.WHITELIST_DEVICE";
+    /** the intent that acceptlists a remote bluetooth device for auto-receive confirmation (NFC) */
+    static final String ACTION_ACCEPTLIST_DEVICE = "android.btopp.intent.action.ACCEPTLIST_DEVICE";
 
     /** the intent that can be sent by handover requesters to stop a BTOPP transfer */
     static final String ACTION_STOP_HANDOVER = "android.btopp.intent.action.STOP_HANDOVER_TRANSFER";
@@ -172,7 +172,7 @@
 
     /**
      * The MIME type(s) of we could accept from other device.
-     * This is in essence a "white list" of acceptable types.
+     * This is in essence a "acceptlist" of acceptable types.
      * Today, restricted to images, audio, video and certain text types.
      */
     static final String[] ACCEPTABLE_SHARE_INBOUND_TYPES = new String[]{
@@ -185,6 +185,7 @@
             "text/plain",
             "text/html",
             "text/xml",
+            "application/epub+zip",
             "application/zip",
             "application/vnd.ms-excel",
             "application/msword",
@@ -250,8 +251,8 @@
     }
 
     private static boolean mimeTypeMatches(String mimeType, String matchAgainst) {
-        Pattern p =
-                Pattern.compile(matchAgainst.replaceAll("\\*", "\\.\\*"), Pattern.CASE_INSENSITIVE);
+        String matchRegex = matchAgainst.replaceAll("\\+", "\\\\+").replaceAll("\\*", ".*");
+        Pattern p = Pattern.compile(matchRegex, Pattern.CASE_INSENSITIVE);
         return p.matcher(mimeType).matches();
     }
 
diff --git a/src/com/android/bluetooth/pan/BluetoothTetheringNetworkFactory.java b/src/com/android/bluetooth/pan/BluetoothTetheringNetworkFactory.java
index 74b4160..f8f5535 100644
--- a/src/com/android/bluetooth/pan/BluetoothTetheringNetworkFactory.java
+++ b/src/com/android/bluetooth/pan/BluetoothTetheringNetworkFactory.java
@@ -28,6 +28,7 @@
 import android.net.ip.IpClientUtil;
 import android.net.ip.IpClientUtil.WaitForProvisioningCallbacks;
 import android.net.shared.ProvisioningConfiguration;
+import android.os.Binder;
 import android.os.Looper;
 import android.os.RemoteException;
 import android.text.TextUtils;
@@ -64,8 +65,7 @@
         mContext = context;
         mPanService = panService;
 
-        mNetworkCapabilities = new NetworkCapabilities();
-        initNetworkCapabilities();
+        mNetworkCapabilities = initNetworkCapabilities();
         setCapabilityFilter(mNetworkCapabilities);
     }
 
@@ -175,7 +175,7 @@
                                     mNetworkCapabilities, linkProperties, NETWORK_SCORE,
                                     config, getProvider()) {
                                 @Override
-                                public void unwanted() {
+                                public void onNetworkUnwanted() {
                                     BluetoothTetheringNetworkFactory.this.onCancelRequest();
                                 }
                             };
@@ -203,8 +203,13 @@
             mNetworkAgent.unregister();
             mNetworkAgent = null;
         }
-        for (BluetoothDevice device : mPanService.getConnectedDevices()) {
-            mPanService.disconnect(device);
+        final long token = Binder.clearCallingIdentity();
+        try {
+            for (BluetoothDevice device : mPanService.getConnectedDevices()) {
+                mPanService.disconnect(device);
+            }
+        } finally {
+            Binder.restoreCallingIdentity(token);
         }
     }
 
@@ -239,15 +244,18 @@
         terminate();
     }
 
-    private void initNetworkCapabilities() {
-        mNetworkCapabilities.addTransportType(NetworkCapabilities.TRANSPORT_BLUETOOTH);
-        mNetworkCapabilities.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET);
-        mNetworkCapabilities.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED);
-        mNetworkCapabilities.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING);
-        mNetworkCapabilities.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED);
-        // Bluetooth v3 and v4 go up to 24 Mbps.
-        // TODO: Adjust this to actual connection bandwidth.
-        mNetworkCapabilities.setLinkUpstreamBandwidthKbps(24 * 1000);
-        mNetworkCapabilities.setLinkDownstreamBandwidthKbps(24 * 1000);
+    private NetworkCapabilities initNetworkCapabilities() {
+        final NetworkCapabilities.Builder builder = new NetworkCapabilities.Builder()
+                .addTransportType(NetworkCapabilities.TRANSPORT_BLUETOOTH)
+                .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
+                .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)
+                .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING)
+                .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED)
+                .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED)
+                // Bluetooth v3 and v4 go up to 24 Mbps.
+                // TODO: Adjust this to actual connection bandwidth.
+                .setLinkUpstreamBandwidthKbps(24 * 1000)
+                .setLinkDownstreamBandwidthKbps(24 * 1000);
+        return builder.build();
     }
 }
diff --git a/src/com/android/bluetooth/pan/PanService.java b/src/com/android/bluetooth/pan/PanService.java
index ffaf1ce..1deafe0 100644
--- a/src/com/android/bluetooth/pan/PanService.java
+++ b/src/com/android/bluetooth/pan/PanService.java
@@ -16,14 +16,19 @@
 
 package com.android.bluetooth.pan;
 
+import static android.Manifest.permission.BLUETOOTH_CONNECT;
 import static android.Manifest.permission.TETHER_PRIVILEGED;
 
+import android.annotation.RequiresPermission;
+import android.app.ActivityThread;
 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.Attributable;
+import android.content.AttributionSource;
 import android.content.Context;
 import android.content.Intent;
 import android.content.res.Resources.NotFoundException;
@@ -31,6 +36,7 @@
 import android.net.InetAddresses;
 import android.net.InterfaceConfiguration;
 import android.net.LinkAddress;
+import android.net.TetheringManager;
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.INetworkManagementService;
@@ -44,12 +50,14 @@
 import com.android.bluetooth.btservice.AdapterService;
 import com.android.bluetooth.btservice.MetricsLogger;
 import com.android.bluetooth.btservice.ProfileService;
+import com.android.bluetooth.btservice.storage.DatabaseManager;
 import com.android.internal.annotations.VisibleForTesting;
 
 import java.net.InetAddress;
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
+import java.util.Objects;
 
 /**
  * Provides Bluetooth Pan Device profile, as a service in
@@ -61,13 +69,6 @@
     private static final boolean DBG = false;
     private static PanService sPanService;
 
-    private static final String ACTION_TETHERING_STATE_CHANGED =
-            "android.bluetooth.pan.profile.action.TETHERING_STATE_CHANGED";
-    private static final String EXTRA_TETHERING_STATE =
-            "android.bluetooth.pan.extra.TETHERING_STATE";
-    private static final int TETHERING_STATE_OFF = 1;
-    private static final int TETHERING_STATE_ON = 2;
-
     private static final String BLUETOOTH_IFACE_ADDR_START = "192.168.44.1";
     private static final int BLUETOOTH_MAX_PAN_CONNECTIONS = 5;
     private static final int BLUETOOTH_PREFIX_LENGTH = 24;
@@ -79,6 +80,7 @@
     private String mNapIfaceAddr;
     private boolean mNativeAvailable;
 
+    private DatabaseManager mDatabaseManager;
     @VisibleForTesting
     UserManager mUserManager;
 
@@ -121,6 +123,9 @@
 
     @Override
     protected boolean start() {
+        mDatabaseManager = Objects.requireNonNull(AdapterService.getAdapterService().getDatabase(),
+                "DatabaseManager cannot be null when PanService starts");
+
         mPanDevices = new HashMap<BluetoothDevice, BluetoothPanDevice>();
         mBluetoothIfaceAddresses = new ArrayList<String>();
         try {
@@ -181,6 +186,8 @@
             switch (msg.what) {
                 case MESSAGE_CONNECT: {
                     BluetoothDevice device = (BluetoothDevice) msg.obj;
+                    Attributable.setAttributionSource(device,
+                            ActivityThread.currentAttributionSource());
                     if (!connectPanNative(Utils.getByteAddress(device),
                             BluetoothPan.LOCAL_PANU_ROLE, BluetoothPan.REMOTE_NAP_ROLE)) {
                         handlePanDeviceStateChange(device, null, BluetoothProfile.STATE_CONNECTING,
@@ -194,6 +201,8 @@
                 break;
                 case MESSAGE_DISCONNECT: {
                     BluetoothDevice device = (BluetoothDevice) msg.obj;
+                    Attributable.setAttributionSource(device,
+                            ActivityThread.currentAttributionSource());
                     if (!disconnectPanNative(Utils.getByteAddress(device))) {
                         handlePanDeviceStateChange(device, mPanIfName,
                                 BluetoothProfile.STATE_DISCONNECTING, BluetoothPan.LOCAL_PANU_ROLE,
@@ -207,12 +216,17 @@
                 break;
                 case MESSAGE_CONNECT_STATE_CHANGED: {
                     ConnectState cs = (ConnectState) msg.obj;
-                    BluetoothDevice device = getDevice(cs.addr);
+                    final BluetoothDevice device = getAnonymousDevice(cs.addr);
                     // TBD get iface from the msg
                     if (DBG) {
                         Log.d(TAG,
                                 "MESSAGE_CONNECT_STATE_CHANGED: " + device + " state: " + cs.state);
                     }
+                    // It could be null if the connection up is coming when the Bluetooth is turning
+                    // off.
+                    if (device == null) {
+                        break;
+                    }
                     handlePanDeviceStateChange(device, mPanIfName /* iface */,
                             convertHalState(cs.state), cs.local_role, cs.remote_role);
                 }
@@ -237,21 +251,20 @@
             mService = null;
         }
 
-        private PanService getService() {
-            if (!Utils.checkCaller()) {
-                Log.w(TAG, "Pan call not allowed for non-active user");
+        @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
+        private PanService getService(AttributionSource source) {
+            if (!Utils.checkCallerIsSystemOrActiveUser(TAG)
+                    || !Utils.checkServiceAvailable(mService, TAG)
+                    || !Utils.checkConnectPermissionForDataDelivery(mService, source, TAG)) {
                 return null;
             }
-
-            if (mService != null && mService.isAvailable()) {
-                return mService;
-            }
-            return null;
+            return mService;
         }
 
         @Override
-        public boolean connect(BluetoothDevice device) {
-            PanService service = getService();
+        public boolean connect(BluetoothDevice device, AttributionSource source) {
+            Attributable.setAttributionSource(device, source);
+            PanService service = getService(source);
             if (service == null) {
                 return false;
             }
@@ -259,8 +272,9 @@
         }
 
         @Override
-        public boolean disconnect(BluetoothDevice device) {
-            PanService service = getService();
+        public boolean disconnect(BluetoothDevice device, AttributionSource source) {
+            Attributable.setAttributionSource(device, source);
+            PanService service = getService(source);
             if (service == null) {
                 return false;
             }
@@ -268,8 +282,10 @@
         }
 
         @Override
-        public boolean setConnectionPolicy(BluetoothDevice device, int connectionPolicy) {
-            PanService service = getService();
+        public boolean setConnectionPolicy(BluetoothDevice device, int connectionPolicy,
+                AttributionSource source) {
+            Attributable.setAttributionSource(device, source);
+            PanService service = getService(source);
             if (service == null) {
                 return false;
             }
@@ -277,37 +293,19 @@
         }
 
         @Override
-        public int getConnectionState(BluetoothDevice device) {
-            PanService service = getService();
+        public int getConnectionState(BluetoothDevice device, AttributionSource source) {
+            Attributable.setAttributionSource(device, source);
+            PanService service = getService(source);
             if (service == null) {
                 return BluetoothPan.STATE_DISCONNECTED;
             }
             return service.getConnectionState(device);
         }
 
-        private boolean isPanNapOn() {
-            PanService service = getService();
-            if (service == null) {
-                return false;
-            }
-            return service.isPanNapOn();
-        }
-
-        private boolean isPanUOn() {
-            if (DBG) {
-                Log.d(TAG, "isTetheringOn call getPanLocalRoleNative");
-            }
-            PanService service = getService();
-            if (service == null) {
-                return false;
-            }
-            return service.isPanUOn();
-        }
-
         @Override
-        public boolean isTetheringOn() {
+        public boolean isTetheringOn(AttributionSource source) {
             // TODO(BT) have a variable marking the on/off state
-            PanService service = getService();
+            PanService service = getService(source);
             if (service == null) {
                 return false;
             }
@@ -315,19 +313,20 @@
         }
 
         @Override
-        public void setBluetoothTethering(boolean value, String pkgName) {
-            PanService service = getService();
+        public void setBluetoothTethering(boolean value, AttributionSource source) {
+            PanService service = getService(source);
             if (service == null) {
                 return;
             }
-            Log.d(TAG, "setBluetoothTethering: " + value + ", pkgName: " + pkgName
+            Log.d(TAG, "setBluetoothTethering: " + value + ", pkgName: " + source.getPackageName()
                     + ", mTetherOn: " + service.mTetherOn);
-            service.setBluetoothTethering(value, pkgName);
+            service.setBluetoothTethering(value, source.getPackageName(),
+                    source.getAttributionTag());
         }
 
         @Override
-        public List<BluetoothDevice> getConnectedDevices() {
-            PanService service = getService();
+        public List<BluetoothDevice> getConnectedDevices(AttributionSource source) {
+            PanService service = getService(source);
             if (service == null) {
                 return new ArrayList<BluetoothDevice>(0);
             }
@@ -335,8 +334,9 @@
         }
 
         @Override
-        public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
-            PanService service = getService();
+        public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states,
+                AttributionSource source) {
+            PanService service = getService(source);
             if (service == null) {
                 return new ArrayList<BluetoothDevice>(0);
             }
@@ -344,10 +344,8 @@
         }
     }
 
-    ;
-
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED)
     public boolean connect(BluetoothDevice device) {
-        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
         if (mUserManager.isGuestUser()) {
             Log.w(TAG, "Guest user does not have the permission to change the WiFi network");
             return false;
@@ -362,12 +360,12 @@
     }
 
     public boolean disconnect(BluetoothDevice device) {
-        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
         Message msg = mHandler.obtainMessage(MESSAGE_DISCONNECT, device);
         mHandler.sendMessage(msg);
         return true;
     }
 
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED)
     public int getConnectionState(BluetoothDevice device) {
         enforceCallingOrSelfPermission(
                 BLUETOOTH_PRIVILEGED, "Need BLUETOOTH_PRIVILEGED permission");
@@ -378,27 +376,17 @@
         return panDevice.mState;
     }
 
-    boolean isPanNapOn() {
-        if (DBG) {
-            Log.d(TAG, "isTetheringOn call getPanLocalRoleNative");
-        }
-        return (getPanLocalRoleNative() & BluetoothPan.LOCAL_NAP_ROLE) != 0;
-    }
-
-    boolean isPanUOn() {
-        if (DBG) {
-            Log.d(TAG, "isTetheringOn call getPanLocalRoleNative");
-        }
-        return (getPanLocalRoleNative() & BluetoothPan.LOCAL_PANU_ROLE) != 0;
-    }
-
     public boolean isTetheringOn() {
         // TODO(BT) have a variable marking the on/off state
-        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
         return mTetherOn;
     }
 
-    void setBluetoothTethering(boolean value, final String pkgName) {
+    @RequiresPermission(allOf = {
+            android.Manifest.permission.BLUETOOTH_PRIVILEGED,
+            android.Manifest.permission.TETHER_PRIVILEGED,
+    })
+    void setBluetoothTethering(boolean value, final String pkgName,
+            final String callingAttributionTag) {
         if (DBG) {
             Log.d(TAG, "setBluetoothTethering: " + value + ", mTetherOn: " + mTetherOn);
         }
@@ -418,10 +406,10 @@
             for (BluetoothDevice dev : devList) {
                 disconnect(dev);
             }
-            Intent intent = new Intent(ACTION_TETHERING_STATE_CHANGED);
-            intent.putExtra(EXTRA_TETHERING_STATE,
-                    mTetherOn ? TETHERING_STATE_ON : TETHERING_STATE_OFF);
-            sendBroadcast(intent, BLUETOOTH_PERM);
+            Intent intent = new Intent(BluetoothPan.ACTION_TETHERING_STATE_CHANGED);
+            intent.putExtra(BluetoothPan.EXTRA_TETHERING_STATE,
+                    mTetherOn ? BluetoothPan.TETHERING_STATE_ON : BluetoothPan.TETHERING_STATE_OFF);
+            sendBroadcast(intent, null, Utils.getTempAllowlistBroadcastOptions());
         }
     }
 
@@ -440,22 +428,24 @@
      * @param connectionPolicy is the connection policy to set to for this profile
      * @return true if connectionPolicy is set, false on error
      */
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED)
     public boolean setConnectionPolicy(BluetoothDevice device, int connectionPolicy) {
         enforceCallingOrSelfPermission(
                 BLUETOOTH_PRIVILEGED, "Need BLUETOOTH_PRIVILEGED permission");
         if (DBG) {
             Log.d(TAG, "Saved connectionPolicy " + device + " = " + connectionPolicy);
         }
-        boolean setSuccessfully;
-        setSuccessfully = AdapterService.getAdapterService().getDatabase()
-                .setProfileConnectionPolicy(device, BluetoothProfile.PAN, connectionPolicy);
-        if (setSuccessfully && connectionPolicy == BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
+
+        if (!mDatabaseManager.setProfileConnectionPolicy(device, BluetoothProfile.PAN,
+                  connectionPolicy)) {
+            return false;
+        }
+        if (connectionPolicy == BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
             connect(device);
-        } else if (setSuccessfully
-                && connectionPolicy == BluetoothProfile.CONNECTION_POLICY_FORBIDDEN) {
+        } else if (connectionPolicy == BluetoothProfile.CONNECTION_POLICY_FORBIDDEN) {
             disconnect(device);
         }
-        return setSuccessfully;
+        return true;
     }
 
     /**
@@ -471,11 +461,11 @@
      * @hide
      */
     public int getConnectionPolicy(BluetoothDevice device) {
-        enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission");
-        return AdapterService.getAdapterService().getDatabase()
+        return mDatabaseManager
                 .getProfileConnectionPolicy(device, BluetoothProfile.PAN);
     }
 
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED)
     public List<BluetoothDevice> getConnectedDevices() {
         enforceCallingOrSelfPermission(
                 BLUETOOTH_PRIVILEGED, "Need BLUETOOTH_PRIVILEGED permission");
@@ -484,8 +474,8 @@
         return devices;
     }
 
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED)
     List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
-        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
         List<BluetoothDevice> panDevices = new ArrayList<BluetoothDevice>();
 
         for (BluetoothDevice device : mPanDevices.keySet()) {
@@ -652,7 +642,7 @@
         intent.putExtra(BluetoothPan.EXTRA_PREVIOUS_STATE, prevState);
         intent.putExtra(BluetoothPan.EXTRA_STATE, state);
         intent.putExtra(BluetoothPan.EXTRA_LOCAL_ROLE, localRole);
-        sendBroadcast(intent, BLUETOOTH_PERM);
+        sendBroadcast(intent, BLUETOOTH_CONNECT);
     }
 
     private String startTethering(String iface) {
@@ -668,9 +658,8 @@
 
         IBinder b = ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE);
         INetworkManagementService service = INetworkManagementService.Stub.asInterface(b);
-        ConnectivityManager cm =
-                (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
-        String[] bluetoothRegexs = cm.getTetherableBluetoothRegexs();
+        TetheringManager tm = getBaseContext().getSystemService(TetheringManager.class);
+        String[] bluetoothRegexs = tm.getTetherableBluetoothRegexs();
 
         // bring toggle the interfaces
         String[] currentIfaces = new String[0];
@@ -716,13 +705,13 @@
                 service.setInterfaceConfig(iface, ifcg);
 
                 if (enable) {
-                    int tetherStatus = cm.tether(iface);
+                    int tetherStatus = tm.tether(iface);
                     if (tetherStatus != ConnectivityManager.TETHER_ERROR_NO_ERROR) {
                         Log.e(TAG, "Error tethering " + iface + " tetherStatus: " + tetherStatus);
                         return null;
                     }
                 } else {
-                    int untetherStatus = cm.untether(iface);
+                    int untetherStatus = tm.untether(iface);
                     Log.i(TAG, "Untethered: " + iface + " untetherStatus: " + untetherStatus);
                 }
             }
@@ -795,8 +784,4 @@
 
     private native boolean disconnectPanNative(byte[] btAddress);
 
-    private native boolean enablePanNative(int localRole);
-
-    private native int getPanLocalRoleNative();
-
 }
diff --git a/src/com/android/bluetooth/pbap/BluetoothPbapActivity.java b/src/com/android/bluetooth/pbap/BluetoothPbapActivity.java
index 000dd00..b9ebd6a 100644
--- a/src/com/android/bluetooth/pbap/BluetoothPbapActivity.java
+++ b/src/com/android/bluetooth/pbap/BluetoothPbapActivity.java
@@ -61,8 +61,7 @@
  * remote Bluetooth device.
  */
 public class BluetoothPbapActivity extends AlertActivity
-        implements DialogInterface.OnClickListener, Preference.OnPreferenceChangeListener,
-        TextWatcher {
+        implements Preference.OnPreferenceChangeListener, TextWatcher {
     private static final String TAG = "BluetoothPbapActivity";
 
     private static final boolean V = BluetoothPbapService.VERBOSE;
@@ -126,8 +125,10 @@
             case DIALOG_YES_NO_AUTH:
                 mAlertBuilder.setTitle(getString(R.string.pbap_session_key_dialog_header));
                 mAlertBuilder.setView(createView(DIALOG_YES_NO_AUTH));
-                mAlertBuilder.setPositiveButton(android.R.string.ok, this);
-                mAlertBuilder.setNegativeButton(android.R.string.cancel, this);
+                mAlertBuilder.setPositiveButton(android.R.string.ok,
+                        (dialog, which) -> onPositive());
+                mAlertBuilder.setNegativeButton(android.R.string.cancel,
+                        (dialog, which) -> onNegative());
                 setupAlert();
                 changeButtonEnabled(DialogInterface.BUTTON_POSITIVE, false);
                 break;
@@ -164,6 +165,10 @@
     }
 
     private void onPositive() {
+        if (mCurrentDialog == DIALOG_YES_NO_AUTH) {
+            mSessionKey = mKeyView.getText().toString();
+        }
+
         if (!mTimeout) {
             if (mCurrentDialog == DIALOG_YES_NO_AUTH) {
                 sendIntentToReceiver(BluetoothPbapService.AUTH_RESPONSE_ACTION,
@@ -194,24 +199,6 @@
         sendBroadcast(intent);
     }
 
-    @Override
-    public void onClick(DialogInterface dialog, int which) {
-        switch (which) {
-            case DialogInterface.BUTTON_POSITIVE:
-                if (mCurrentDialog == DIALOG_YES_NO_AUTH) {
-                    mSessionKey = mKeyView.getText().toString();
-                }
-                onPositive();
-                break;
-
-            case DialogInterface.BUTTON_NEGATIVE:
-                onNegative();
-                break;
-            default:
-                break;
-        }
-    }
-
     private void onTimeout() {
         mTimeout = true;
         if (mCurrentDialog == DIALOG_YES_NO_AUTH) {
diff --git a/src/com/android/bluetooth/pbap/BluetoothPbapService.java b/src/com/android/bluetooth/pbap/BluetoothPbapService.java
index e3816ec..c521b54 100644
--- a/src/com/android/bluetooth/pbap/BluetoothPbapService.java
+++ b/src/com/android/bluetooth/pbap/BluetoothPbapService.java
@@ -32,11 +32,17 @@
 
 package com.android.bluetooth.pbap;
 
+import static android.Manifest.permission.BLUETOOTH_CONNECT;
+
+import android.annotation.RequiresPermission;
+import android.app.Activity;
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothDevice;
 import android.bluetooth.BluetoothProfile;
 import android.bluetooth.BluetoothSocket;
 import android.bluetooth.IBluetoothPbap;
+import android.content.Attributable;
+import android.content.AttributionSource;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
@@ -58,6 +64,7 @@
 import com.android.bluetooth.Utils;
 import com.android.bluetooth.btservice.AdapterService;
 import com.android.bluetooth.btservice.ProfileService;
+import com.android.bluetooth.btservice.storage.DatabaseManager;
 import com.android.bluetooth.sdp.SdpManager;
 import com.android.bluetooth.util.DevicePolicyUtils;
 import com.android.internal.annotations.VisibleForTesting;
@@ -65,6 +72,7 @@
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
+import java.util.Objects;
 
 public class BluetoothPbapService extends ProfileService implements IObexConnectionHandler {
     private static final String TAG = "BluetoothPbapService";
@@ -117,9 +125,6 @@
     static final int MSG_RELEASE_WAKE_LOCK = 5005;
     static final int MSG_STATE_MACHINE_DONE = 5006;
 
-    static final String BLUETOOTH_PERM = android.Manifest.permission.BLUETOOTH;
-    private static final String BLUETOOTH_ADMIN_PERM = android.Manifest.permission.BLUETOOTH_ADMIN;
-
     static final int START_LISTENER = 1;
     static final int USER_TIMEOUT = 2;
     static final int SHUTDOWN = 3;
@@ -138,6 +143,7 @@
     private static String sLocalPhoneName;
 
     private ObexServerSockets mServerSockets = null;
+    private DatabaseManager mDatabaseManager;
 
     private static final int SDP_PBAP_SERVER_VERSION = 0x0102;
     // PBAP v1.2.3, Sec. 7.1.2: local phonebook and favorites
@@ -364,7 +370,8 @@
                     intent.putExtra(BluetoothDevice.EXTRA_DEVICE, stateMachine.getRemoteDevice());
                     intent.putExtra(BluetoothDevice.EXTRA_ACCESS_REQUEST_TYPE,
                             BluetoothDevice.REQUEST_TYPE_PHONEBOOK_ACCESS);
-                    sendBroadcast(intent, BLUETOOTH_ADMIN_PERM);
+                    sendBroadcast(intent, BLUETOOTH_CONNECT,
+                            Utils.getTempAllowlistBroadcastOptions());
                     stateMachine.sendMessage(PbapStateMachine.REJECTED);
                     break;
                 case MSG_ACQUIRE_WAKE_LOCK:
@@ -426,6 +433,7 @@
      * {@link BluetoothProfile#STATE_CONNECTING}, {@link BluetoothProfile#STATE_CONNECTED}, or
      * {@link BluetoothProfile#STATE_DISCONNECTING}
      */
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED)
     public int getConnectionState(BluetoothDevice device) {
         enforceCallingOrSelfPermission(
                 BLUETOOTH_PRIVILEGED, "Need BLUETOOTH_PRIVILEGED permission");
@@ -443,7 +451,6 @@
     }
 
     List<BluetoothDevice> getConnectedDevices() {
-        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
         if (mPbapStateMachineMap == null) {
             return new ArrayList<>();
         }
@@ -453,7 +460,6 @@
     }
 
     List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
-        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
         List<BluetoothDevice> devices = new ArrayList<>();
         if (mPbapStateMachineMap == null || states == null) {
             return devices;
@@ -484,14 +490,18 @@
      * @param connectionPolicy is the connection policy to set to for this profile
      * @return true if connectionPolicy is set, false on error
      */
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED)
     public boolean setConnectionPolicy(BluetoothDevice device, int connectionPolicy) {
         enforceCallingOrSelfPermission(
                 BLUETOOTH_PRIVILEGED, "Need BLUETOOTH_PRIVILEGED permission");
         if (DEBUG) {
             Log.d(TAG, "Saved connectionPolicy " + device + " = " + connectionPolicy);
         }
-        AdapterService.getAdapterService().getDatabase()
-                .setProfileConnectionPolicy(device, BluetoothProfile.PBAP, connectionPolicy);
+
+        if (!mDatabaseManager.setProfileConnectionPolicy(device, BluetoothProfile.PBAP,
+                  connectionPolicy)) {
+            return false;
+        }
         if (connectionPolicy == BluetoothProfile.CONNECTION_POLICY_FORBIDDEN) {
             disconnect(device);
         }
@@ -514,8 +524,7 @@
         if (device == null) {
             throw new IllegalArgumentException("Null device");
         }
-        enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission");
-        return AdapterService.getAdapterService().getDatabase()
+        return mDatabaseManager
                 .getProfileConnectionPolicy(device, BluetoothProfile.PBAP);
     }
 
@@ -524,7 +533,6 @@
      * @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);
             if (sm != null) {
@@ -551,6 +559,9 @@
         if (VERBOSE) {
             Log.v(TAG, "start()");
         }
+        mDatabaseManager = Objects.requireNonNull(AdapterService.getAdapterService().getDatabase(),
+            "DatabaseManager cannot be null when PbapService starts");
+
         mContext = this;
         mContactsLoaded = false;
         mHandlerThread = new HandlerThread("PbapHandlerThread");
@@ -648,15 +659,14 @@
     private static class PbapBinder extends IBluetoothPbap.Stub implements IProfileServiceBinder {
         private BluetoothPbapService mService;
 
-        private BluetoothPbapService getService() {
-            if (!Utils.checkCaller()) {
-                Log.w(TAG, "not allowed for non-active user");
+        @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
+        private BluetoothPbapService getService(AttributionSource source) {
+            if (!Utils.checkCallerIsSystemOrActiveUser(TAG)
+                    || !Utils.checkServiceAvailable(mService, TAG)
+                    || !Utils.checkConnectPermissionForDataDelivery(mService, source, TAG)) {
                 return null;
             }
-            if (mService != null && mService.isAvailable()) {
-                return mService;
-            }
-            return null;
+            return mService;
         }
 
         PbapBinder(BluetoothPbapService service) {
@@ -672,11 +682,11 @@
         }
 
         @Override
-        public List<BluetoothDevice> getConnectedDevices() {
+        public List<BluetoothDevice> getConnectedDevices(AttributionSource source) {
             if (DEBUG) {
                 Log.d(TAG, "getConnectedDevices");
             }
-            BluetoothPbapService service = getService();
+            BluetoothPbapService service = getService(source);
             if (service == null) {
                 return new ArrayList<>(0);
             }
@@ -684,11 +694,12 @@
         }
 
         @Override
-        public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
+        public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states,
+                AttributionSource source) {
             if (DEBUG) {
                 Log.d(TAG, "getDevicesMatchingConnectionStates");
             }
-            BluetoothPbapService service = getService();
+            BluetoothPbapService service = getService(source);
             if (service == null) {
                 return new ArrayList<>(0);
             }
@@ -696,11 +707,12 @@
         }
 
         @Override
-        public int getConnectionState(BluetoothDevice device) {
+        public int getConnectionState(BluetoothDevice device, AttributionSource source) {
             if (DEBUG) {
                 Log.d(TAG, "getConnectionState: " + device);
             }
-            BluetoothPbapService service = getService();
+            Attributable.setAttributionSource(device, source);
+            BluetoothPbapService service = getService(source);
             if (service == null) {
                 return BluetoothAdapter.STATE_DISCONNECTED;
             }
@@ -708,12 +720,14 @@
         }
 
         @Override
-        public boolean setConnectionPolicy(BluetoothDevice device, int connectionPolicy) {
+        public boolean setConnectionPolicy(BluetoothDevice device, int connectionPolicy,
+                AttributionSource source) {
             if (DEBUG) {
                 Log.d(TAG, "setConnectionPolicy for device: " + device + ", policy:"
                         + connectionPolicy);
             }
-            BluetoothPbapService service = getService();
+            Attributable.setAttributionSource(device, source);
+            BluetoothPbapService service = getService(source);
             if (service == null) {
                 return false;
             }
@@ -721,11 +735,12 @@
         }
 
         @Override
-        public void disconnect(BluetoothDevice device) {
+        public void disconnect(BluetoothDevice device, AttributionSource source) {
             if (DEBUG) {
                 Log.d(TAG, "disconnect");
             }
-            BluetoothPbapService service = getService();
+            Attributable.setAttributionSource(device, source);
+            BluetoothPbapService service = getService(source);
             if (service == null) {
                 return;
             }
@@ -760,6 +775,7 @@
      * @param stateMachine PbapStateMachine which sends the request
      */
     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED)
     public void checkOrGetPhonebookPermission(PbapStateMachine stateMachine) {
         BluetoothDevice device = stateMachine.getRemoteDevice();
         int permission = device.getPhonebookAccessPermission();
@@ -780,7 +796,10 @@
                     BluetoothDevice.REQUEST_TYPE_PHONEBOOK_ACCESS);
             intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
             intent.putExtra(BluetoothDevice.EXTRA_PACKAGE_NAME, this.getPackageName());
-            this.sendOrderedBroadcast(intent, BluetoothPbapService.BLUETOOTH_ADMIN_PERM);
+            this.sendOrderedBroadcast(intent, BLUETOOTH_CONNECT,
+                    Utils.getTempAllowlistBroadcastOptions(), null/* resultReceiver */,
+                    null/* scheduler */, Activity.RESULT_OK/* initialCode */, null/* initialData */,
+                    null/* initialExtras */);
             if (VERBOSE) {
                 Log.v(TAG, "waiting for authorization for connection from: " + device);
             }
diff --git a/src/com/android/bluetooth/pbap/BluetoothPbapUtils.java b/src/com/android/bluetooth/pbap/BluetoothPbapUtils.java
index f8b8e5f..7691c3f 100644
--- a/src/com/android/bluetooth/pbap/BluetoothPbapUtils.java
+++ b/src/com/android/bluetooth/pbap/BluetoothPbapUtils.java
@@ -429,7 +429,12 @@
         int indexData = c.getColumnIndex(Data.DATA1);
         int indexMimeType = c.getColumnIndex(Data.MIMETYPE);
         String contactId, data, mimeType;
+
         while (c.moveToNext()) {
+            if (c.isNull(indexCId)) {
+                Log.w(TAG, "_id column is null. Row was deleted during iteration, skipping");
+                continue;
+            }
             contactId = c.getString(indexCId);
             data = c.getString(indexData);
             mimeType = c.getString(indexMimeType);
diff --git a/src/com/android/bluetooth/pbap/PbapStateMachine.java b/src/com/android/bluetooth/pbap/PbapStateMachine.java
index 254f425..585f6fe 100644
--- a/src/com/android/bluetooth/pbap/PbapStateMachine.java
+++ b/src/com/android/bluetooth/pbap/PbapStateMachine.java
@@ -16,6 +16,8 @@
 
 package com.android.bluetooth.pbap;
 
+import static android.Manifest.permission.BLUETOOTH_CONNECT;
+
 import android.annotation.NonNull;
 import android.app.Notification;
 import android.app.NotificationChannel;
@@ -38,6 +40,7 @@
 import com.android.bluetooth.IObexConnectionHandler;
 import com.android.bluetooth.ObexRejectServer;
 import com.android.bluetooth.R;
+import com.android.bluetooth.Utils;
 import com.android.bluetooth.btservice.MetricsLogger;
 import com.android.internal.util.State;
 import com.android.internal.util.StateMachine;
@@ -75,6 +78,12 @@
     static final int AUTH_KEY_INPUT = 7;
     static final int AUTH_CANCELLED = 8;
 
+    /**
+     * Used to limit PBAP OBEX maximum packet size in order to reduce
+     * transaction time.
+     */
+    private static final int PBAP_OBEX_MAXIMUM_PACKET_SIZE = 8192;
+
     private BluetoothPbapService mService;
     private IObexConnectionHandler mIObexConnectionHandler;
 
@@ -155,7 +164,7 @@
             intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
             intent.addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
             mService.sendBroadcastAsUser(intent, UserHandle.ALL,
-                    BluetoothPbapService.BLUETOOTH_PERM);
+                    BLUETOOTH_CONNECT, Utils.getTempAllowlistBroadcastOptions());
         }
 
         /**
@@ -241,7 +250,8 @@
         private void rejectConnection() {
             mPbapServer =
                     new BluetoothPbapObexServer(mServiceHandler, mService, PbapStateMachine.this);
-            BluetoothObexTransport transport = new BluetoothObexTransport(mConnSocket);
+            BluetoothObexTransport transport = new BluetoothObexTransport(mConnSocket,
+                    PBAP_OBEX_MAXIMUM_PACKET_SIZE, BluetoothObexTransport.PACKET_SIZE_UNSPECIFIED);
             ObexRejectServer server =
                     new ObexRejectServer(ResponseCodes.OBEX_HTTP_UNAVAILABLE, mConnSocket);
             try {
@@ -342,7 +352,8 @@
                 mObexAuth.setChallenged(false);
                 mObexAuth.setCancelled(false);
             }
-            BluetoothObexTransport transport = new BluetoothObexTransport(mConnSocket);
+            BluetoothObexTransport transport = new BluetoothObexTransport(mConnSocket,
+                    PBAP_OBEX_MAXIMUM_PACKET_SIZE, BluetoothObexTransport.PACKET_SIZE_UNSPECIFIED);
             mServerSession = new ServerSession(transport, mPbapServer, mObexAuth);
             // It's ok to just use one wake lock
             // Message MSG_ACQUIRE_WAKE_LOCK is always surrounded by RELEASE. safe.
@@ -377,7 +388,7 @@
             deleteIntent.setClass(mService, BluetoothPbapService.class);
             deleteIntent.setAction(BluetoothPbapService.AUTH_CANCELLED_ACTION);
 
-            String name = mRemoteDevice.getName();
+            String name = Utils.getName(mRemoteDevice);
 
             Notification notification =
                     new Notification.Builder(mService, PBAP_OBEX_NOTIFICATION_CHANNEL).setWhen(
@@ -393,10 +404,14 @@
                                             mService.getTheme()))
                             .setFlag(Notification.FLAG_AUTO_CANCEL, true)
                             .setFlag(Notification.FLAG_ONLY_ALERT_ONCE, true)
+                            // TODO(b/171825892) Please replace FLAG_MUTABLE_UNAUDITED below
+                            // with either FLAG_IMMUTABLE (recommended) or FLAG_MUTABLE.
                             .setContentIntent(
-                                    PendingIntent.getActivity(mService, 0, clickIntent, 0))
+                                    PendingIntent.getActivity(mService, 0, clickIntent,
+                                        PendingIntent.FLAG_IMMUTABLE))
                             .setDeleteIntent(
-                                    PendingIntent.getBroadcast(mService, 0, deleteIntent, 0))
+                                    PendingIntent.getBroadcast(mService, 0, deleteIntent,
+                                        PendingIntent.FLAG_IMMUTABLE))
                             .setLocalOnly(true)
                             .build();
             nm.notify(mNotificationId, notification);
diff --git a/src/com/android/bluetooth/pbapclient/PbapClientService.java b/src/com/android/bluetooth/pbapclient/PbapClientService.java
index 718683e..3bca31c 100644
--- a/src/com/android/bluetooth/pbapclient/PbapClientService.java
+++ b/src/com/android/bluetooth/pbapclient/PbapClientService.java
@@ -18,10 +18,13 @@
 
 import android.accounts.Account;
 import android.accounts.AccountManager;
+import android.annotation.RequiresPermission;
 import android.bluetooth.BluetoothDevice;
 import android.bluetooth.BluetoothHeadsetClient;
 import android.bluetooth.BluetoothProfile;
 import android.bluetooth.IBluetoothPbapClient;
+import android.content.Attributable;
+import android.content.AttributionSource;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
 import android.content.Context;
@@ -31,14 +34,17 @@
 import android.util.Log;
 
 import com.android.bluetooth.R;
+import com.android.bluetooth.Utils;
 import com.android.bluetooth.btservice.AdapterService;
 import com.android.bluetooth.btservice.ProfileService;
+import com.android.bluetooth.btservice.storage.DatabaseManager;
 import com.android.bluetooth.hfpclient.connserv.HfpClientConnectionService;
 import com.android.bluetooth.sdp.SdpManager;
 
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 import java.util.concurrent.ConcurrentHashMap;
 
 /**
@@ -47,8 +53,8 @@
  * @hide
  */
 public class PbapClientService extends ProfileService {
-    private static final boolean DBG = Utils.DBG;
-    private static final boolean VDBG = Utils.VDBG;
+    private static final boolean DBG = com.android.bluetooth.pbapclient.Utils.DBG;
+    private static final boolean VDBG = com.android.bluetooth.pbapclient.Utils.VDBG;
 
     private static final String TAG = "PbapClientService";
     private static final String SERVICE_NAME = "Phonebook Access PCE";
@@ -60,6 +66,8 @@
     private PbapBroadcastReceiver mPbapBroadcastReceiver = new PbapBroadcastReceiver();
     private int mSdpHandle = -1;
 
+    private DatabaseManager mDatabaseManager;
+
     @Override
     public IProfileServiceBinder initBinder() {
         return new BluetoothPbapClientBinder(this);
@@ -70,6 +78,10 @@
         if (VDBG) {
             Log.v(TAG, "onStart");
         }
+
+        mDatabaseManager = Objects.requireNonNull(AdapterService.getAdapterService().getDatabase(),
+                "DatabaseManager cannot be null when PbapClientService starts");
+
         IntentFilter filter = new IntentFilter();
         filter.addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED);
         // delay initial download until after the user is unlocked to add an account.
@@ -227,21 +239,20 @@
             mService = null;
         }
 
-        private PbapClientService getService() {
-            if (!com.android.bluetooth.Utils.checkCaller()) {
-                Log.w(TAG, "PbapClient call not allowed for non-active user");
+        @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
+        private PbapClientService getService(AttributionSource source) {
+            if (!Utils.checkCallerIsSystemOrActiveUser(TAG)
+                    || !Utils.checkServiceAvailable(mService, TAG)
+                    || !Utils.checkConnectPermissionForDataDelivery(mService, source, TAG)) {
                 return null;
             }
-
-            if (mService != null && mService.isAvailable()) {
-                return mService;
-            }
-            return null;
+            return mService;
         }
 
         @Override
-        public boolean connect(BluetoothDevice device) {
-            PbapClientService service = getService();
+        public boolean connect(BluetoothDevice device, AttributionSource source) {
+            Attributable.setAttributionSource(device, source);
+            PbapClientService service = getService(source);
             if (DBG) {
                 Log.d(TAG, "PbapClient Binder connect ");
             }
@@ -253,8 +264,9 @@
         }
 
         @Override
-        public boolean disconnect(BluetoothDevice device) {
-            PbapClientService service = getService();
+        public boolean disconnect(BluetoothDevice device, AttributionSource source) {
+            Attributable.setAttributionSource(device, source);
+            PbapClientService service = getService(source);
             if (service == null) {
                 return false;
             }
@@ -262,8 +274,8 @@
         }
 
         @Override
-        public List<BluetoothDevice> getConnectedDevices() {
-            PbapClientService service = getService();
+        public List<BluetoothDevice> getConnectedDevices(AttributionSource source) {
+            PbapClientService service = getService(source);
             if (service == null) {
                 return new ArrayList<BluetoothDevice>(0);
             }
@@ -271,8 +283,9 @@
         }
 
         @Override
-        public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
-            PbapClientService service = getService();
+        public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states,
+                AttributionSource source) {
+            PbapClientService service = getService(source);
             if (service == null) {
                 return new ArrayList<BluetoothDevice>(0);
             }
@@ -280,8 +293,9 @@
         }
 
         @Override
-        public int getConnectionState(BluetoothDevice device) {
-            PbapClientService service = getService();
+        public int getConnectionState(BluetoothDevice device, AttributionSource source) {
+            Attributable.setAttributionSource(device, source);
+            PbapClientService service = getService(source);
             if (service == null) {
                 return BluetoothProfile.STATE_DISCONNECTED;
             }
@@ -289,8 +303,10 @@
         }
 
         @Override
-        public boolean setConnectionPolicy(BluetoothDevice device, int connectionPolicy) {
-            PbapClientService service = getService();
+        public boolean setConnectionPolicy(BluetoothDevice device, int connectionPolicy,
+                AttributionSource source) {
+            Attributable.setAttributionSource(device, source);
+            PbapClientService service = getService(source);
             if (service == null) {
                 return false;
             }
@@ -298,8 +314,9 @@
         }
 
         @Override
-        public int getConnectionPolicy(BluetoothDevice device) {
-            PbapClientService service = getService();
+        public int getConnectionPolicy(BluetoothDevice device, AttributionSource source) {
+            Attributable.setAttributionSource(device, source);
+            PbapClientService service = getService(source);
             if (service == null) {
                 return BluetoothProfile.CONNECTION_POLICY_UNKNOWN;
             }
@@ -329,6 +346,7 @@
         sPbapClientService = instance;
     }
 
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED)
     public boolean connect(BluetoothDevice device) {
         if (device == null) {
             throw new IllegalArgumentException("Null device");
@@ -360,6 +378,7 @@
      * @param device is the device with which we will disconnect the pbap client profile
      * @return true if we disconnected the pbap client profile, false otherwise
      */
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED)
     public boolean disconnect(BluetoothDevice device) {
         if (device == null) {
             throw new IllegalArgumentException("Null device");
@@ -378,13 +397,11 @@
     }
 
     public List<BluetoothDevice> getConnectedDevices() {
-        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
         int[] desiredStates = {BluetoothProfile.STATE_CONNECTED};
         return getDevicesMatchingConnectionStates(desiredStates);
     }
 
     private List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
-        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
         List<BluetoothDevice> deviceList = new ArrayList<BluetoothDevice>(0);
         for (Map.Entry<BluetoothDevice, PbapClientStateMachine> stateMachineEntry :
                 mPbapClientStateMachineMap
@@ -413,7 +430,6 @@
         if (device == null) {
             throw new IllegalArgumentException("Null device");
         }
-        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
         PbapClientStateMachine pbapClientStateMachine = mPbapClientStateMachineMap.get(device);
         if (pbapClientStateMachine == null) {
             return BluetoothProfile.STATE_DISCONNECTED;
@@ -437,6 +453,7 @@
      * @param connectionPolicy is the connection policy to set to for this profile
      * @return true if connectionPolicy is set, false on error
      */
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED)
     public boolean setConnectionPolicy(BluetoothDevice device, int connectionPolicy) {
         if (device == null) {
             throw new IllegalArgumentException("Null device");
@@ -446,8 +463,11 @@
         if (DBG) {
             Log.d(TAG, "Saved connectionPolicy " + device + " = " + connectionPolicy);
         }
-        AdapterService.getAdapterService().getDatabase()
-            .setProfileConnectionPolicy(device, BluetoothProfile.PBAP_CLIENT, connectionPolicy);
+
+        if (!mDatabaseManager.setProfileConnectionPolicy(device, BluetoothProfile.PBAP_CLIENT,
+                  connectionPolicy)) {
+            return false;
+        }
         if (connectionPolicy == BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
             connect(device);
         } else if (connectionPolicy == BluetoothProfile.CONNECTION_POLICY_FORBIDDEN) {
@@ -468,13 +488,14 @@
      * @return connection policy of the device
      * @hide
      */
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED)
     public int getConnectionPolicy(BluetoothDevice device) {
         if (device == null) {
             throw new IllegalArgumentException("Null device");
         }
         enforceCallingOrSelfPermission(BLUETOOTH_PRIVILEGED,
                 "Need BLUETOOTH_PRIVILEGED permission");
-        return AdapterService.getAdapterService().getDatabase()
+        return mDatabaseManager
                 .getProfileConnectionPolicy(device, BluetoothProfile.PBAP_CLIENT);
     }
 
diff --git a/src/com/android/bluetooth/pbapclient/PbapClientStateMachine.java b/src/com/android/bluetooth/pbapclient/PbapClientStateMachine.java
index 71e6c67..f340128 100644
--- a/src/com/android/bluetooth/pbapclient/PbapClientStateMachine.java
+++ b/src/com/android/bluetooth/pbapclient/PbapClientStateMachine.java
@@ -55,8 +55,10 @@
 import android.os.Process;
 import android.os.UserManager;
 import android.util.Log;
+import static android.Manifest.permission.BLUETOOTH_CONNECT;
 
 import com.android.bluetooth.BluetoothMetricsProto;
+import com.android.bluetooth.Utils;
 import com.android.bluetooth.btservice.MetricsLogger;
 import com.android.bluetooth.btservice.ProfileService;
 import com.android.bluetooth.statemachine.IState;
@@ -67,7 +69,7 @@
 import java.util.List;
 
 final class PbapClientStateMachine extends StateMachine {
-    private static final boolean DBG = Utils.DBG;
+    private static final boolean DBG = false; //Utils.DBG;
     private static final String TAG = "PbapClientStateMachine";
 
     // Messages for handling connect/disconnect requests.
@@ -343,7 +345,7 @@
         intent.putExtra(BluetoothProfile.EXTRA_STATE, state);
         intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
         intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
-        mService.sendBroadcast(intent, ProfileService.BLUETOOTH_PERM);
+        mService.sendBroadcast(intent, BLUETOOTH_CONNECT, Utils.getTempAllowlistBroadcastOptions());
     }
 
     public void disconnect(BluetoothDevice device) {
@@ -432,6 +434,6 @@
 
     public void dump(StringBuilder sb) {
         ProfileService.println(sb, "mCurrentDevice: " + mCurrentDevice.getAddress() + "("
-                + mCurrentDevice.getName() + ") " + this.toString());
+                + Utils.getName(mCurrentDevice) + ") " + this.toString());
     }
 }
diff --git a/src/com/android/bluetooth/sap/SapServer.java b/src/com/android/bluetooth/sap/SapServer.java
index 6885422..5926e3d 100644
--- a/src/com/android/bluetooth/sap/SapServer.java
+++ b/src/com/android/bluetooth/sap/SapServer.java
@@ -196,7 +196,8 @@
             /* Handle local disconnect procedures */
             if (discType == SapMessage.DISC_GRACEFULL) {
                 /* Update the notification to allow the user to initiate a force disconnect */
-                setNotification(SapMessage.DISC_IMMEDIATE, PendingIntent.FLAG_CANCEL_CURRENT);
+                setNotification(SapMessage.DISC_IMMEDIATE,
+                        PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE);
 
             } else if (discType == SapMessage.DISC_IMMEDIATE) {
                 /* Request an immediate disconnect, but start a timer to force disconnect if the
@@ -754,8 +755,10 @@
             sapDisconnectIntent.putExtra(SAP_DISCONNECT_TYPE_EXTRA, discType);
             AlarmManager alarmManager =
                     (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
+            // TODO(b/171825892) Please replace FLAG_MUTABLE_UNAUDITED below
+            // with either FLAG_IMMUTABLE (recommended) or FLAG_MUTABLE.
             mPendingDiscIntent = PendingIntent.getBroadcast(mContext, discType, sapDisconnectIntent,
-                    PendingIntent.FLAG_CANCEL_CURRENT);
+                    PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE);
             alarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,
                     SystemClock.elapsedRealtime() + timeMs, mPendingDiscIntent);
 
diff --git a/src/com/android/bluetooth/sap/SapService.java b/src/com/android/bluetooth/sap/SapService.java
index 4092284..7aa80a6 100644
--- a/src/com/android/bluetooth/sap/SapService.java
+++ b/src/com/android/bluetooth/sap/SapService.java
@@ -1,5 +1,8 @@
 package com.android.bluetooth.sap;
 
+import static android.Manifest.permission.BLUETOOTH_CONNECT;
+
+import android.annotation.RequiresPermission;
 import android.annotation.TargetApi;
 import android.app.AlarmManager;
 import android.app.PendingIntent;
@@ -11,6 +14,8 @@
 import android.bluetooth.BluetoothSocket;
 import android.bluetooth.BluetoothUuid;
 import android.bluetooth.IBluetoothSap;
+import android.content.Attributable;
+import android.content.AttributionSource;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
@@ -76,7 +81,7 @@
     private static final int USER_CONFIRM_TIMEOUT_VALUE = 25000;
 
     private PowerManager.WakeLock mWakeLock = null;
-    private BluetoothAdapter mAdapter;
+    private AdapterService mAdapterService;
     private SocketAcceptThread mAcceptThread = null;
     private BluetoothServerSocket mServerSocket = null;
     private int mSdpHandle = -1;
@@ -118,7 +123,7 @@
     }
 
     private void removeSdpRecord() {
-        if (mAdapter != null && mSdpHandle >= 0 && SdpManager.getDefaultManager() != null) {
+        if (mAdapterService != null && mSdpHandle >= 0 && SdpManager.getDefaultManager() != null) {
             if (VERBOSE) {
                 Log.d(TAG, "Removing SDP record handle: " + mSdpHandle);
             }
@@ -155,7 +160,7 @@
                 // It is mandatory for MSE to support initiation of bonding and encryption.
                 // TODO: Consider reusing the mServerSocket - it is indented to be reused
                 //       for multiple connections.
-                mServerSocket = mAdapter.listenUsingRfcommOn(
+                mServerSocket = BluetoothAdapter.getDefaultAdapter().listenUsingRfcommOn(
                         BluetoothAdapter.SOCKET_CHANNEL_AUTO_STATIC_NO_SDP, true, true);
                 removeSdpRecord();
                 mSdpHandle = SdpManager.getDefaultManager()
@@ -168,10 +173,10 @@
 
             if (!initSocketOK) {
                 // Need to break out of this loop if BT is being turned off.
-                if (mAdapter == null) {
+                if (mAdapterService == null) {
                     break;
                 }
-                int state = mAdapter.getState();
+                int state = mAdapterService.getState();
                 if ((state != BluetoothAdapter.STATE_TURNING_ON) && (state
                         != BluetoothAdapter.STATE_ON)) {
                     Log.w(TAG, "initServerSocket failed as BT is (being) turned off");
@@ -310,7 +315,7 @@
 
         // Last SAP transaction is finished, we start to listen for incoming
         // rfcomm connection again
-        if (mAdapter.isEnabled()) {
+        if (mAdapterService.isEnabled()) {
             startRfcommSocketListener();
         }
     }
@@ -360,7 +365,7 @@
                         break;
                     }
 
-                    sRemoteDeviceName = mRemoteDevice.getName();
+                    sRemoteDeviceName = Utils.getName(mRemoteDevice);
                     // In case getRemoteName failed and return null
                     if (TextUtils.isEmpty(sRemoteDeviceName)) {
                         sRemoteDeviceName = getString(R.string.defaultname);
@@ -392,7 +397,8 @@
 
                         mIsWaitingAuthorization = true;
                         setUserTimeoutAlarm();
-                        sendBroadcast(intent, BLUETOOTH_ADMIN_PERM);
+                        sendBroadcast(intent, BLUETOOTH_CONNECT,
+                                Utils.getTempAllowlistBroadcastOptions());
 
                         if (VERBOSE) {
                             Log.v(TAG, "waiting for authorization for connection from: "
@@ -433,7 +439,7 @@
 
             switch (msg.what) {
                 case START_LISTENER:
-                    if (mAdapter.isEnabled()) {
+                    if (mAdapterService.isEnabled()) {
                         startRfcommSocketListener();
                     }
                     break;
@@ -520,7 +526,7 @@
             intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, prevState);
             intent.putExtra(BluetoothProfile.EXTRA_STATE, mState);
             intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mRemoteDevice);
-            sendBroadcast(intent, BLUETOOTH_PERM);
+            sendBroadcast(intent, BLUETOOTH_CONNECT, Utils.getTempAllowlistBroadcastOptions());
         }
     }
 
@@ -566,7 +572,7 @@
 
     public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
         List<BluetoothDevice> deviceList = new ArrayList<BluetoothDevice>();
-        Set<BluetoothDevice> bondedDevices = mAdapter.getBondedDevices();
+        BluetoothDevice[] bondedDevices = mAdapterService.getBondedDevices();
         int connectionState;
         synchronized (this) {
             for (BluetoothDevice device : bondedDevices) {
@@ -609,6 +615,7 @@
      * @param connectionPolicy is the connection policy to set to for this profile
      * @return true if connectionPolicy is set, false on error
      */
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED)
     public boolean setConnectionPolicy(BluetoothDevice device, int connectionPolicy) {
         if (DEBUG) {
             Log.d(TAG, "Saved connectionPolicy " + device + " = " + connectionPolicy);
@@ -635,6 +642,7 @@
      * @return connection policy of the device
      * @hide
      */
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED)
     public int getConnectionPolicy(BluetoothDevice device) {
         enforceCallingOrSelfPermission(BLUETOOTH_PRIVILEGED,
                 "Need BLUETOOTH_PRIVILEGED permission");
@@ -663,7 +671,7 @@
             Log.w(TAG, "Unable to register sap receiver", e);
         }
         mInterrupted = false;
-        mAdapter = BluetoothAdapter.getDefaultAdapter();
+        mAdapterService = AdapterService.getAdapterService();
         // start RFCOMM listener
         mSessionStatusHandler.sendMessage(mSessionStatusHandler.obtainMessage(START_LISTENER));
         setSapService(this);
@@ -730,7 +738,8 @@
         cancelUserTimeoutAlarm();
         mRemoveTimeoutMsg = true;
         Intent timeoutIntent = new Intent(USER_CONFIRM_TIMEOUT_ACTION);
-        PendingIntent pIntent = PendingIntent.getBroadcast(this, 0, timeoutIntent, 0);
+        PendingIntent pIntent = PendingIntent.getBroadcast(this, 0, timeoutIntent,
+                PendingIntent.FLAG_IMMUTABLE);
         mAlarmManager.set(AlarmManager.RTC_WAKEUP,
                 System.currentTimeMillis() + USER_CONFIRM_TIMEOUT_VALUE, pIntent);
     }
@@ -744,7 +753,8 @@
         }
         if (mRemoveTimeoutMsg) {
             Intent timeoutIntent = new Intent(USER_CONFIRM_TIMEOUT_ACTION);
-            PendingIntent sender = PendingIntent.getBroadcast(this, 0, timeoutIntent, 0);
+            PendingIntent sender = PendingIntent.getBroadcast(this, 0, timeoutIntent,
+                    PendingIntent.FLAG_IMMUTABLE);
             mAlarmManager.cancel(sender);
             mRemoveTimeoutMsg = false;
         }
@@ -756,7 +766,7 @@
         intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
         intent.putExtra(BluetoothDevice.EXTRA_ACCESS_REQUEST_TYPE,
                 BluetoothDevice.REQUEST_TYPE_SIM_ACCESS);
-        sendBroadcast(intent, BLUETOOTH_PERM);
+        sendBroadcast(intent, BLUETOOTH_CONNECT);
     }
 
     private void sendShutdownMessage() {
@@ -764,7 +774,7 @@
         To speed up things, simply delete them. */
         if (mRemoveTimeoutMsg) {
             Intent timeoutIntent = new Intent(USER_CONFIRM_TIMEOUT_ACTION);
-            sendBroadcast(timeoutIntent, BLUETOOTH_PERM);
+            sendBroadcast(timeoutIntent);
             mIsWaitingAuthorization = false;
             cancelUserTimeoutAlarm();
         }
@@ -911,18 +921,14 @@
     private static class SapBinder extends IBluetoothSap.Stub implements IProfileServiceBinder {
         private SapService mService;
 
-        private SapService getService() {
-            if (!Utils.checkCaller()) {
-                Log.w(TAG, "call not allowed for non-active user");
+        @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
+        private SapService getService(AttributionSource source) {
+            if (!Utils.checkCallerIsSystemOrActiveUser(TAG)
+                    || !Utils.checkServiceAvailable(mService, TAG)
+                    || !Utils.checkConnectPermissionForDataDelivery(mService, source, TAG)) {
                 return null;
             }
-
-            if (mService != null && mService.isAvailable()) {
-                mService.enforceCallingOrSelfPermission(BLUETOOTH_PERM,
-                        "Need BLUETOOTH permission");
-                return mService;
-            }
-            return null;
+            return mService;
         }
 
         SapBinder(SapService service) {
@@ -936,19 +942,19 @@
         }
 
         @Override
-        public int getState() {
+        public int getState(AttributionSource source) {
             Log.v(TAG, "getState()");
-            SapService service = getService();
+            SapService service = getService(source);
             if (service == null) {
                 return BluetoothSap.STATE_DISCONNECTED;
             }
-            return getService().getState();
+            return service.getState();
         }
 
         @Override
-        public BluetoothDevice getClient() {
+        public BluetoothDevice getClient(AttributionSource source) {
             Log.v(TAG, "getClient()");
-            SapService service = getService();
+            SapService service = getService(source);
             if (service == null) {
                 return null;
             }
@@ -957,9 +963,10 @@
         }
 
         @Override
-        public boolean isConnected(BluetoothDevice device) {
+        public boolean isConnected(BluetoothDevice device, AttributionSource source) {
             Log.v(TAG, "isConnected()");
-            SapService service = getService();
+            Attributable.setAttributionSource(device, source);
+            SapService service = getService(source);
             if (service == null) {
                 return false;
             }
@@ -968,9 +975,10 @@
         }
 
         @Override
-        public boolean connect(BluetoothDevice device) {
+        public boolean connect(BluetoothDevice device, AttributionSource source) {
             Log.v(TAG, "connect()");
-            SapService service = getService();
+            Attributable.setAttributionSource(device, source);
+            SapService service = getService(source);
             if (service == null) {
                 return false;
             }
@@ -978,9 +986,10 @@
         }
 
         @Override
-        public boolean disconnect(BluetoothDevice device) {
+        public boolean disconnect(BluetoothDevice device, AttributionSource source) {
             Log.v(TAG, "disconnect()");
-            SapService service = getService();
+            Attributable.setAttributionSource(device, source);
+            SapService service = getService(source);
             if (service == null) {
                 return false;
             }
@@ -988,9 +997,9 @@
         }
 
         @Override
-        public List<BluetoothDevice> getConnectedDevices() {
+        public List<BluetoothDevice> getConnectedDevices(AttributionSource source) {
             Log.v(TAG, "getConnectedDevices()");
-            SapService service = getService();
+            SapService service = getService(source);
             if (service == null) {
                 return new ArrayList<BluetoothDevice>(0);
             }
@@ -998,9 +1007,10 @@
         }
 
         @Override
-        public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
+        public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states,
+                AttributionSource source) {
             Log.v(TAG, "getDevicesMatchingConnectionStates()");
-            SapService service = getService();
+            SapService service = getService(source);
             if (service == null) {
                 return new ArrayList<BluetoothDevice>(0);
             }
@@ -1008,9 +1018,10 @@
         }
 
         @Override
-        public int getConnectionState(BluetoothDevice device) {
+        public int getConnectionState(BluetoothDevice device, AttributionSource source) {
             Log.v(TAG, "getConnectionState()");
-            SapService service = getService();
+            Attributable.setAttributionSource(device, source);
+            SapService service = getService(source);
             if (service == null) {
                 return BluetoothProfile.STATE_DISCONNECTED;
             }
@@ -1018,8 +1029,10 @@
         }
 
         @Override
-        public boolean setConnectionPolicy(BluetoothDevice device, int connectionPolicy) {
-            SapService service = getService();
+        public boolean setConnectionPolicy(BluetoothDevice device, int connectionPolicy,
+                AttributionSource source) {
+            Attributable.setAttributionSource(device, source);
+            SapService service = getService(source);
             if (service == null) {
                 return false;
             }
@@ -1027,8 +1040,9 @@
         }
 
         @Override
-        public int getConnectionPolicy(BluetoothDevice device) {
-            SapService service = getService();
+        public int getConnectionPolicy(BluetoothDevice device, AttributionSource source) {
+            Attributable.setAttributionSource(device, source);
+            SapService service = getService(source);
             if (service == null) {
                 return BluetoothProfile.CONNECTION_POLICY_UNKNOWN;
             }
diff --git a/src/com/android/bluetooth/sdp/SdpManager.java b/src/com/android/bluetooth/sdp/SdpManager.java
index d5e3770..099493e 100644
--- a/src/com/android/bluetooth/sdp/SdpManager.java
+++ b/src/com/android/bluetooth/sdp/SdpManager.java
@@ -14,7 +14,10 @@
 */
 package com.android.bluetooth.sdp;
 
+import static android.Manifest.permission.BLUETOOTH_CONNECT;
+
 import android.bluetooth.BluetoothDevice;
+import android.bluetooth.SdpDipRecord;
 import android.bluetooth.SdpMasRecord;
 import android.bluetooth.SdpMnsRecord;
 import android.bluetooth.SdpOppOpsRecord;
@@ -365,6 +368,39 @@
         }
     }
 
+    void sdpDipRecordFoundCallback(int status, byte[] address,
+            byte[] uuid,  int specificationId,
+            int vendorId, int vendorIdSource,
+            int productId, int version,
+            boolean primaryRecord,
+            boolean moreResults) {
+        synchronized(TRACKER_LOCK) {
+            SdpSearchInstance inst = sSdpSearchTracker.getSearchInstance(address, uuid);
+            SdpDipRecord sdpRecord = null;
+            if (inst == null) {
+              Log.e(TAG, "sdpDipRecordFoundCallback: Search instance is NULL");
+              return;
+            }
+            inst.setStatus(status);
+            if (D) {
+                Log.d(TAG, "sdpDipRecordFoundCallback: status " + status);
+            }
+            if (status == AbstractionLayer.BT_STATUS_SUCCESS) {
+                sdpRecord = new SdpDipRecord(specificationId,
+                        vendorId, vendorIdSource,
+                        productId, version,
+                        primaryRecord);
+            }
+            if (D) {
+                Log.d(TAG, "UUID: " + Arrays.toString(uuid));
+            }
+            if (D) {
+                Log.d(TAG, "UUID in parcel: " + ((Utils.byteArrayToUuid(uuid))[0]).toString());
+            }
+            sendSdpIntent(inst, sdpRecord, moreResults);
+        }
+    }
+
     /* TODO: Test or remove! */
     void sdpRecordFoundCallback(int status, byte[] address, byte[] uuid, int sizeRecord,
             byte[] record) {
@@ -455,7 +491,8 @@
          * Keep in mind that the MAP client needs to use this as well,
          * hence to make it call-backs, the MAP client profile needs to be
          * part of the Bluetooth APK. */
-        sAdapterService.sendBroadcast(intent, AdapterService.BLUETOOTH_ADMIN_PERM);
+        sAdapterService.sendBroadcast(intent, BLUETOOTH_CONNECT,
+                Utils.getTempAllowlistBroadcastOptions());
 
         if (!moreResults) {
             //Remove the outstanding UUID request
diff --git a/src/com/android/bluetooth/statemachine/State.java b/src/com/android/bluetooth/statemachine/State.java
index 358938f..a5cecc3 100644
--- a/src/com/android/bluetooth/statemachine/State.java
+++ b/src/com/android/bluetooth/statemachine/State.java
@@ -16,6 +16,7 @@
 
 package com.android.bluetooth.statemachine;
 
+import android.annotation.SuppressLint;
 import android.os.Message;
 
 /**
@@ -23,6 +24,7 @@
  *
  * The class for implementing states in a StateMachine
  */
+@SuppressLint("AndroidFrameworkRequiresPermission")
 public class State implements IState {
 
     /**
diff --git a/src/com/android/bluetooth/telephony/BluetoothCall.java b/src/com/android/bluetooth/telephony/BluetoothCall.java
new file mode 100644
index 0000000..85a0b9a
--- /dev/null
+++ b/src/com/android/bluetooth/telephony/BluetoothCall.java
@@ -0,0 +1,318 @@
+/*
+ * Copyright (C) 2020 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.telephony;
+
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Handler;
+import android.telecom.Call;
+import android.telecom.GatewayInfo;
+import android.telecom.InCallService;
+import android.telecom.PhoneAccountHandle;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * A proxy class of android.telecom.Call that
+ * 1) facilitates testing of the BluetoothInCallService class; We can't mock the final class
+ * Call directly;
+ * 2) Some helper functions, to let Call have same methods as com.android.server.telecom.Call
+ *
+ * This is necessary due to the "final" attribute of the Call class. In order to
+ * test the correct functioning of the BluetoothInCallService class, the final class must be put
+ * into a container that can be mocked correctly.
+ */
+@VisibleForTesting
+public class BluetoothCall {
+
+    private Call mCall;
+
+    public Call getCall() {
+        return mCall;
+    }
+
+    public void setCall(Call call) {
+        mCall = call;
+    }
+
+    public BluetoothCall(Call call) {
+        mCall = call;
+    }
+
+    public String getRemainingPostDialSequence() {
+        return mCall.getRemainingPostDialSequence();
+    }
+
+    public void answer(int videoState) {
+        mCall.answer(videoState);
+    }
+
+    public void deflect(Uri address) {
+        mCall.deflect(address);
+    }
+
+    public void reject(boolean rejectWithMessage, String textMessage) {
+        mCall.reject(rejectWithMessage, textMessage);
+    }
+
+    public void disconnect() {
+        mCall.disconnect();
+    }
+
+    public void hold() {
+        mCall.hold();
+    }
+
+    public void unhold() {
+        mCall.unhold();
+    }
+
+    public void enterBackgroundAudioProcessing() {
+        mCall.enterBackgroundAudioProcessing();
+    }
+
+    public void exitBackgroundAudioProcessing(boolean shouldRing) {
+        mCall.exitBackgroundAudioProcessing(shouldRing);
+    }
+
+    public void playDtmfTone(char digit) {
+        mCall.playDtmfTone(digit);
+    }
+
+    public void stopDtmfTone() {
+        mCall.stopDtmfTone();
+    }
+
+    public void postDialContinue(boolean proceed) {
+        mCall.postDialContinue(proceed);
+    }
+
+    public void phoneAccountSelected(PhoneAccountHandle accountHandle, boolean setDefault) {
+        mCall.phoneAccountSelected(accountHandle, setDefault);
+    }
+
+    public void conference(BluetoothCall callToConferenceWith) {
+        if (callToConferenceWith != null) {
+            mCall.conference(callToConferenceWith.getCall());
+        }
+    }
+
+    public void splitFromConference() {
+        mCall.splitFromConference();
+    }
+
+    public void mergeConference() {
+        mCall.mergeConference();
+    }
+
+    public void swapConference() {
+        mCall.swapConference();
+    }
+
+    public void pullExternalCall() {
+        mCall.pullExternalCall();
+    }
+
+    public void sendCallEvent(String event, Bundle extras) {
+        mCall.sendCallEvent(event, extras);
+    }
+
+    public void sendRttRequest() {
+        mCall.sendRttRequest();
+    }
+
+    public void respondToRttRequest(int id, boolean accept) {
+        mCall.respondToRttRequest(id, accept);
+    }
+
+    public void handoverTo(PhoneAccountHandle toHandle, int videoState, Bundle extras) {
+        mCall.handoverTo(toHandle, videoState, extras);
+    }
+
+    public void stopRtt() {
+        mCall.stopRtt();
+    }
+
+    public void putExtras(Bundle extras) {
+        mCall.putExtras(extras);
+    }
+
+    public void putExtra(String key, boolean value) {
+        mCall.putExtra(key, value);
+    }
+
+    public void putExtra(String key, int value) {
+        mCall.putExtra(key, value);
+    }
+
+    public void putExtra(String key, String value) {
+        mCall.putExtra(key, value);
+    }
+
+    public void removeExtras(List<String> keys) {
+        mCall.removeExtras(keys);
+    }
+
+    public void removeExtras(String... keys) {
+        mCall.removeExtras(keys);
+    }
+
+    public String getParentId() {
+        Call parent = mCall.getParent();
+        if (parent != null) {
+            return parent.getDetails().getTelecomCallId();
+        }
+        return null;
+    }
+
+    public List<String> getChildrenIds() {
+        return getIds(mCall.getChildren());
+    }
+
+    public List<String> getConferenceableCalls() {
+        return getIds(mCall.getConferenceableCalls());
+    }
+
+    public int getState() {
+        return mCall.getState();
+    }
+
+    public List<String> getCannedTextResponses() {
+        return mCall.getCannedTextResponses();
+    }
+
+    public InCallService.VideoCall getVideoCall() {
+        return mCall.getVideoCall();
+    }
+
+    public Call.Details getDetails() {
+        return mCall.getDetails();
+    }
+
+    public Call.RttCall getRttCall() {
+        return mCall.getRttCall();
+    }
+
+    public boolean isRttActive() {
+        return mCall.isRttActive();
+    }
+
+    public void registerCallback(Call.Callback callback) {
+        mCall.registerCallback(callback);
+    }
+
+    public void registerCallback(Call.Callback callback, Handler handler) {
+        mCall.registerCallback(callback, handler);
+    }
+
+    public void unregisterCallback(Call.Callback callback) {
+        mCall.unregisterCallback(callback);
+    }
+
+    public String toString() {
+        String string = mCall.toString();
+        return string == null ? "" : string;
+    }
+
+    public void addListener(Call.Listener listener) {
+        mCall.addListener(listener);
+    }
+
+    public void removeListener(Call.Listener listener) {
+        mCall.removeListener(listener);
+    }
+
+    public String getGenericConferenceActiveChildCallId() {
+        return mCall.getGenericConferenceActiveChildCall().getDetails().getTelecomCallId();
+    }
+
+    public String getContactDisplayName() {
+        return mCall.getDetails().getContactDisplayName();
+    }
+
+    public PhoneAccountHandle getAccountHandle() {
+        return mCall.getDetails().getAccountHandle();
+    }
+
+    public int getVideoState() {
+        return mCall.getDetails().getVideoState();
+    }
+
+    public String getCallerDisplayName() {
+        return mCall.getDetails().getCallerDisplayName();
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (o == null) {
+            return getCall() == null;
+        }
+        return o instanceof BluetoothCall && getCall() == ((BluetoothCall) o).getCall();
+    }
+
+    // helper functions
+    public boolean isSilentRingingRequested() {
+        return getDetails().getExtras() != null
+                && getDetails().getExtras().getBoolean(Call.EXTRA_SILENT_RINGING_REQUESTED);
+    }
+
+    public boolean isConference() {
+        return getDetails().hasProperty(Call.Details.PROPERTY_CONFERENCE);
+    }
+
+    public boolean can(int capability) {
+        return getDetails().can(capability);
+    }
+
+    public Uri getHandle() {
+        return getDetails().getHandle();
+    }
+
+    public GatewayInfo getGatewayInfo() {
+        return getDetails().getGatewayInfo();
+    }
+
+    public boolean isIncoming() {
+        return getDetails().getCallDirection() == Call.Details.DIRECTION_INCOMING;
+    }
+
+    public boolean isExternalCall() {
+        return getDetails().hasProperty(Call.Details.PROPERTY_IS_EXTERNAL_CALL);
+    }
+
+    public String getTelecomCallId() {
+        return getDetails().getTelecomCallId();
+    }
+
+    public boolean wasConferencePreviouslyMerged() {
+        return can(Call.Details.CAPABILITY_SWAP_CONFERENCE) &&
+                !can(Call.Details.CAPABILITY_MERGE_CONFERENCE);
+    }
+
+    public static List<String> getIds(List<Call> calls) {
+        List<String> result = new ArrayList<>();
+        for (Call call : calls) {
+            if (call != null) {
+                result.add(call.getDetails().getTelecomCallId());
+            }
+        }
+        return result;
+    }
+}
diff --git a/src/com/android/bluetooth/telephony/BluetoothInCallService.java b/src/com/android/bluetooth/telephony/BluetoothInCallService.java
new file mode 100644
index 0000000..cfa8982
--- /dev/null
+++ b/src/com/android/bluetooth/telephony/BluetoothInCallService.java
@@ -0,0 +1,1162 @@
+/*
+ * Copyright (C) 2020 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.telephony;
+
+import android.annotation.RequiresPermission;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothHeadset;
+import android.bluetooth.BluetoothProfile;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.PackageManager;
+import android.net.Uri;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.telecom.BluetoothCallQualityReport;
+import android.telecom.Call;
+import android.telecom.CallAudioState;
+import android.telecom.Connection;
+import android.telecom.InCallService;
+import android.telecom.PhoneAccount;
+import android.telecom.PhoneAccountHandle;
+import android.telecom.TelecomManager;
+import android.telecom.VideoProfile;
+import android.telephony.PhoneNumberUtils;
+import android.telephony.TelephonyManager;
+import android.text.TextUtils;
+import android.util.Log;
+
+import com.android.bluetooth.btservice.AdapterService;
+import com.android.bluetooth.hfp.BluetoothHeadsetProxy;
+import com.android.bluetooth.hfp.HeadsetService;
+
+import androidx.annotation.VisibleForTesting;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Used to receive updates about calls from the Telecom component. This service is bound to Telecom
+ * while there exist calls which potentially require UI. This includes ringing (incoming), dialing
+ * (outgoing), and active calls. When the last BluetoothCall is disconnected, Telecom will unbind
+ * to the service triggering InCallActivity (via CallList) to finish soon after.
+ */
+public class BluetoothInCallService extends InCallService {
+
+    private static final String TAG = "BluetoothInCallService";
+
+    // match up with bthf_call_state_t of bt_hf.h
+    private static final int CALL_STATE_ACTIVE = 0;
+    private static final int CALL_STATE_HELD = 1;
+    private static final int CALL_STATE_DIALING = 2;
+    private static final int CALL_STATE_ALERTING = 3;
+    private static final int CALL_STATE_INCOMING = 4;
+    private static final int CALL_STATE_WAITING = 5;
+    private static final int CALL_STATE_IDLE = 6;
+    private static final int CALL_STATE_DISCONNECTED = 7;
+
+    // match up with bthf_call_state_t of bt_hf.h
+    // Terminate all held or set UDUB("busy") to a waiting call
+    private static final int CHLD_TYPE_RELEASEHELD = 0;
+    // Terminate all active calls and accepts a waiting/held call
+    private static final int CHLD_TYPE_RELEASEACTIVE_ACCEPTHELD = 1;
+    // Hold all active calls and accepts a waiting/held call
+    private static final int CHLD_TYPE_HOLDACTIVE_ACCEPTHELD = 2;
+    // Add all held calls to a conference
+    private static final int CHLD_TYPE_ADDHELDTOCONF = 3;
+
+    // Indicates that no BluetoothCall is ringing
+    private static final int DEFAULT_RINGING_ADDRESS_TYPE = 128;
+
+    private int mNumActiveCalls = 0;
+    private int mNumHeldCalls = 0;
+    private int mNumChildrenOfActiveCall = 0;
+    private int mBluetoothCallState = CALL_STATE_IDLE;
+    private String mRingingAddress = "";
+    private int mRingingAddressType = DEFAULT_RINGING_ADDRESS_TYPE;
+    private BluetoothCall mOldHeldCall = null;
+    private boolean mHeadsetUpdatedRecently = false;
+    private boolean mIsDisconnectedTonePlaying = false;
+
+    private static final Object LOCK = new Object();
+    private BluetoothHeadsetProxy mBluetoothHeadset;
+
+    @VisibleForTesting
+    public TelephonyManager mTelephonyManager;
+
+    @VisibleForTesting
+    public TelecomManager mTelecomManager;
+
+    @VisibleForTesting
+    public final HashMap<String, CallStateCallback> mCallbacks = new HashMap<>();
+
+    @VisibleForTesting
+    public final HashMap<String, BluetoothCall> mBluetoothCallHashMap = new HashMap<>();
+
+    // A map from Calls to indexes used to identify calls for CLCC (C* List Current Calls).
+    private final Map<BluetoothCall, Integer> mClccIndexMap = new HashMap<>();
+
+    private static BluetoothInCallService sInstance = null;
+
+    public CallInfo mCallInfo = new CallInfo();
+
+    /**
+     * Listens to connections and disconnections of bluetooth headsets.  We need to save the current
+     * bluetooth headset so that we know where to send BluetoothCall updates.
+     */
+    @VisibleForTesting
+    public BluetoothProfile.ServiceListener mProfileListener =
+            new BluetoothProfile.ServiceListener() {
+                @Override
+                public void onServiceConnected(int profile, BluetoothProfile proxy) {
+                    synchronized (LOCK) {
+                        setBluetoothHeadset(new BluetoothHeadsetProxy((BluetoothHeadset) proxy));
+                        updateHeadsetWithCallState(true /* force */);
+                    }
+                }
+
+                @Override
+                public void onServiceDisconnected(int profile) {
+                    synchronized (LOCK) {
+                        setBluetoothHeadset(null);
+                    }
+                }
+            };
+
+    public class BluetoothAdapterReceiver extends BroadcastReceiver {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            synchronized (LOCK) {
+                if (intent.getAction() != BluetoothAdapter.ACTION_STATE_CHANGED) {
+                    Log.w(TAG, "BluetoothAdapterReceiver: Intent action " + intent.getAction());
+                    return;
+                }
+                int state = intent
+                        .getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR);
+                Log.d(TAG, "Bluetooth Adapter state: " + state);
+                if (state == BluetoothAdapter.STATE_ON) {
+                    queryPhoneState();
+                }
+            }
+        }
+    };
+
+    /**
+     * Receives events for global state changes of the bluetooth adapter.
+     */
+    // TODO: The code is moved from Telecom stack. Since we're running in the BT process itself,
+    // we may be able to simplify this in a future patch.
+    @VisibleForTesting
+    public BluetoothAdapterReceiver mBluetoothAdapterReceiver;
+
+    @VisibleForTesting
+    public class CallStateCallback extends Call.Callback {
+        public int mLastState;
+
+        public CallStateCallback(int initialState) {
+            mLastState = initialState;
+        }
+
+        public int getLastState() {
+            return mLastState;
+        }
+
+        public void onStateChanged(BluetoothCall call, int state) {
+            if (mCallInfo.isNullCall(call)) {
+                return;
+            }
+            if (call.isExternalCall()) {
+                return;
+            }
+
+            // If a BluetoothCall is being put on hold because of a new connecting call, ignore the
+            // CONNECTING since the BT state update needs to send out the numHeld = 1 + dialing
+            // state atomically.
+            // When the BluetoothCall later transitions to DIALING/DISCONNECTED we will then
+            // send out the aggregated update.
+            if (getLastState() == Call.STATE_ACTIVE && state == Call.STATE_HOLDING) {
+                for (BluetoothCall otherCall : mCallInfo.getBluetoothCalls()) {
+                    if (otherCall.getState() == Call.STATE_CONNECTING) {
+                        mLastState = state;
+                        return;
+                    }
+                }
+            }
+
+            // To have an active BluetoothCall and another dialing at the same time is an invalid BT
+            // state. We can assume that the active BluetoothCall will be automatically held
+            // which will send another update at which point we will be in the right state.
+            BluetoothCall activeCall = mCallInfo.getActiveCall();
+            if (!mCallInfo.isNullCall(activeCall)
+                    && getLastState() == Call.STATE_CONNECTING
+                    && (state == Call.STATE_DIALING || state == Call.STATE_PULLING_CALL)) {
+                mLastState = state;
+                return;
+            }
+            mLastState = state;
+            updateHeadsetWithCallState(false /* force */);
+        }
+
+        @Override
+        public void onStateChanged(Call call, int state) {
+            super.onStateChanged(call, state);
+            onStateChanged(getBluetoothCallById(call.getDetails().getTelecomCallId()), state);
+        }
+
+        public void onDetailsChanged(BluetoothCall call, Call.Details details) {
+            if (mCallInfo.isNullCall(call)) {
+                return;
+            }
+            if (call.isExternalCall()) {
+                onCallRemoved(call);
+            } else {
+                onCallAdded(call);
+            }
+        }
+
+        @Override
+        public void onDetailsChanged(Call call, Call.Details details) {
+            super.onDetailsChanged(call, details);
+            onDetailsChanged(getBluetoothCallById(call.getDetails().getTelecomCallId()), details);
+        }
+
+        public void onParentChanged(BluetoothCall call) {
+            if (call.isExternalCall()) {
+                return;
+            }
+            if (call.getParentId() != null) {
+                // If this BluetoothCall is newly conferenced, ignore the callback.
+                // We only care about the one sent for the parent conference call.
+                Log.d(TAG,
+                        "Ignoring onIsConferenceChanged from child BluetoothCall with new parent");
+                return;
+            }
+            updateHeadsetWithCallState(false /* force */);
+        }
+
+        @Override
+        public void onParentChanged(Call call, Call parent) {
+            super.onParentChanged(call, parent);
+            onParentChanged(
+                    getBluetoothCallById(call.getDetails().getTelecomCallId()));
+        }
+
+        public void onChildrenChanged(BluetoothCall call, List<BluetoothCall> children) {
+            if (call.isExternalCall()) {
+                return;
+            }
+            if (call.getChildrenIds().size() == 1) {
+                // If this is a parent BluetoothCall with only one child,
+                // ignore the callback as well since the minimum number of child calls to
+                // start a conference BluetoothCall is 2. We expect this to be called again
+                // when the parent BluetoothCall has another child BluetoothCall added.
+                Log.d(TAG,
+                        "Ignoring onIsConferenceChanged from parent with only one child call");
+                return;
+            }
+            updateHeadsetWithCallState(false /* force */);
+        }
+
+        @Override
+        public void onChildrenChanged(Call call, List<Call> children) {
+            super.onChildrenChanged(call, children);
+            onChildrenChanged(
+                    getBluetoothCallById(call.getDetails().getTelecomCallId()),
+                    getBluetoothCallsByIds(BluetoothCall.getIds(children)));
+        }
+    }
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        Log.i(TAG, "onBind. Intent: " + intent);
+        BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
+        if (bluetoothAdapter == null || !bluetoothAdapter.isEnabled()) {
+            Log.i(TAG, "Bluetooth is off");
+            ComponentName componentName
+                    = new ComponentName(getPackageName(), this.getClass().getName());
+            getPackageManager().setComponentEnabledSetting(
+                    componentName,
+                    PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
+                    PackageManager.DONT_KILL_APP);
+            return null;
+        }
+        IBinder binder = super.onBind(intent);
+        mTelephonyManager = getSystemService(TelephonyManager.class);
+        mTelecomManager = getSystemService(TelecomManager.class);
+        return binder;
+    }
+
+    @Override
+    public boolean onUnbind(Intent intent) {
+        Log.i(TAG, "onUnbind. Intent: " + intent);
+        BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
+        if (bluetoothAdapter == null || !bluetoothAdapter.isEnabled()) {
+            Log.i(TAG, "Bluetooth is off when unbind, disable BluetoothInCallService");
+            AdapterService adapterService = AdapterService.getAdapterService();
+            adapterService.enableBluetoothInCallService(false);
+
+        }
+        return super.onUnbind(intent);
+    }
+
+    public BluetoothInCallService() {
+        Log.i(TAG, "BluetoothInCallService is created");
+        sInstance = this;
+    }
+
+    public static BluetoothInCallService getInstance() {
+        return sInstance;
+    }
+
+    @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
+    protected void enforceModifyPermission() {
+        enforceCallingOrSelfPermission(android.Manifest.permission.MODIFY_PHONE_STATE, null);
+    }
+
+    @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
+    public boolean answerCall() {
+        synchronized (LOCK) {
+            enforceModifyPermission();
+            Log.i(TAG, "BT - answering call");
+            BluetoothCall call = mCallInfo.getRingingOrSimulatedRingingCall();
+            if (mCallInfo.isNullCall(call)) {
+                return false;
+            }
+            call.answer(VideoProfile.STATE_AUDIO_ONLY);
+            return true;
+        }
+    }
+
+    @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
+    public boolean hangupCall() {
+        synchronized (LOCK) {
+            enforceModifyPermission();
+            Log.i(TAG, "BT - hanging up call");
+            BluetoothCall call = mCallInfo.getForegroundCall();
+            if (mCallInfo.isNullCall(call)) {
+                return false;
+            }
+            // release the parent if there is a conference call
+            BluetoothCall conferenceCall = getBluetoothCallById(call.getParentId());
+            if (!mCallInfo.isNullCall(conferenceCall)
+                    && conferenceCall.getState() == Call.STATE_ACTIVE) {
+                Log.i(TAG, "BT - hanging up conference call");
+                call = conferenceCall;
+            }
+            if (call.getState() == Call.STATE_RINGING) {
+                call.reject(false, "");
+            } else {
+                call.disconnect();
+            }
+            return true;
+        }
+    }
+
+    @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
+    public boolean sendDtmf(int dtmf) {
+        synchronized (LOCK) {
+            enforceModifyPermission();
+            Log.i(TAG, "BT - sendDtmf " + dtmf);
+            BluetoothCall call = mCallInfo.getForegroundCall();
+            if (mCallInfo.isNullCall(call)) {
+                return false;
+            }
+            // TODO: Consider making this a queue instead of starting/stopping
+            // in quick succession.
+            call.playDtmfTone((char) dtmf);
+            call.stopDtmfTone();
+            return true;
+        }
+    }
+
+    @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
+    public String getNetworkOperator()  {
+        synchronized (LOCK) {
+            enforceModifyPermission();
+            Log.i(TAG, "getNetworkOperator");
+            PhoneAccount account = mCallInfo.getBestPhoneAccount();
+            if (account != null && account.getLabel() != null) {
+                return account.getLabel().toString();
+            }
+            // Finally, just get the network name from telephony.
+            return mTelephonyManager.getNetworkOperatorName();
+        }
+    }
+
+    @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
+    public String getSubscriberNumber() {
+        synchronized (LOCK) {
+            enforceModifyPermission();
+            Log.i(TAG, "getSubscriberNumber");
+            String address = null;
+            PhoneAccount account = mCallInfo.getBestPhoneAccount();
+            if (account != null) {
+                Uri addressUri = account.getAddress();
+                if (addressUri != null) {
+                    address = addressUri.getSchemeSpecificPart();
+                }
+            }
+            if (TextUtils.isEmpty(address)) {
+                address = mTelephonyManager.getLine1Number();
+                if (address == null) address = "";
+            }
+            return address;
+        }
+    }
+
+    @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
+    public boolean listCurrentCalls() {
+        synchronized (LOCK) {
+            enforceModifyPermission();
+            // only log if it is after we recently updated the headset state or else it can
+            // clog the android log since this can be queried every second.
+            boolean logQuery = mHeadsetUpdatedRecently;
+            mHeadsetUpdatedRecently = false;
+
+            if (logQuery) {
+                Log.i(TAG, "listcurrentCalls");
+            }
+
+            sendListOfCalls(logQuery);
+            return true;
+        }
+    }
+
+    @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
+    public boolean queryPhoneState() {
+        synchronized (LOCK) {
+            enforceModifyPermission();
+            Log.i(TAG, "queryPhoneState");
+            updateHeadsetWithCallState(true);
+            return true;
+        }
+    }
+
+    @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
+    public boolean processChld(int chld) {
+        synchronized (LOCK) {
+            enforceModifyPermission();
+            long token = Binder.clearCallingIdentity();
+            Log.i(TAG, "processChld " + chld);
+            return _processChld(chld);
+        }
+    }
+
+    public void onCallAdded(BluetoothCall call) {
+        if (call.isExternalCall()) {
+            return;
+        }
+        if (!mBluetoothCallHashMap.containsKey(call.getTelecomCallId())) {
+            Log.d(TAG, "onCallAdded");
+            CallStateCallback callback = new CallStateCallback(call.getState());
+            mCallbacks.put(call.getTelecomCallId(), callback);
+            call.registerCallback(callback);
+
+            mBluetoothCallHashMap.put(call.getTelecomCallId(), call);
+            updateHeadsetWithCallState(false /* force */);
+        }
+    }
+
+    public void sendBluetoothCallQualityReport(
+            long timestamp,
+            int rssi,
+            int snr,
+            int retransmissionCount,
+            int packetsNotReceiveCount,
+            int negativeAcknowledgementCount) {
+        BluetoothCall call = mCallInfo.getForegroundCall();
+        if (mCallInfo.isNullCall(call)) {
+            Log.w(TAG, "No foreground call while trying to send BQR");
+            return;
+        }
+        Bundle b = new Bundle();
+        b.putParcelable(
+                BluetoothCallQualityReport.EXTRA_BLUETOOTH_CALL_QUALITY_REPORT,
+                new BluetoothCallQualityReport.Builder()
+                        .setSentTimestampMillis(timestamp)
+                        .setChoppyVoice(true)
+                        .setRssiDbm(rssi)
+                        .setSnrDb(snr)
+                        .setRetransmittedPacketsCount(retransmissionCount)
+                        .setPacketsNotReceivedCount(packetsNotReceiveCount)
+                        .setPacketsNotReceivedCount(negativeAcknowledgementCount)
+                        .build());
+        call.sendCallEvent(
+                BluetoothCallQualityReport.EVENT_BLUETOOTH_CALL_QUALITY_REPORT, b);
+    }
+
+    @Override
+    public void onCallAdded(Call call) {
+        super.onCallAdded(call);
+        onCallAdded(new BluetoothCall(call));
+    }
+
+    public void onCallRemoved(BluetoothCall call) {
+        if (call.isExternalCall()) {
+            return;
+        }
+        Log.d(TAG, "onCallRemoved");
+        CallStateCallback callback = getCallback(call);
+        if (callback != null) {
+            call.unregisterCallback(callback);
+        }
+
+        if (mBluetoothCallHashMap.containsKey(call.getTelecomCallId())) {
+            mBluetoothCallHashMap.remove(call.getTelecomCallId());
+        }
+
+        mClccIndexMap.remove(call);
+        updateHeadsetWithCallState(false /* force */);
+    }
+
+    @Override
+    public void onCallRemoved(Call call) {
+        super.onCallRemoved(call);
+        BluetoothCall bluetoothCall = getBluetoothCallById(call.getDetails().getTelecomCallId());
+        if (bluetoothCall == null) {
+            Log.w(TAG, "onCallRemoved, BluetoothCall is removed before registered");
+            return;
+        }
+        onCallRemoved(bluetoothCall);
+    }
+
+    @Override
+    public void onCallAudioStateChanged(CallAudioState audioState) {
+        super.onCallAudioStateChanged(audioState);
+        Log.d(TAG, "onCallAudioStateChanged, audioState == " + audioState);
+    }
+
+
+    @Override
+    public void onCreate() {
+        Log.d(TAG, "onCreate");
+        super.onCreate();
+        BluetoothAdapter.getDefaultAdapter()
+                .getProfileProxy(this, mProfileListener, BluetoothProfile.HEADSET);
+        mBluetoothAdapterReceiver = new BluetoothAdapterReceiver();
+        IntentFilter intentFilter = new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED);
+        registerReceiver(mBluetoothAdapterReceiver, intentFilter);
+    }
+
+    @Override
+    public void onDestroy() {
+        Log.d(TAG, "onDestroy");
+        if (mBluetoothAdapterReceiver != null) {
+            unregisterReceiver(mBluetoothAdapterReceiver);
+            mBluetoothAdapterReceiver = null;
+        }
+        sInstance = null;
+        super.onDestroy();
+    }
+
+    private void sendListOfCalls(boolean shouldLog) {
+        Collection<BluetoothCall> calls = mCallInfo.getBluetoothCalls();
+        for (BluetoothCall call : calls) {
+            // We don't send the parent conference BluetoothCall to the bluetooth device.
+            // We do, however want to send conferences that have no children to the bluetooth
+            // device (e.g. IMS Conference).
+            if (!call.isConference()
+                    || (call.isConference()
+                            && call.can(Connection.CAPABILITY_CONFERENCE_HAS_NO_CHILDREN))) {
+                sendClccForCall(call, shouldLog);
+            }
+        }
+        sendClccEndMarker();
+    }
+
+    private void sendClccEndMarker() {
+        // End marker is recognized with an index value of 0. All other parameters are ignored.
+        if (mBluetoothHeadset != null) {
+            mBluetoothHeadset.clccResponse(0 /* index */, 0, 0, 0, false, null, 0);
+        }
+    }
+
+    /**
+     * Sends a single clcc (C* List Current Calls) event for the specified call.
+     */
+    private void sendClccForCall(BluetoothCall call, boolean shouldLog) {
+        boolean isForeground = mCallInfo.getForegroundCall() == call;
+        int state = getBtCallState(call, isForeground);
+        boolean isPartOfConference = false;
+        boolean isConferenceWithNoChildren = call.isConference()
+                && call.can(Connection.CAPABILITY_CONFERENCE_HAS_NO_CHILDREN);
+
+        if (state == CALL_STATE_IDLE) {
+            return;
+        }
+
+        BluetoothCall conferenceCall = getBluetoothCallById(call.getParentId());
+        if (!mCallInfo.isNullCall(conferenceCall)) {
+            isPartOfConference = true;
+
+            // Run some alternative states for Conference-level merge/swap support.
+            // Basically, if BluetoothCall supports swapping or merging at the conference-level,
+            // then we need to expose the calls as having distinct states
+            // (ACTIVE vs CAPABILITY_HOLD) or
+            // the functionality won't show up on the bluetooth device.
+
+            // Before doing any special logic, ensure that we are dealing with an
+            // ACTIVE BluetoothCall and that the conference itself has a notion of
+            // the current "active" child call.
+            BluetoothCall activeChild = getBluetoothCallById(
+                    conferenceCall.getGenericConferenceActiveChildCallId());
+            if (state == CALL_STATE_ACTIVE && !mCallInfo.isNullCall(activeChild)) {
+                // Reevaluate state if we can MERGE or if we can SWAP without previously having
+                // MERGED.
+                boolean shouldReevaluateState =
+                        conferenceCall.can(Connection.CAPABILITY_MERGE_CONFERENCE)
+                                || (conferenceCall.can(Connection.CAPABILITY_SWAP_CONFERENCE)
+                                        && !conferenceCall.wasConferencePreviouslyMerged());
+
+                if (shouldReevaluateState) {
+                    isPartOfConference = false;
+                    if (call == activeChild) {
+                        state = CALL_STATE_ACTIVE;
+                    } else {
+                        // At this point we know there is an "active" child and we know that it is
+                        // not this call, so set it to HELD instead.
+                        state = CALL_STATE_HELD;
+                    }
+                }
+            }
+            if (conferenceCall.getState() == Call.STATE_HOLDING
+                    && conferenceCall.can(Connection.CAPABILITY_MANAGE_CONFERENCE)) {
+                // If the parent IMS CEP conference BluetoothCall is on hold, we should mark
+                // this BluetoothCall as being on hold regardless of what the other
+                // children are doing.
+                state = CALL_STATE_HELD;
+            }
+        } else if (isConferenceWithNoChildren) {
+            // Handle the special case of an IMS conference BluetoothCall without conference
+            // event package support.
+            // The BluetoothCall will be marked as a conference, but the conference will not have
+            // child calls where conference event packages are not used by the carrier.
+            isPartOfConference = true;
+        }
+
+        int index = getIndexForCall(call);
+        int direction = call.isIncoming() ? 1 : 0;
+        final Uri addressUri;
+        if (call.getGatewayInfo() != null) {
+            addressUri = call.getGatewayInfo().getOriginalAddress();
+        } else {
+            addressUri = call.getHandle();
+        }
+
+        String address = addressUri == null ? null : addressUri.getSchemeSpecificPart();
+        if (address != null) {
+            address = PhoneNumberUtils.stripSeparators(address);
+        }
+
+        int addressType = address == null ? -1 : PhoneNumberUtils.toaFromString(address);
+
+        if (shouldLog) {
+            Log.i(TAG, "sending clcc for BluetoothCall "
+                            + index + ", "
+                            + direction + ", "
+                            + state + ", "
+                            + isPartOfConference + ", "
+                            + addressType);
+        }
+
+        if (mBluetoothHeadset == null) {
+            Log.w(TAG, "mBluetoothHeasdset is null when sending clcc for BluetoothCall "
+                    + index + ", "
+                    + direction + ", "
+                    + state + ", "
+                    + isPartOfConference + ", "
+                    + addressType);
+        } else {
+            mBluetoothHeadset.clccResponse(
+                    index, direction, state, 0, isPartOfConference, address, addressType);
+        }
+    }
+
+    /**
+     * Returns the caches index for the specified call.  If no such index exists, then an index is
+     * given (smallest number starting from 1 that isn't already taken).
+     */
+    private int getIndexForCall(BluetoothCall call) {
+        if (mClccIndexMap.containsKey(call)) {
+            return mClccIndexMap.get(call);
+        }
+
+        int i = 1;  // Indexes for bluetooth clcc are 1-based.
+        while (mClccIndexMap.containsValue(i)) {
+            i++;
+        }
+
+        // NOTE: Indexes are removed in {@link #onCallRemoved}.
+        mClccIndexMap.put(call, i);
+        return i;
+    }
+
+    private boolean _processChld(int chld) {
+        BluetoothCall activeCall = mCallInfo.getActiveCall();
+        BluetoothCall ringingCall = mCallInfo.getRingingOrSimulatedRingingCall();
+        if (ringingCall == null) {
+            Log.i(TAG, "asdf ringingCall null");
+        } else {
+            Log.i(TAG, "asdf ringingCall not null " + ringingCall.hashCode());
+        }
+
+        BluetoothCall heldCall = mCallInfo.getHeldCall();
+
+        Log.i(TAG, "Active: " + activeCall
+                + " Ringing: " + ringingCall
+                + " Held: " + heldCall);
+        Log.i(TAG, "asdf chld " + chld);
+
+        if (chld == CHLD_TYPE_RELEASEHELD) {
+            Log.i(TAG, "asdf CHLD_TYPE_RELEASEHELD");
+            if (!mCallInfo.isNullCall(ringingCall)) {
+                Log.i(TAG, "asdf reject " + ringingCall.hashCode());
+                ringingCall.reject(false, null);
+                return true;
+            } else if (!mCallInfo.isNullCall(heldCall)) {
+                heldCall.disconnect();
+                return true;
+            }
+        } else if (chld == CHLD_TYPE_RELEASEACTIVE_ACCEPTHELD) {
+            if (mCallInfo.isNullCall(activeCall)
+                    && mCallInfo.isNullCall(ringingCall)
+                    && mCallInfo.isNullCall(heldCall)) {
+                return false;
+            }
+            if (!mCallInfo.isNullCall(activeCall)) {
+                BluetoothCall conferenceCall = getBluetoothCallById(activeCall.getParentId());
+                if (!mCallInfo.isNullCall(conferenceCall)
+                        && conferenceCall.getState() == Call.STATE_ACTIVE) {
+                    Log.i(TAG, "CHLD: disconnect conference call");
+                    conferenceCall.disconnect();
+                } else {
+                    activeCall.disconnect();
+                }
+            }
+            if (!mCallInfo.isNullCall(ringingCall)) {
+                ringingCall.answer(ringingCall.getVideoState());
+            } else if (!mCallInfo.isNullCall(heldCall)) {
+                heldCall.unhold();
+            }
+            return true;
+        } else if (chld == CHLD_TYPE_HOLDACTIVE_ACCEPTHELD) {
+            if (!mCallInfo.isNullCall(activeCall)
+                    && activeCall.can(Connection.CAPABILITY_SWAP_CONFERENCE)) {
+                activeCall.swapConference();
+                Log.i(TAG, "CDMA calls in conference swapped, updating headset");
+                updateHeadsetWithCallState(true /* force */);
+                return true;
+            } else if (!mCallInfo.isNullCall(ringingCall)) {
+                ringingCall.answer(VideoProfile.STATE_AUDIO_ONLY);
+                return true;
+            } else if (!mCallInfo.isNullCall(heldCall)) {
+                // CallsManager will hold any active calls when unhold() is called on a
+                // currently-held call.
+                heldCall.unhold();
+                return true;
+            } else if (!mCallInfo.isNullCall(activeCall)
+                    && activeCall.can(Connection.CAPABILITY_HOLD)) {
+                activeCall.hold();
+                return true;
+            }
+        } else if (chld == CHLD_TYPE_ADDHELDTOCONF) {
+            if (!mCallInfo.isNullCall(activeCall)) {
+                if (activeCall.can(Connection.CAPABILITY_MERGE_CONFERENCE)) {
+                    activeCall.mergeConference();
+                    return true;
+                } else {
+                    List<BluetoothCall> conferenceable = getBluetoothCallsByIds(
+                            activeCall.getConferenceableCalls());
+                    if (!conferenceable.isEmpty()) {
+                        activeCall.conference(conferenceable.get(0));
+                        return true;
+                    }
+                }
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Sends an update of the current BluetoothCall state to the current Headset.
+     *
+     * @param force {@code true} if the headset state should be sent regardless if no changes to
+     * the state have occurred, {@code false} if the state should only be sent if the state
+     * has changed.
+     */
+    private void updateHeadsetWithCallState(boolean force) {
+        BluetoothCall activeCall = mCallInfo.getActiveCall();
+        BluetoothCall ringingCall = mCallInfo.getRingingOrSimulatedRingingCall();
+        BluetoothCall heldCall = mCallInfo.getHeldCall();
+
+        int bluetoothCallState = getBluetoothCallStateForUpdate();
+
+        String ringingAddress = null;
+        int ringingAddressType = DEFAULT_RINGING_ADDRESS_TYPE;
+        String ringingName = null;
+        if (!mCallInfo.isNullCall(ringingCall) && ringingCall.getHandle() != null
+                && !ringingCall.isSilentRingingRequested()) {
+            ringingAddress = ringingCall.getHandle().getSchemeSpecificPart();
+            if (ringingAddress != null) {
+                ringingAddressType = PhoneNumberUtils.toaFromString(ringingAddress);
+            }
+            ringingName = ringingCall.getCallerDisplayName();
+            if (TextUtils.isEmpty(ringingName)) {
+                ringingName = ringingCall.getContactDisplayName();
+            }
+        }
+        if (ringingAddress == null) {
+            ringingAddress = "";
+        }
+
+        int numActiveCalls = mCallInfo.isNullCall(activeCall) ? 0 : 1;
+        int numHeldCalls = mCallInfo.getNumHeldCalls();
+        int numChildrenOfActiveCall =
+                mCallInfo.isNullCall(activeCall) ? 0 : activeCall.getChildrenIds().size();
+
+        // Intermediate state for GSM calls which are in the process of being swapped.
+        // TODO: Should we be hardcoding this value to 2 or should we check if all top level calls
+        //       are held?
+        boolean callsPendingSwitch = (numHeldCalls == 2);
+
+        // For conference calls which support swapping the active BluetoothCall within the
+        // conference (namely CDMA calls) we need to expose that as a held BluetoothCall
+        // in order for the BT device to show "swap" and "merge" functionality.
+        boolean ignoreHeldCallChange = false;
+        if (!mCallInfo.isNullCall(activeCall) && activeCall.isConference()
+                && !activeCall.can(Connection.CAPABILITY_CONFERENCE_HAS_NO_CHILDREN)) {
+            if (activeCall.can(Connection.CAPABILITY_SWAP_CONFERENCE)) {
+                // Indicate that BT device should show SWAP command by indicating that there is a
+                // BluetoothCall on hold, but only if the conference wasn't previously merged.
+                numHeldCalls = activeCall.wasConferencePreviouslyMerged() ? 0 : 1;
+            } else if (activeCall.can(Connection.CAPABILITY_MERGE_CONFERENCE)) {
+                numHeldCalls = 1;  // Merge is available, so expose via numHeldCalls.
+            }
+
+            for (String id : activeCall.getChildrenIds()) {
+                // Held BluetoothCall has changed due to it being combined into a CDMA conference.
+                // Keep track of this and ignore any future update since it doesn't really count
+                // as a BluetoothCall change.
+                if (mOldHeldCall != null && mOldHeldCall.getTelecomCallId() == id) {
+                    ignoreHeldCallChange = true;
+                    break;
+                }
+            }
+        }
+
+        if (mBluetoothHeadset != null
+                && (force
+                    || (!callsPendingSwitch
+                        && (numActiveCalls != mNumActiveCalls
+                            || numChildrenOfActiveCall != mNumChildrenOfActiveCall
+                            || numHeldCalls != mNumHeldCalls
+                            || bluetoothCallState != mBluetoothCallState
+                            || !TextUtils.equals(ringingAddress, mRingingAddress)
+                            || ringingAddressType != mRingingAddressType
+                            || (heldCall != mOldHeldCall && !ignoreHeldCallChange))))) {
+
+            // If the BluetoothCall is transitioning into the alerting state, send DIALING first.
+            // Some devices expect to see a DIALING state prior to seeing an ALERTING state
+            // so we need to send it first.
+            boolean sendDialingFirst = mBluetoothCallState != bluetoothCallState
+                    && bluetoothCallState == CALL_STATE_ALERTING;
+
+            mOldHeldCall = heldCall;
+            mNumActiveCalls = numActiveCalls;
+            mNumChildrenOfActiveCall = numChildrenOfActiveCall;
+            mNumHeldCalls = numHeldCalls;
+            mBluetoothCallState = bluetoothCallState;
+            mRingingAddress = ringingAddress;
+            mRingingAddressType = ringingAddressType;
+
+            if (sendDialingFirst) {
+                // Log in full to make logs easier to debug.
+                Log.i(TAG, "updateHeadsetWithCallState "
+                                + "numActive " + mNumActiveCalls + ", "
+                                + "numHeld " + mNumHeldCalls + ", "
+                                + "callState " + CALL_STATE_DIALING + ", "
+                                + "ringing type " + mRingingAddressType);
+                mBluetoothHeadset.phoneStateChanged(
+                        mNumActiveCalls,
+                        mNumHeldCalls,
+                        CALL_STATE_DIALING,
+                        mRingingAddress,
+                        mRingingAddressType,
+                        ringingName);
+            }
+
+            Log.i(TAG, "updateHeadsetWithCallState "
+                    + "numActive " + mNumActiveCalls + ", "
+                    + "numHeld " + mNumHeldCalls + ", "
+                    + "callState " + mBluetoothCallState + ", "
+                    + "ringing type " + mRingingAddressType);
+
+            mBluetoothHeadset.phoneStateChanged(
+                    mNumActiveCalls,
+                    mNumHeldCalls,
+                    mBluetoothCallState,
+                    mRingingAddress,
+                    mRingingAddressType,
+                    ringingName);
+
+            mHeadsetUpdatedRecently = true;
+        }
+    }
+
+    private int getBluetoothCallStateForUpdate() {
+        BluetoothCall ringingCall = mCallInfo.getRingingOrSimulatedRingingCall();
+        BluetoothCall dialingCall = mCallInfo.getOutgoingCall();
+        boolean hasOnlyDisconnectedCalls = mCallInfo.hasOnlyDisconnectedCalls();
+
+        //
+        // !! WARNING !!
+        // You will note that CALL_STATE_WAITING, CALL_STATE_HELD, and CALL_STATE_ACTIVE are not
+        // used in this version of the BluetoothCall state mappings.  This is on purpose.
+        // phone_state_change() in btif_hf.c is not written to handle these states. Only with the
+        // listCalls*() method are WAITING and ACTIVE used.
+        // Using the unsupported states here caused problems with inconsistent state in some
+        // bluetooth devices (like not getting out of ringing state after answering a call).
+        //
+        int bluetoothCallState = CALL_STATE_IDLE;
+        if (!mCallInfo.isNullCall(ringingCall) && !ringingCall.isSilentRingingRequested()) {
+            bluetoothCallState = CALL_STATE_INCOMING;
+        } else if (!mCallInfo.isNullCall(dialingCall)) {
+            bluetoothCallState = CALL_STATE_ALERTING;
+        } else if (hasOnlyDisconnectedCalls || mIsDisconnectedTonePlaying) {
+            // Keep the DISCONNECTED state until the disconnect tone's playback is done
+            bluetoothCallState = CALL_STATE_DISCONNECTED;
+        }
+        return bluetoothCallState;
+    }
+
+    private int getBtCallState(BluetoothCall call, boolean isForeground) {
+        switch (call.getState()) {
+            case Call.STATE_NEW:
+            case Call.STATE_DISCONNECTED:
+            case Call.STATE_AUDIO_PROCESSING:
+                return CALL_STATE_IDLE;
+
+            case Call.STATE_ACTIVE:
+                return CALL_STATE_ACTIVE;
+
+            case Call.STATE_CONNECTING:
+            case Call.STATE_SELECT_PHONE_ACCOUNT:
+            case Call.STATE_DIALING:
+            case Call.STATE_PULLING_CALL:
+                // Yes, this is correctly returning ALERTING.
+                // "Dialing" for BT means that we have sent information to the service provider
+                // to place the BluetoothCall but there is no confirmation that the BluetoothCall
+                // is going through. When there finally is confirmation, the ringback is
+                // played which is referred to as an "alert" tone, thus, ALERTING.
+                // TODO: We should consider using the ALERTING terms in Telecom because that
+                // seems to be more industry-standard.
+                return CALL_STATE_ALERTING;
+
+            case Call.STATE_HOLDING:
+                return CALL_STATE_HELD;
+
+            case Call.STATE_RINGING:
+            case Call.STATE_SIMULATED_RINGING:
+                if (call.isSilentRingingRequested()) {
+                    return CALL_STATE_IDLE;
+                } else if (isForeground) {
+                    return CALL_STATE_INCOMING;
+                } else {
+                    return CALL_STATE_WAITING;
+                }
+        }
+        return CALL_STATE_IDLE;
+    }
+
+    @VisibleForTesting
+    public CallStateCallback getCallback(BluetoothCall call) {
+        return mCallbacks.get(call.getTelecomCallId());
+    }
+
+    @VisibleForTesting
+    public void setBluetoothHeadset(BluetoothHeadsetProxy bluetoothHeadset) {
+        mBluetoothHeadset = bluetoothHeadset;
+    }
+
+    @VisibleForTesting
+    public BluetoothCall getBluetoothCallById(String id) {
+        if (mBluetoothCallHashMap.containsKey(id)) {
+            return mBluetoothCallHashMap.get(id);
+        }
+        return null;
+    }
+
+    @VisibleForTesting
+    public List<BluetoothCall> getBluetoothCallsByIds(List<String> ids) {
+        List<BluetoothCall> calls = new ArrayList<>();
+        for (String id : ids) {
+            BluetoothCall call = getBluetoothCallById(id);
+            if (!mCallInfo.isNullCall(call)) {
+                calls.add(call);
+            }
+        }
+        return calls;
+    }
+
+    // extract call information functions out into this part, so we can mock it in testing
+    @VisibleForTesting
+    public class CallInfo {
+
+        public BluetoothCall getForegroundCall() {
+            LinkedHashSet<Integer> states = new LinkedHashSet<Integer>();
+            BluetoothCall foregroundCall;
+
+            states.add(Call.STATE_CONNECTING);
+            foregroundCall = getCallByStates(states);
+            if (!mCallInfo.isNullCall(foregroundCall)) {
+                return foregroundCall;
+            }
+
+            states.clear();
+            states.add(Call.STATE_ACTIVE);
+            states.add(Call.STATE_DIALING);
+            states.add(Call.STATE_PULLING_CALL);
+            foregroundCall = getCallByStates(states);
+            if (!mCallInfo.isNullCall(foregroundCall)) {
+                return foregroundCall;
+            }
+
+            states.clear();
+            states.add(Call.STATE_RINGING);
+            foregroundCall = getCallByStates(states);
+            if (!mCallInfo.isNullCall(foregroundCall)) {
+                return foregroundCall;
+            }
+
+            return null;
+        }
+
+        public BluetoothCall getCallByStates(LinkedHashSet<Integer> states) {
+            List<BluetoothCall> calls = getBluetoothCalls();
+            for (BluetoothCall call : calls) {
+                if (states.contains(call.getState())) {
+                    return call;
+                }
+            }
+            return null;
+        }
+
+        public BluetoothCall getCallByState(int state) {
+            List<BluetoothCall> calls = getBluetoothCalls();
+            for (BluetoothCall call : calls) {
+                if (state == call.getState()) {
+                    return call;
+                }
+            }
+            return null;
+        }
+
+        public int getNumHeldCalls() {
+            int number = 0;
+            List<BluetoothCall> calls = getBluetoothCalls();
+            for (BluetoothCall call : calls) {
+                if (call.getState() == Call.STATE_HOLDING) {
+                    number++;
+                }
+            }
+            return number;
+        }
+
+        public boolean hasOnlyDisconnectedCalls() {
+            List<BluetoothCall> calls = getBluetoothCalls();
+            if (calls.size() == 0) {
+                return false;
+            }
+            for (BluetoothCall call : calls) {
+                if (call.getState() != Call.STATE_DISCONNECTED) {
+                    return false;
+                }
+            }
+            return true;
+        }
+
+        public List<BluetoothCall> getBluetoothCalls() {
+            return getBluetoothCallsByIds(BluetoothCall.getIds(getCalls()));
+        }
+
+        public BluetoothCall getOutgoingCall() {
+            LinkedHashSet<Integer> states = new LinkedHashSet<Integer>();
+            states.add(Call.STATE_CONNECTING);
+            states.add(Call.STATE_DIALING);
+            states.add(Call.STATE_PULLING_CALL);
+            return getCallByStates(states);
+        }
+
+        public BluetoothCall getRingingOrSimulatedRingingCall() {
+            LinkedHashSet<Integer> states = new LinkedHashSet<Integer>();
+            states.add(Call.STATE_RINGING);
+            states.add(Call.STATE_SIMULATED_RINGING);
+            return getCallByStates(states);
+        }
+
+        public BluetoothCall getActiveCall() {
+            return getCallByState(Call.STATE_ACTIVE);
+        }
+
+        public BluetoothCall getHeldCall() {
+            return getCallByState(Call.STATE_HOLDING);
+        }
+
+        /**
+         * Returns the best phone account to use for the given state of all calls.
+         * First, tries to return the phone account for the foreground call, second the default
+         * phone account for PhoneAccount.SCHEME_TEL.
+         */
+        public PhoneAccount getBestPhoneAccount() {
+            BluetoothCall call = getForegroundCall();
+
+            PhoneAccount account = null;
+            if (!mCallInfo.isNullCall(call)) {
+                PhoneAccountHandle handle = call.getAccountHandle();
+                if (handle != null) {
+                    // First try to get the network name of the foreground call.
+                    account = mTelecomManager.getPhoneAccount(handle);
+                }
+            }
+
+            if (account == null) {
+                // Second, Try to get the label for the default Phone Account.
+                List<PhoneAccountHandle> handles =
+                        mTelecomManager.getPhoneAccountsSupportingScheme(PhoneAccount.SCHEME_TEL);
+                while (handles.iterator().hasNext()) {
+                    account = mTelecomManager.getPhoneAccount(handles.iterator().next());
+                    if (account != null) {
+                        return account;
+                    }
+                }
+            }
+            return null;
+        }
+
+        public boolean isNullCall(BluetoothCall call) {
+            return call == null || call.getCall() == null;
+        }
+    };
+};
diff --git a/tests/robotests/Android.mk b/tests/robotests/Android.mk
index 0cb6917..afcfa11 100644
--- a/tests/robotests/Android.mk
+++ b/tests/robotests/Android.mk
@@ -5,6 +5,8 @@
 include $(CLEAR_VARS)
 
 LOCAL_MODULE := BluetoothRoboTests
+LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
+LOCAL_LICENSE_CONDITIONS := notice
 
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 
@@ -32,6 +34,8 @@
 include $(CLEAR_VARS)
 
 LOCAL_MODULE := RunBluetoothRoboTests
+LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
+LOCAL_LICENSE_CONDITIONS := notice
 
 LOCAL_JAVA_LIBRARIES := \
     BluetoothRoboTests \
diff --git a/tests/unit/Android.bp b/tests/unit/Android.bp
old mode 100644
new mode 100755
index 68b70c2..c0bc0ff
--- a/tests/unit/Android.bp
+++ b/tests/unit/Android.bp
@@ -1,3 +1,7 @@
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
 android_test {
     name: "BluetoothInstrumentationTests",
 
@@ -14,6 +18,7 @@
     ],
 
     static_libs: [
+        "androidx.test.ext.truth",
         "androidx.test.rules",
         "mockito-target",
         "androidx.test.espresso.intents",
@@ -21,6 +26,7 @@
         "androidx.room_room-migration",
         "androidx.room_room-runtime",
         "androidx.room_room-testing",
+        "truth-prebuilt",
     ],
 
     asset_dirs: ["src/com/android/bluetooth/btservice/storage/schemas"],
diff --git a/tests/unit/AndroidManifest.xml b/tests/unit/AndroidManifest.xml
index eaf7fdc..cf50f48 100644
--- a/tests/unit/AndroidManifest.xml
+++ b/tests/unit/AndroidManifest.xml
@@ -9,6 +9,8 @@
     <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.BLUETOOTH_CONNECT" />
+    <uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
     <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" />
@@ -39,7 +41,7 @@
     <uses-permission android:name="android.permission.READ_SMS" />
     <uses-permission android:name="android.permission.WRITE_SMS" />
     <uses-permission android:name="android.permission.READ_CONTACTS" />
-    <uses-permission android:name="com.android.permission.WHITELIST_BLUETOOTH_DEVICE" />
+    <uses-permission android:name="com.android.permission.ALLOWLIST_BLUETOOTH_DEVICE" />
     <uses-permission android:name="com.android.email.permission.ACCESS_PROVIDER"/>
 
     <!-- For testing Email content access -->
@@ -52,8 +54,8 @@
          this package needs to link against the android.test library,
          which is needed when building test cases. -->
 
-    <application
-        android:allowBackup="true" >
+    <application android:allowBackup="true"
+                 android:autoRevokePermissions="disallowed">
         <uses-library android:name="android.test.runner" />
         <uses-library android:name="org.apache.http.legacy" android:required="false" />
     </application>
diff --git a/tests/unit/res/raw/image_200_200_blue.jpeg b/tests/unit/res/raw/image_200_200_blue.jpeg
new file mode 100644
index 0000000..4233b2a
--- /dev/null
+++ b/tests/unit/res/raw/image_200_200_blue.jpeg
Binary files differ
diff --git a/tests/unit/res/raw/image_200_200_orange.jpeg b/tests/unit/res/raw/image_200_200_orange.jpeg
new file mode 100644
index 0000000..e209294
--- /dev/null
+++ b/tests/unit/res/raw/image_200_200_orange.jpeg
Binary files differ
diff --git a/tests/unit/src/com/android/bluetooth/StateMachineTest.java b/tests/unit/src/com/android/bluetooth/StateMachineTest.java
index 5cf2255..7bd6dbd 100644
--- a/tests/unit/src/com/android/bluetooth/StateMachineTest.java
+++ b/tests/unit/src/com/android/bluetooth/StateMachineTest.java
@@ -85,13 +85,13 @@
     /**
      * Tests {@link StateMachine#toString()}.
      */
-    class StateMachineToStringTest extends StateMachine {
+    static class StateMachineToStringTest extends StateMachine {
         StateMachineToStringTest(String name) {
             super(name);
         }
     }
 
-    class ExampleState extends State {
+    static class ExampleState extends State {
         String mName;
 
         ExampleState(String name) {
@@ -195,7 +195,7 @@
             }
         }
 
-        private StateMachineQuitTest mThisSm;
+        private final StateMachineQuitTest mThisSm;
         private S1 mS1 = new S1();
     }
 
@@ -298,7 +298,7 @@
             }
         }
 
-        private StateMachineQuitNowTest mThisSm;
+        private final StateMachineQuitNowTest mThisSm;
         private S1 mS1 = new S1();
     }
 
@@ -402,14 +402,14 @@
             }
         }
 
-        private StateMachineQuitNowAfterStartTest mThisSm;
+        private final StateMachineQuitNowAfterStartTest mThisSm;
         private S1 mS1 = new S1();
     }
 
     /**
      * Test enter/exit can use transitionTo
      */
-    class StateMachineEnterExitTransitionToTest extends StateMachine {
+    static class StateMachineEnterExitTransitionToTest extends StateMachine {
 
         StateMachineEnterExitTransitionToTest(String name) {
             super(name);
@@ -502,7 +502,7 @@
             }
         }
 
-        private StateMachineEnterExitTransitionToTest mThisSm;
+        private final StateMachineEnterExitTransitionToTest mThisSm;
         private S1 mS1 = new S1();
         private S2 mS2 = new S2();
         private S3 mS3 = new S3();
@@ -597,7 +597,7 @@
     /**
      * Tests that ProcessedMessage works as a circular buffer.
      */
-    class StateMachine0 extends StateMachine {
+    static class StateMachine0 extends StateMachine {
         StateMachine0(String name) {
             super(name);
             mThisSm = this;
@@ -628,7 +628,7 @@
             }
         }
 
-        private StateMachine0 mThisSm;
+        private final StateMachine0 mThisSm;
         private S1 mS1 = new S1();
     }
 
@@ -684,7 +684,7 @@
      * in state mS1. With the first message it transitions to
      * itself which causes it to be exited and reentered.
      */
-    class StateMachine1 extends StateMachine {
+    static class StateMachine1 extends StateMachine {
         StateMachine1(String name) {
             super(name);
             mThisSm = this;
@@ -729,7 +729,7 @@
             }
         }
 
-        private StateMachine1 mThisSm;
+        private final StateMachine1 mThisSm;
         private S1 mS1 = new S1();
 
         private int mEnterCount;
@@ -784,7 +784,7 @@
      * mS2 then receives both of the deferred messages first TEST_CMD_1 and
      * then TEST_CMD_2.
      */
-    class StateMachine2 extends StateMachine {
+    static class StateMachine2 extends StateMachine {
         StateMachine2(String name) {
             super(name);
             mThisSm = this;
@@ -835,7 +835,7 @@
             }
         }
 
-        private StateMachine2 mThisSm;
+        private final StateMachine2 mThisSm;
         private S1 mS1 = new S1();
         private S2 mS2 = new S2();
 
@@ -891,7 +891,7 @@
      * Test that unhandled messages in a child are handled by the parent.
      * When TEST_CMD_2 is received.
      */
-    class StateMachine3 extends StateMachine {
+    static class StateMachine3 extends StateMachine {
         StateMachine3(String name) {
             super(name);
             mThisSm = this;
@@ -918,7 +918,7 @@
             }
         }
 
-        class ChildState extends State {
+        static class ChildState extends State {
             @Override
             public boolean processMessage(Message message) {
                 return NOT_HANDLED;
@@ -932,7 +932,7 @@
             }
         }
 
-        private StateMachine3 mThisSm;
+        private final StateMachine3 mThisSm;
         private ParentState mParentState = new ParentState();
         private ChildState mChildState = new ChildState();
     }
@@ -977,7 +977,7 @@
      * with transition from child 1 to child 2 and child 2
      * lets the parent handle the messages.
      */
-    class StateMachine4 extends StateMachine {
+    static class StateMachine4 extends StateMachine {
         StateMachine4(String name) {
             super(name);
             mThisSm = this;
@@ -1013,7 +1013,7 @@
             }
         }
 
-        class ChildState2 extends State {
+        static class ChildState2 extends State {
             @Override
             public boolean processMessage(Message message) {
                 return NOT_HANDLED;
@@ -1027,7 +1027,7 @@
             }
         }
 
-        private StateMachine4 mThisSm;
+        private final StateMachine4 mThisSm;
         private ParentState mParentState = new ParentState();
         private ChildState1 mChildState1 = new ChildState1();
         private ChildState2 mChildState2 = new ChildState2();
@@ -1073,7 +1073,7 @@
      * Test transition from one child to another of a "complex"
      * hierarchy with two parents and multiple children.
      */
-    class StateMachine5 extends StateMachine {
+    static class StateMachine5 extends StateMachine {
         StateMachine5(String name) {
             super(name);
             mThisSm = this;
@@ -1303,7 +1303,7 @@
             }
         }
 
-        private StateMachine5 mThisSm;
+        private final StateMachine5 mThisSm;
         private ParentState1 mParentState1 = new ParentState1();
         private ChildState1 mChildState1 = new ChildState1();
         private ChildState2 mChildState2 = new ChildState2();
@@ -1408,7 +1408,7 @@
      * after construction and before any other messages arrive and that
      * sendMessageDelayed works.
      */
-    class StateMachine6 extends StateMachine {
+    static class StateMachine6 extends StateMachine {
         StateMachine6(String name) {
             super(name);
             mThisSm = this;
@@ -1446,7 +1446,7 @@
             }
         }
 
-        private StateMachine6 mThisSm;
+        private final StateMachine6 mThisSm;
         private S1 mS1 = new S1();
 
         private long mArrivalTimeMsg1;
@@ -1492,7 +1492,7 @@
      * Test that enter is invoked immediately after exit. This validates
      * that enter can be used to send a watch dog message for its state.
      */
-    class StateMachine7 extends StateMachine {
+    static class StateMachine7 extends StateMachine {
         private final int SM7_DELAY_TIME = 250;
 
         StateMachine7(String name) {
@@ -1551,7 +1551,7 @@
             }
         }
 
-        private StateMachine7 mThisSm;
+        private final StateMachine7 mThisSm;
         private S1 mS1 = new S1();
         private S2 mS2 = new S2();
 
@@ -1586,7 +1586,7 @@
          * without a delay the arrival time difference should always >= to SM7_DELAY_TIME.
          */
         long arrivalTimeDiff = sm7.mArrivalTimeMsg3 - sm7.mArrivalTimeMsg2;
-        long expectedDelay = sm7.SM7_DELAY_TIME - SM7_DELAY_FUDGE;
+        long expectedDelay = (long) sm7.SM7_DELAY_TIME - SM7_DELAY_FUDGE;
         if (sm7.isDbg()) tlog("testStateMachine7: expect " + arrivalTimeDiff
                 + " >= " + expectedDelay);
         Assert.assertTrue(arrivalTimeDiff >= expectedDelay);
@@ -1597,7 +1597,7 @@
     /**
      * Test unhandledMessage.
      */
-    class StateMachineUnhandledMessage extends StateMachine {
+    static class StateMachineUnhandledMessage extends StateMachine {
         StateMachineUnhandledMessage(String name) {
             super(name);
             mThisSm = this;
@@ -1631,7 +1631,7 @@
             }
         }
 
-        private StateMachineUnhandledMessage mThisSm;
+        private final StateMachineUnhandledMessage mThisSm;
         private int mUnhandledMessageCount;
         private S1 mS1 = new S1();
     }
@@ -1671,7 +1671,7 @@
      * will be used to notify testStateMachineSharedThread that the test is
      * complete.
      */
-    class StateMachineSharedThread extends StateMachine {
+    static class StateMachineSharedThread extends StateMachine {
         StateMachineSharedThread(String name, Looper looper, int maxCount) {
             super(name, looper);
             mMaxCount = maxCount;
diff --git a/tests/unit/src/com/android/bluetooth/TestUtils.java b/tests/unit/src/com/android/bluetooth/TestUtils.java
index 4e6b7ba..dd453e0 100644
--- a/tests/unit/src/com/android/bluetooth/TestUtils.java
+++ b/tests/unit/src/com/android/bluetooth/TestUtils.java
@@ -23,6 +23,7 @@
 import android.content.Intent;
 import android.os.Handler;
 import android.os.Looper;
+import android.os.MessageQueue;
 
 import androidx.test.InstrumentationRegistry;
 import androidx.test.rule.ServiceTestRule;
@@ -246,6 +247,41 @@
     }
 
     /**
+     * Wait for looper to become idle
+     *
+     * @param looper looper of interest
+     */
+    public static void waitForLooperToBeIdle(Looper looper) {
+        class Idler implements MessageQueue.IdleHandler {
+            private boolean mIdle = false;
+
+            @Override
+            public boolean queueIdle() {
+                synchronized (this) {
+                    mIdle = true;
+                    notifyAll();
+                }
+                return false;
+            }
+
+            public synchronized void waitForIdle() {
+                while (!mIdle) {
+                    try {
+                        wait();
+                    } catch (InterruptedException e) {
+                    }
+                }
+            }
+        }
+
+        Idler idle = new Idler();
+        looper.getQueue().addIdleHandler(idle);
+        // Ensure we are not Idle to begin with so the idle handler will run
+        waitForLooperToFinishScheduledTask(looper);
+        idle.waitForIdle();
+    }
+
+    /**
      * Run synchronously a runnable action on a looper.
      * The method will return after the action has been execution to completion.
      *
diff --git a/tests/unit/src/com/android/bluetooth/a2dp/A2dpServiceTest.java b/tests/unit/src/com/android/bluetooth/a2dp/A2dpServiceTest.java
index 99403eb..3397f75 100644
--- a/tests/unit/src/com/android/bluetooth/a2dp/A2dpServiceTest.java
+++ b/tests/unit/src/com/android/bluetooth/a2dp/A2dpServiceTest.java
@@ -93,7 +93,9 @@
 
         TestUtils.setAdapterService(mAdapterService);
         doReturn(MAX_CONNECTED_AUDIO_DEVICES).when(mAdapterService).getMaxConnectedAudioDevices();
+        doReturn(true, false).when(mAdapterService).isStartedProfile(anyString());
         doReturn(false).when(mAdapterService).isQuietModeEnabled();
+        doReturn(mDatabaseManager).when(mAdapterService).getDatabase();
 
         mAdapter = BluetoothAdapter.getDefaultAdapter();
 
@@ -259,21 +261,18 @@
      */
     @Test
     public void testGetPriority() {
-        when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager);
         when(mDatabaseManager.getProfileConnectionPolicy(mTestDevice, BluetoothProfile.A2DP))
                 .thenReturn(BluetoothProfile.CONNECTION_POLICY_UNKNOWN);
         Assert.assertEquals("Initial device priority",
                             BluetoothProfile.CONNECTION_POLICY_UNKNOWN,
                             mA2dpService.getConnectionPolicy(mTestDevice));
 
-        when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager);
         when(mDatabaseManager.getProfileConnectionPolicy(mTestDevice, BluetoothProfile.A2DP))
                 .thenReturn(BluetoothProfile.CONNECTION_POLICY_FORBIDDEN);
         Assert.assertEquals("Setting device priority to PRIORITY_OFF",
                             BluetoothProfile.CONNECTION_POLICY_FORBIDDEN,
                             mA2dpService.getConnectionPolicy(mTestDevice));
 
-        when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager);
         when(mDatabaseManager.getProfileConnectionPolicy(mTestDevice, BluetoothProfile.A2DP))
                 .thenReturn(BluetoothProfile.CONNECTION_POLICY_ALLOWED);
         Assert.assertEquals("Setting device priority to PRIORITY_ON",
@@ -329,7 +328,6 @@
     @Test
     public void testOutgoingConnectMissingAudioSinkUuid() {
         // Update the device priority so okToConnect() returns true
-        when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager);
         when(mDatabaseManager.getProfileConnectionPolicy(mTestDevice, BluetoothProfile.A2DP))
                 .thenReturn(BluetoothProfile.CONNECTION_POLICY_ALLOWED);
         doReturn(true).when(mA2dpNativeInterface).connectA2dp(any(BluetoothDevice.class));
@@ -352,7 +350,6 @@
         doReturn(true).when(mA2dpNativeInterface).disconnectA2dp(any(BluetoothDevice.class));
 
         // Set the device priority to PRIORITY_OFF so connect() should fail
-        when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager);
         when(mDatabaseManager.getProfileConnectionPolicy(mTestDevice, BluetoothProfile.A2DP))
                 .thenReturn(BluetoothProfile.CONNECTION_POLICY_FORBIDDEN);
 
@@ -366,7 +363,6 @@
     @Test
     public void testOutgoingConnectTimeout() {
         // Update the device priority so okToConnect() returns true
-        when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager);
         when(mDatabaseManager.getProfileConnectionPolicy(mTestDevice, BluetoothProfile.A2DP))
                 .thenReturn(BluetoothProfile.CONNECTION_POLICY_ALLOWED);
         doReturn(true).when(mA2dpNativeInterface).connectA2dp(any(BluetoothDevice.class));
@@ -397,7 +393,6 @@
         A2dpStackEvent connCompletedEvent;
 
         // Update the device priority so okToConnect() returns true
-        when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager);
         when(mDatabaseManager.getProfileConnectionPolicy(mTestDevice, BluetoothProfile.A2DP))
                 .thenReturn(BluetoothProfile.CONNECTION_POLICY_ALLOWED);
         doReturn(true).when(mA2dpNativeInterface).connectA2dp(any(BluetoothDevice.class));
@@ -468,7 +463,6 @@
         for (int i = 0; i < MAX_CONNECTED_AUDIO_DEVICES; i++) {
             BluetoothDevice testDevice = TestUtils.getTestDevice(mAdapter, i);
             testDevices[i] = testDevice;
-            when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager);
             when(mDatabaseManager.getProfileConnectionPolicy(testDevice, BluetoothProfile.A2DP))
                     .thenReturn(BluetoothProfile.CONNECTION_POLICY_ALLOWED);
             // Send a connect request
@@ -496,7 +490,6 @@
 
         // Prepare and connect the extra test device. The connect request should fail
         extraTestDevice = TestUtils.getTestDevice(mAdapter, MAX_CONNECTED_AUDIO_DEVICES);
-        when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager);
         when(mDatabaseManager.getProfileConnectionPolicy(extraTestDevice, BluetoothProfile.A2DP))
                 .thenReturn(BluetoothProfile.CONNECTION_POLICY_ALLOWED);
         // Send a connect request
@@ -512,7 +505,6 @@
         A2dpStackEvent stackEvent;
 
         // Update the device priority so okToConnect() returns true
-        when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager);
         when(mDatabaseManager.getProfileConnectionPolicy(mTestDevice, BluetoothProfile.A2DP))
                 .thenReturn(BluetoothProfile.CONNECTION_POLICY_ALLOWED);
         doReturn(true).when(mA2dpNativeInterface).connectA2dp(any(BluetoothDevice.class));
@@ -592,7 +584,6 @@
                                                                     codecsSelectableCapabilities);
 
         // Update the device priority so okToConnect() returns true
-        when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager);
         when(mDatabaseManager.getProfileConnectionPolicy(mTestDevice, BluetoothProfile.A2DP))
                 .thenReturn(BluetoothProfile.CONNECTION_POLICY_ALLOWED);
         doReturn(true).when(mA2dpNativeInterface).connectA2dp(any(BluetoothDevice.class));
@@ -657,7 +648,6 @@
         A2dpStackEvent stackEvent;
 
         // Update the device priority so okToConnect() returns true
-        when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager);
         when(mDatabaseManager.getProfileConnectionPolicy(mTestDevice, BluetoothProfile.A2DP))
                 .thenReturn(BluetoothProfile.CONNECTION_POLICY_ALLOWED);
         doReturn(true).when(mA2dpNativeInterface).connectA2dp(any(BluetoothDevice.class));
@@ -716,7 +706,6 @@
         A2dpStackEvent stackEvent;
 
         // Update the device priority so okToConnect() returns true
-        when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager);
         when(mDatabaseManager.getProfileConnectionPolicy(mTestDevice, BluetoothProfile.A2DP))
                 .thenReturn(BluetoothProfile.CONNECTION_POLICY_ALLOWED);
         doReturn(true).when(mA2dpNativeInterface).connectA2dp(any(BluetoothDevice.class));
@@ -890,7 +879,6 @@
         List<BluetoothDevice> prevConnectedDevices = mA2dpService.getConnectedDevices();
 
         // Update the device priority so okToConnect() returns true
-        when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager);
         when(mDatabaseManager.getProfileConnectionPolicy(device, BluetoothProfile.A2DP))
                 .thenReturn(BluetoothProfile.CONNECTION_POLICY_ALLOWED);
         doReturn(true).when(mA2dpNativeInterface).connectA2dp(device);
@@ -1010,7 +998,6 @@
     private void testOkToConnectCase(BluetoothDevice device, int bondState, int priority,
             boolean expected) {
         doReturn(bondState).when(mAdapterService).getBondState(device);
-        when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager);
         when(mDatabaseManager.getProfileConnectionPolicy(device, BluetoothProfile.A2DP))
                 .thenReturn(priority);
 
@@ -1040,7 +1027,6 @@
     private void testUpdateOptionalCodecsSupportCase(int previousSupport, boolean support,
             int previousEnabled, int verifySupportTime, int verifyNotSupportTime,
             int verifyEnabledTime) {
-        when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager);
         doReturn(true).when(mA2dpNativeInterface).setActiveDevice(any(BluetoothDevice.class));
 
         BluetoothCodecConfig codecConfigSbc =
diff --git a/tests/unit/src/com/android/bluetooth/a2dp/A2dpStateMachineTest.java b/tests/unit/src/com/android/bluetooth/a2dp/A2dpStateMachineTest.java
index cacd71a..844eacb 100644
--- a/tests/unit/src/com/android/bluetooth/a2dp/A2dpStateMachineTest.java
+++ b/tests/unit/src/com/android/bluetooth/a2dp/A2dpStateMachineTest.java
@@ -26,6 +26,7 @@
 import android.bluetooth.BluetoothProfile;
 import android.content.Context;
 import android.content.Intent;
+import android.os.Bundle;
 import android.os.HandlerThread;
 
 import androidx.test.InstrumentationRegistry;
@@ -149,7 +150,7 @@
 
         // Verify that no connection state broadcast is executed
         verify(mA2dpService, after(TIMEOUT_MS).never()).sendBroadcast(any(Intent.class),
-                                                                      anyString());
+                anyString(), any(Bundle.class));
         // Check that we are in Disconnected state
         Assert.assertThat(mA2dpStateMachine.getCurrentState(),
                           IsInstanceOf.instanceOf(A2dpStateMachine.Disconnected.class));
@@ -172,7 +173,7 @@
         // Verify that one connection state broadcast is executed
         ArgumentCaptor<Intent> intentArgument1 = ArgumentCaptor.forClass(Intent.class);
         verify(mA2dpService, timeout(TIMEOUT_MS).times(1)).sendBroadcast(intentArgument1.capture(),
-                                                                         anyString());
+                anyString(), any(Bundle.class));
         Assert.assertEquals(BluetoothProfile.STATE_CONNECTING,
                 intentArgument1.getValue().getIntExtra(BluetoothProfile.EXTRA_STATE, -1));
 
@@ -192,7 +193,7 @@
         // - one call to broadcastAudioState() when entering Connected state
         ArgumentCaptor<Intent> intentArgument2 = ArgumentCaptor.forClass(Intent.class);
         verify(mA2dpService, timeout(TIMEOUT_MS).times(3)).sendBroadcast(intentArgument2.capture(),
-                anyString());
+                anyString(), any(Bundle.class));
         // Verify that the last broadcast was to change the A2DP playing state
         // to STATE_NOT_PLAYING
         Assert.assertEquals(BluetoothA2dp.ACTION_PLAYING_STATE_CHANGED,
@@ -219,7 +220,7 @@
         // Verify that one connection state broadcast is executed
         ArgumentCaptor<Intent> intentArgument1 = ArgumentCaptor.forClass(Intent.class);
         verify(mA2dpService, timeout(TIMEOUT_MS).times(1)).sendBroadcast(intentArgument1.capture(),
-                                                                         anyString());
+                anyString(), any(Bundle.class));
         Assert.assertEquals(BluetoothProfile.STATE_CONNECTING,
                 intentArgument1.getValue().getIntExtra(BluetoothProfile.EXTRA_STATE, -1));
 
@@ -230,7 +231,8 @@
         // Verify that one connection state broadcast is executed
         ArgumentCaptor<Intent> intentArgument2 = ArgumentCaptor.forClass(Intent.class);
         verify(mA2dpService, timeout(A2dpStateMachine.sConnectTimeoutMs * 2).times(
-                2)).sendBroadcast(intentArgument2.capture(), anyString());
+                2)).sendBroadcast(intentArgument2.capture(), anyString(),
+                any(Bundle.class));
         Assert.assertEquals(BluetoothProfile.STATE_DISCONNECTED,
                 intentArgument2.getValue().getIntExtra(BluetoothProfile.EXTRA_STATE, -1));
 
@@ -258,7 +260,7 @@
         // Verify that one connection state broadcast is executed
         ArgumentCaptor<Intent> intentArgument1 = ArgumentCaptor.forClass(Intent.class);
         verify(mA2dpService, timeout(TIMEOUT_MS).times(1)).sendBroadcast(intentArgument1.capture(),
-                anyString());
+                anyString(), any(Bundle.class));
         Assert.assertEquals(BluetoothProfile.STATE_CONNECTING,
                 intentArgument1.getValue().getIntExtra(BluetoothProfile.EXTRA_STATE, -1));
 
@@ -269,7 +271,8 @@
         // Verify that one connection state broadcast is executed
         ArgumentCaptor<Intent> intentArgument2 = ArgumentCaptor.forClass(Intent.class);
         verify(mA2dpService, timeout(A2dpStateMachine.sConnectTimeoutMs * 2).times(
-                2)).sendBroadcast(intentArgument2.capture(), anyString());
+                2)).sendBroadcast(intentArgument2.capture(), anyString(),
+                any(Bundle.class));
         Assert.assertEquals(BluetoothProfile.STATE_DISCONNECTED,
                 intentArgument2.getValue().getIntExtra(BluetoothProfile.EXTRA_STATE, -1));
 
@@ -341,7 +344,7 @@
         // - one call to broadcastAudioState() when entering Connected state
         ArgumentCaptor<Intent> intentArgument2 = ArgumentCaptor.forClass(Intent.class);
         verify(mA2dpService, timeout(TIMEOUT_MS).times(2)).sendBroadcast(intentArgument2.capture(),
-                anyString());
+                anyString(), any(Bundle.class));
 
         // Verify that state machine update optional codec when enter connected state
         verify(mA2dpService, times(1)).updateOptionalCodecsSupport(mTestDevice);
diff --git a/tests/unit/src/com/android/bluetooth/a2dpsink/A2dpSinkServiceTest.java b/tests/unit/src/com/android/bluetooth/a2dpsink/A2dpSinkServiceTest.java
index c089dd3..6917e2f 100644
--- a/tests/unit/src/com/android/bluetooth/a2dpsink/A2dpSinkServiceTest.java
+++ b/tests/unit/src/com/android/bluetooth/a2dpsink/A2dpSinkServiceTest.java
@@ -61,13 +61,15 @@
                 mTargetContext.getResources().getBoolean(R.bool.profile_supported_a2dp_sink));
         MockitoAnnotations.initMocks(this);
         TestUtils.setAdapterService(mAdapterService);
+        doReturn(mDatabaseManager).when(mAdapterService).getDatabase();
+        doReturn(true, false).when(mAdapterService).isStartedProfile(anyString());
+        setMaxConnectedAudioDevices(1);
         TestUtils.startService(mServiceRule, A2dpSinkService.class);
         mService = A2dpSinkService.getA2dpSinkService();
         Assert.assertNotNull(mService);
         // Try getting the Bluetooth adapter
         mAdapter = BluetoothAdapter.getDefaultAdapter();
         Assert.assertNotNull(mAdapter);
-        when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager);
     }
 
     @After
@@ -86,6 +88,13 @@
     }
 
     /**
+     * Set the upper connected device limit
+     */
+    private void setMaxConnectedAudioDevices(int maxConnectedAudioDevices) {
+        when(mAdapterService.getMaxConnectedAudioDevices()).thenReturn(maxConnectedAudioDevices);
+    }
+
+    /**
      * Mock the priority of a bluetooth device
      *
      * @param device - The bluetooth device you wish to mock the priority of
@@ -120,4 +129,30 @@
         mockDevicePriority(device, BluetoothProfile.CONNECTION_POLICY_FORBIDDEN);
         Assert.assertFalse(mService.connect(device));
     }
+
+    /**
+     * Test that we can connect multiple devices
+     */
+    @Test
+    public void testConnectMultipleDevices() {
+        setMaxConnectedAudioDevices(5);
+
+        BluetoothDevice device1 = makeBluetoothDevice("11:11:11:11:11:11");
+        BluetoothDevice device2 = makeBluetoothDevice("22:22:22:22:22:22");
+        BluetoothDevice device3 = makeBluetoothDevice("33:33:33:33:33:33");
+        BluetoothDevice device4 = makeBluetoothDevice("44:44:44:44:44:44");
+        BluetoothDevice device5 = makeBluetoothDevice("55:55:55:55:55:55");
+
+        mockDevicePriority(device1, BluetoothProfile.CONNECTION_POLICY_ALLOWED);
+        mockDevicePriority(device2, BluetoothProfile.CONNECTION_POLICY_ALLOWED);
+        mockDevicePriority(device3, BluetoothProfile.CONNECTION_POLICY_ALLOWED);
+        mockDevicePriority(device4, BluetoothProfile.CONNECTION_POLICY_ALLOWED);
+        mockDevicePriority(device5, BluetoothProfile.CONNECTION_POLICY_ALLOWED);
+
+        Assert.assertTrue(mService.connect(device1));
+        Assert.assertTrue(mService.connect(device2));
+        Assert.assertTrue(mService.connect(device3));
+        Assert.assertTrue(mService.connect(device4));
+        Assert.assertTrue(mService.connect(device5));
+    }
 }
diff --git a/tests/unit/src/com/android/bluetooth/audio_util/BrowserPlayerWrapperTest.java b/tests/unit/src/com/android/bluetooth/audio_util/BrowserPlayerWrapperTest.java
new file mode 100644
index 0000000..5cb6782
--- /dev/null
+++ b/tests/unit/src/com/android/bluetooth/audio_util/BrowserPlayerWrapperTest.java
@@ -0,0 +1,413 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.bluetooth.audio_util;
+
+import static org.mockito.Mockito.*;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.content.res.AssetFileDescriptor;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.media.MediaDescription;
+import android.media.browse.MediaBrowser.MediaItem;
+import android.media.session.PlaybackState;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.test.mock.MockContentProvider;
+import android.test.mock.MockContentResolver;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.bluetooth.R;
+
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.List;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class BrowserPlayerWrapperTest {
+
+    @Captor ArgumentCaptor<MediaBrowser.ConnectionCallback> mBrowserConnCb;
+    @Captor ArgumentCaptor<MediaBrowser.SubscriptionCallback> mSubscriptionCb;
+    @Captor ArgumentCaptor<MediaController.Callback> mControllerCb;
+    @Captor ArgumentCaptor<Handler> mTimeoutHandler;
+    @Captor ArgumentCaptor<List<ListItem>> mWrapperBrowseCb;
+    @Mock MediaBrowser mMockBrowser;
+    @Mock BrowsedPlayerWrapper.ConnectionCallback mConnCb;
+    @Mock BrowsedPlayerWrapper.BrowseCallback mBrowseCb;
+    private HandlerThread mThread;
+
+    @Mock Context mMockContext;
+    @Mock Resources mMockResources;
+    private Context mTargetContext;
+    private Resources mTestResources;
+    private MockContentResolver mTestContentResolver;
+
+    private static final String TEST_AUTHORITY = "com.android.bluetooth.avrcp.test";
+    private static final Uri TEST_CONTENT_URI = new Uri.Builder()
+            .scheme(ContentResolver.SCHEME_CONTENT)
+            .authority(TEST_AUTHORITY)
+            .build();
+
+    private static final String IMAGE_HANDLE_1 = "0000001";
+    private static final Uri IMAGE_URI_1 = TEST_CONTENT_URI.buildUpon()
+            .appendQueryParameter("handle", IMAGE_HANDLE_1)
+            .build();
+    private static final String IMAGE_STRING_1 = IMAGE_URI_1.toString();
+
+    private Bitmap mTestBitmap = null;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+
+        mTargetContext = InstrumentationRegistry.getTargetContext();
+        try {
+            mTestResources = mTargetContext.getPackageManager()
+                    .getResourcesForApplication("com.android.bluetooth.tests");
+        } catch (PackageManager.NameNotFoundException e) {
+            Assert.fail("Setup Failure Unable to get resources" + e.toString());
+        }
+
+        mTestBitmap = loadImage(com.android.bluetooth.tests.R.raw.image_200_200);
+
+        mTestContentResolver = new MockContentResolver(mTargetContext);
+        mTestContentResolver.addProvider(TEST_AUTHORITY, new MockContentProvider() {
+            @Override
+            public AssetFileDescriptor openTypedAssetFile(Uri url, String mimeType, Bundle opts) {
+                String handle = url.getQueryParameter("handle");
+                if (IMAGE_URI_1.equals(url)) {
+                    return mTestResources.openRawResourceFd(
+                            com.android.bluetooth.tests.R.raw.image_200_200);
+                }
+                return null;
+            }
+        });
+
+        when(mMockContext.getContentResolver()).thenReturn(mTestContentResolver);
+        when(mMockResources.getBoolean(R.bool.avrcp_target_cover_art_uri_images)).thenReturn(true);
+        when(mMockContext.getResources()).thenReturn(mMockResources);
+
+        // Set up Looper thread for the timeout handler
+        mThread = new HandlerThread("MediaPlayerWrapperTestThread");
+        mThread.start();
+
+        when(mMockBrowser.getRoot()).thenReturn("root_folder");
+
+        MediaBrowserFactory.inject(mMockBrowser);
+    }
+
+    @After
+    public void tearDown() {
+        if (mThread != null) {
+            mThread.quitSafely();
+        }
+        mThread = null;
+        mTestContentResolver = null;
+        mTestBitmap = null;
+        mTestResources = null;
+        mTargetContext = null;
+    }
+
+    private Bitmap loadImage(int resId) {
+        InputStream imageInputStream = mTestResources.openRawResource(resId);
+        return BitmapFactory.decodeStream(imageInputStream);
+    }
+
+    private MediaDescription getMediaDescription(String id, String title, String artist,
+            String album, Bitmap bitmap, Uri uri, Bundle extras) {
+        MediaDescription.Builder builder = new MediaDescription.Builder()
+                .setMediaId(id)
+                .setTitle(title)
+                .setSubtitle(artist)
+                .setDescription(album);
+        if (bitmap != null) {
+            builder.setIconBitmap(bitmap);
+        }
+        if (uri != null) {
+            builder.setIconUri(uri);
+        }
+        if (extras != null) {
+            builder.setExtras(extras);
+        }
+        return builder.build();
+    }
+
+    private MediaItem getMediaItem(MediaDescription description, int flags) {
+        return new MediaItem(description, flags);
+    }
+
+    @Test
+    public void testWrap() {
+        BrowsedPlayerWrapper wrapper =
+                BrowsedPlayerWrapper.wrap(mMockContext, mThread.getLooper(), "test", "test");
+        wrapper.connect(mConnCb);
+        verify(mMockBrowser).testInit(any(), any(), mBrowserConnCb.capture(), any());
+        verify(mMockBrowser).connect();
+
+        MediaBrowser.ConnectionCallback browserConnCb = mBrowserConnCb.getValue();
+        browserConnCb.onConnected();
+
+        verify(mConnCb).run(eq(BrowsedPlayerWrapper.STATUS_SUCCESS), eq(wrapper));
+        verify(mMockBrowser).disconnect();
+    }
+
+    @Test
+    public void testConnect_Successful() {
+        BrowsedPlayerWrapper wrapper =
+                BrowsedPlayerWrapper.wrap(mMockContext, mThread.getLooper(), "test", "test");
+        wrapper.connect(mConnCb);
+        verify(mMockBrowser).testInit(any(), any(), mBrowserConnCb.capture(), any());
+        MediaBrowser.ConnectionCallback browserConnCb = mBrowserConnCb.getValue();
+
+        verify(mMockBrowser, times(1)).connect();
+        browserConnCb.onConnected();
+        verify(mConnCb).run(eq(BrowsedPlayerWrapper.STATUS_SUCCESS), eq(wrapper));
+        verify(mMockBrowser, times(1)).disconnect();
+    }
+
+    @Test
+    public void testConnect_Suspended() {
+        BrowsedPlayerWrapper wrapper =
+                BrowsedPlayerWrapper.wrap(mMockContext, mThread.getLooper(), "test", "test");
+        wrapper.connect(mConnCb);
+        verify(mMockBrowser).testInit(any(), any(), mBrowserConnCb.capture(), any());
+        MediaBrowser.ConnectionCallback browserConnCb = mBrowserConnCb.getValue();
+
+        verify(mMockBrowser, times(1)).connect();
+        browserConnCb.onConnectionSuspended();
+        verify(mConnCb).run(eq(BrowsedPlayerWrapper.STATUS_CONN_ERROR), eq(wrapper));
+        // Twice because our mConnCb is wrapped when using the plain connect() call and disconnect
+        // is called for us when the callback is invoked in addition to error handling calling
+        // disconnect.
+        verify(mMockBrowser, times(2)).disconnect();
+    }
+
+    @Test
+    public void testConnect_Failed() {
+        BrowsedPlayerWrapper wrapper =
+                BrowsedPlayerWrapper.wrap(mMockContext, mThread.getLooper(), "test", "test");
+        wrapper.connect(mConnCb);
+        verify(mMockBrowser).testInit(any(), any(), mBrowserConnCb.capture(), any());
+        MediaBrowser.ConnectionCallback browserConnCb = mBrowserConnCb.getValue();
+
+        verify(mMockBrowser, times(1)).connect();
+        browserConnCb.onConnectionFailed();
+        verify(mConnCb).run(eq(BrowsedPlayerWrapper.STATUS_CONN_ERROR), eq(wrapper));
+        verify(mMockBrowser, times(1)).disconnect();
+    }
+
+    @Test
+    public void testEmptyRoot() {
+        BrowsedPlayerWrapper wrapper =
+                BrowsedPlayerWrapper.wrap(mMockContext, mThread.getLooper(), "test", "test");
+
+        doReturn("").when(mMockBrowser).getRoot();
+
+        wrapper.connect(mConnCb);
+        verify(mMockBrowser).testInit(any(), any(), mBrowserConnCb.capture(), any());
+        MediaBrowser.ConnectionCallback browserConnCb = mBrowserConnCb.getValue();
+
+        verify(mMockBrowser, times(1)).connect();
+
+        browserConnCb.onConnected();
+        verify(mConnCb).run(eq(BrowsedPlayerWrapper.STATUS_CONN_ERROR), eq(wrapper));
+        verify(mMockBrowser, times(1)).disconnect();
+    }
+
+    @Test
+    public void testDisconnect() {
+        BrowsedPlayerWrapper wrapper =
+                BrowsedPlayerWrapper.wrap(mMockContext, mThread.getLooper(), "test", "test");
+        wrapper.connect(mConnCb);
+        verify(mMockBrowser).testInit(any(), any(), mBrowserConnCb.capture(), any());
+        MediaBrowser.ConnectionCallback browserConnCb = mBrowserConnCb.getValue();
+        browserConnCb.onConnected();
+        verify(mMockBrowser).disconnect();
+    }
+
+    @Test
+    public void testGetRootId() {
+        BrowsedPlayerWrapper wrapper =
+                BrowsedPlayerWrapper.wrap(mMockContext, mThread.getLooper(), "test", "test");
+        wrapper.connect(mConnCb);
+        verify(mMockBrowser).testInit(any(), any(), mBrowserConnCb.capture(), any());
+        MediaBrowser.ConnectionCallback browserConnCb = mBrowserConnCb.getValue();
+        browserConnCb.onConnected();
+
+        Assert.assertEquals("root_folder", wrapper.getRootId());
+        verify(mMockBrowser).disconnect();
+    }
+
+    @Test
+    public void testPlayItem() {
+        BrowsedPlayerWrapper wrapper =
+                BrowsedPlayerWrapper.wrap(mMockContext, mThread.getLooper(), "test", "test");
+        verify(mMockBrowser).testInit(any(), any(), mBrowserConnCb.capture(), any());
+        MediaBrowser.ConnectionCallback browserConnCb = mBrowserConnCb.getValue();
+
+        wrapper.playItem("test_item");
+        verify(mMockBrowser, times(1)).connect();
+
+        MediaController mockController = mock(MediaController.class);
+        MediaController.TransportControls mockTransport =
+                mock(MediaController.TransportControls.class);
+        when(mockController.getTransportControls()).thenReturn(mockTransport);
+        MediaControllerFactory.inject(mockController);
+
+        browserConnCb.onConnected();
+        verify(mockTransport).playFromMediaId(eq("test_item"), eq(null));
+
+        // Do not immediately disconnect. Non-foreground playback serves will likely fail
+        verify(mMockBrowser, times(0)).disconnect();
+
+        verify(mockController).registerCallback(mControllerCb.capture(), any());
+        MediaController.Callback controllerCb = mControllerCb.getValue();
+        PlaybackState.Builder builder = new PlaybackState.Builder();
+
+        // Do not disconnect on an event that isn't "playing"
+        builder.setState(PlaybackState.STATE_PAUSED, 0, 1);
+        controllerCb.onPlaybackStateChanged(builder.build());
+        verify(mMockBrowser, times(0)).disconnect();
+
+        // Once we're told we're playing, make sure we disconnect
+        builder.setState(PlaybackState.STATE_PLAYING, 0, 1);
+        controllerCb.onPlaybackStateChanged(builder.build());
+        verify(mMockBrowser, times(1)).disconnect();
+    }
+
+    @Test
+    public void testPlayItem_Timeout() {
+        BrowsedPlayerWrapper wrapper =
+                BrowsedPlayerWrapper.wrap(mMockContext, mThread.getLooper(), "test", "test");
+        verify(mMockBrowser).testInit(any(), any(), mBrowserConnCb.capture(), any());
+        MediaBrowser.ConnectionCallback browserConnCb = mBrowserConnCb.getValue();
+
+        wrapper.playItem("test_item");
+        verify(mMockBrowser, times(1)).connect();
+
+        MediaController mockController = mock(MediaController.class);
+        MediaController.TransportControls mockTransport =
+                mock(MediaController.TransportControls.class);
+        when(mockController.getTransportControls()).thenReturn(mockTransport);
+        MediaControllerFactory.inject(mockController);
+
+        browserConnCb.onConnected();
+        verify(mockTransport).playFromMediaId(eq("test_item"), eq(null));
+
+        verify(mockController).registerCallback(any(), mTimeoutHandler.capture());
+        Handler timeoutHandler = mTimeoutHandler.getValue();
+
+        timeoutHandler.sendEmptyMessage(BrowsedPlayerWrapper.TimeoutHandler.MSG_TIMEOUT);
+
+        verify(mMockBrowser, timeout(2000).times(1)).disconnect();
+    }
+
+    @Test
+    public void testGetFolderItems() {
+        BrowsedPlayerWrapper wrapper =
+                BrowsedPlayerWrapper.wrap(mMockContext, mThread.getLooper(), "test", "test");
+        verify(mMockBrowser).testInit(any(), any(), mBrowserConnCb.capture(), any());
+        MediaBrowser.ConnectionCallback browserConnCb = mBrowserConnCb.getValue();
+
+        wrapper.getFolderItems("test_folder", mBrowseCb);
+
+        browserConnCb.onConnected();
+        verify(mMockBrowser).subscribe(any(), mSubscriptionCb.capture());
+        MediaBrowser.SubscriptionCallback subscriptionCb = mSubscriptionCb.getValue();
+
+        MediaDescription desc = null;
+        ArrayList<MediaItem> items = new ArrayList<MediaItem>();
+
+        desc = getMediaDescription("s1", "song1", "artist", "album", null, null, null);
+        items.add(getMediaItem(desc, MediaItem.FLAG_PLAYABLE));
+
+        desc = getMediaDescription("s2", "song2", "artist", "album", mTestBitmap, null, null);
+        items.add(getMediaItem(desc, MediaItem.FLAG_PLAYABLE));
+
+        desc = getMediaDescription("s3", "song3", "artist", "album", null, IMAGE_URI_1, null);
+        items.add(getMediaItem(desc, MediaItem.FLAG_PLAYABLE));
+
+        desc = getMediaDescription("a1", "album1", "artist", null, null, null, null);
+        items.add(getMediaItem(desc, MediaItem.FLAG_BROWSABLE));
+
+        desc = getMediaDescription("a2", "album2", "artist", null, mTestBitmap, null, null);
+        items.add(getMediaItem(desc, MediaItem.FLAG_BROWSABLE));
+
+        desc = getMediaDescription("a3", "album3", "artist", null, null, IMAGE_URI_1, null);
+        items.add(getMediaItem(desc, MediaItem.FLAG_BROWSABLE));
+
+        desc = getMediaDescription("p1", "playlist1", null, null, mTestBitmap, null, null);
+        items.add(getMediaItem(desc, MediaItem.FLAG_BROWSABLE | MediaItem.FLAG_PLAYABLE));
+
+        subscriptionCb.onChildrenLoaded("test_folder", items);
+        verify(mMockBrowser).unsubscribe(eq("test_folder"));
+        verify(mBrowseCb).run(eq(BrowsedPlayerWrapper.STATUS_SUCCESS), eq("test_folder"),
+                mWrapperBrowseCb.capture());
+
+        // Verify returned ListItems
+        List<ListItem> item_list = mWrapperBrowseCb.getValue();
+        for (int i = 0; i < item_list.size(); i++) {
+            MediaItem expected = items.get(i);
+            ListItem item = item_list.get(i);
+            Assert.assertEquals(expected.isBrowsable(), item.isFolder);
+            if (item.isFolder) {
+                Folder folder = item.folder;
+                Assert.assertNotNull(folder);
+                Assert.assertFalse(folder.isPlayable);
+                Assert.assertEquals(expected.getDescription().getMediaId(), folder.mediaId);
+                Assert.assertEquals(expected.getDescription().getTitle(), folder.title);
+            } else {
+                Metadata song = item.song;
+                Assert.assertNotNull(song);
+                Assert.assertEquals(expected.getDescription().getMediaId(), song.mediaId);
+                Assert.assertEquals(expected.getDescription().getTitle(), song.title);
+                Assert.assertEquals(expected.getDescription().getSubtitle(), song.artist);
+                Assert.assertEquals(expected.getDescription().getDescription(), song.album);
+                if (expected.getDescription().getIconBitmap() != null) {
+                    Assert.assertNotNull(song.image);
+                    Bitmap expectedBitmap = expected.getDescription().getIconBitmap();
+                    Assert.assertTrue(expectedBitmap.sameAs(song.image.getImage()));
+                } else if (expected.getDescription().getIconUri() != null) {
+                    Assert.assertTrue(mTestBitmap.sameAs(song.image.getImage()));
+                } else {
+                    Assert.assertEquals(null, song.image);
+                }
+            }
+        }
+
+        verify(mMockBrowser).disconnect();
+    }
+}
diff --git a/tests/unit/src/com/android/bluetooth/audio_util/ImageTest.java b/tests/unit/src/com/android/bluetooth/audio_util/ImageTest.java
new file mode 100644
index 0000000..89bbf5c
--- /dev/null
+++ b/tests/unit/src/com/android/bluetooth/audio_util/ImageTest.java
@@ -0,0 +1,473 @@
+/*
+ * Copyright 2020 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.audio_util;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import static org.mockito.Mockito.when;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.content.res.AssetFileDescriptor;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.media.MediaDescription;
+import android.media.MediaMetadata;
+import android.net.Uri;
+import android.os.Bundle;
+import android.test.mock.MockContentProvider;
+import android.test.mock.MockContentResolver;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.io.InputStream;
+
+@RunWith(AndroidJUnit4.class)
+public class ImageTest {
+    private Context mTargetContext;
+
+    private @Mock Context mMockContext;
+    private Resources mTestResources;
+    private MockContentResolver mTestContentResolver;
+
+    private static final String TEST_AUTHORITY = "com.android.bluetooth.avrcp.test";
+    private static final Uri TEST_CONTENT_URI = new Uri.Builder()
+            .scheme(ContentResolver.SCHEME_CONTENT)
+            .authority(TEST_AUTHORITY)
+            .build();
+
+    private static final String IMAGE_HANDLE_1 = "0000001";
+    private static final Uri IMAGE_URI_1 = TEST_CONTENT_URI.buildUpon()
+            .appendQueryParameter("handle", IMAGE_HANDLE_1)
+            .build();
+    private static final String IMAGE_STRING_1 = IMAGE_URI_1.toString();
+
+    private static final String IMAGE_HANDLE_SECURITY_ERROR = "sec_error";
+    private static final Uri IMAGE_URI_SECURITY_ERROR = TEST_CONTENT_URI.buildUpon()
+            .appendQueryParameter("handle", IMAGE_HANDLE_SECURITY_ERROR)
+            .build();
+
+    private Bitmap mTestBitmap = null;
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+
+        mTargetContext = InstrumentationRegistry.getTargetContext();
+        try {
+            mTestResources = mTargetContext.getPackageManager()
+                    .getResourcesForApplication("com.android.bluetooth.tests");
+        } catch (PackageManager.NameNotFoundException e) {
+            assertWithMessage("Setup Failure Unable to get resources" + e.toString()).fail();
+        }
+
+        mTestBitmap = loadImage(com.android.bluetooth.tests.R.raw.image_200_200);
+
+        mTestContentResolver = new MockContentResolver(mTargetContext);
+        mTestContentResolver.addProvider(TEST_AUTHORITY, new MockContentProvider() {
+            @Override
+            public AssetFileDescriptor openTypedAssetFile(Uri url, String mimeType, Bundle opts) {
+                String handle = url.getQueryParameter("handle");
+                if (IMAGE_URI_1.equals(url)) {
+                    return mTestResources.openRawResourceFd(
+                            com.android.bluetooth.tests.R.raw.image_200_200);
+                } else if (IMAGE_URI_SECURITY_ERROR.equals(url)) {
+                    throw new SecurityException();
+                }
+                return null;
+            }
+        });
+
+        when(mMockContext.getContentResolver()).thenReturn(mTestContentResolver);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        mTestContentResolver = null;
+        mTestBitmap = null;
+        mTestResources = null;
+        mTargetContext = null;
+        mMockContext = null;
+    }
+
+    private Bitmap loadImage(int resId) {
+        InputStream imageInputStream = mTestResources.openRawResource(resId);
+        return BitmapFactory.decodeStream(imageInputStream);
+    }
+
+    private MediaMetadata getMediaMetadataWithoutArt() {
+        MediaMetadata.Builder builder = new MediaMetadata.Builder()
+                .putString(MediaMetadata.METADATA_KEY_TITLE, "BT Test Song")
+                .putString(MediaMetadata.METADATA_KEY_ARTIST, "BT Test Artist")
+                .putString(MediaMetadata.METADATA_KEY_ALBUM, "BT Test Album")
+                .putLong(MediaMetadata.METADATA_KEY_DURATION, 5000L);
+        return builder.build();
+    }
+
+    private MediaMetadata getMediaMetadataWithBitmap(String field, Bitmap image) {
+        MediaMetadata.Builder builder = new MediaMetadata.Builder()
+                .putString(MediaMetadata.METADATA_KEY_TITLE, "BT Test Song")
+                .putString(MediaMetadata.METADATA_KEY_ARTIST, "BT Test Artist")
+                .putString(MediaMetadata.METADATA_KEY_ALBUM, "BT Test Album")
+                .putLong(MediaMetadata.METADATA_KEY_DURATION, 5000L)
+                .putBitmap(field, image);
+        return builder.build();
+    }
+
+    private MediaMetadata getMediaMetadataWithUri(String field, String uri) {
+        MediaMetadata.Builder builder = new MediaMetadata.Builder()
+                .putString(MediaMetadata.METADATA_KEY_TITLE, "BT Test Song")
+                .putString(MediaMetadata.METADATA_KEY_ARTIST, "BT Test Artist")
+                .putString(MediaMetadata.METADATA_KEY_ALBUM, "BT Test Album")
+                .putLong(MediaMetadata.METADATA_KEY_DURATION, 5000L)
+                .putString(field, uri);
+        return builder.build();
+    }
+
+    private MediaDescription getMediaDescriptionWithoutArt() {
+        MediaDescription.Builder builder = new MediaDescription.Builder()
+                .setTitle("BT Test Song")
+                .setDescription("BT Test Description");
+        return builder.build();
+    }
+
+    private MediaDescription getMediaDescriptionWithBitmap(Bitmap image) {
+        MediaDescription.Builder builder = new MediaDescription.Builder()
+                .setTitle("BT Test Song")
+                .setDescription("BT Test Description")
+                .setIconBitmap(image);
+        return builder.build();
+    }
+
+    private MediaDescription getMediaDescriptionWithUri(Uri uri) {
+        MediaDescription.Builder builder = new MediaDescription.Builder()
+                .setTitle("BT Test Song")
+                .setDescription("BT Test Description")
+                .setIconUri(uri);
+        return builder.build();
+    }
+
+    private Bundle getBundleWithBitmap(String field, Bitmap image) {
+        Bundle bundle = new Bundle();
+        bundle.putParcelable(field, image);
+        return bundle;
+    }
+
+    private Bundle getBundleWithUri(String field, String uri) {
+        Bundle bundle = new Bundle();
+        bundle.putString(field, uri);
+        return bundle;
+    }
+
+    /**
+     * Make sure you can create an Image from a MediaMetadata object that contains cover artwork
+     * as an Art Bitmap
+     */
+    @Test
+    public void testCreateImageFromMediaMetadataWithArt() {
+        MediaMetadata metadata =
+                getMediaMetadataWithBitmap(MediaMetadata.METADATA_KEY_ART, mTestBitmap);
+        Image artwork = new Image(mMockContext, metadata);
+        assertThat(mTestBitmap.sameAs(artwork.getImage())).isTrue();
+        assertThat(artwork.getSource()).isEqualTo(Image.SOURCE_BITMAP);
+        assertThat(artwork.getImageHandle()).isNull();
+    }
+
+    /**
+     * Make sure you can create an Image from a MediaMetadata object that contains cover artwork
+     * as an Album Art Bitmap
+     */
+    @Test
+    public void testCreateImageFromMediaMetadataWithAlbumArt() {
+        MediaMetadata metadata =
+                getMediaMetadataWithBitmap(MediaMetadata.METADATA_KEY_ALBUM_ART, mTestBitmap);
+        Image artwork = new Image(mMockContext, metadata);
+        assertThat(mTestBitmap.sameAs(artwork.getImage())).isTrue();
+        assertThat(artwork.getSource()).isEqualTo(Image.SOURCE_BITMAP);
+        assertThat(artwork.getImageHandle()).isNull();
+    }
+
+    /**
+     * Make sure you can create an Image from a MediaMetadata object that contains cover artwork
+     * as a Display Icon Bitmap
+     */
+    @Test
+    public void testCreateImageFromMediaMetadataWithDisplayIcon() {
+        MediaMetadata metadata =
+                getMediaMetadataWithBitmap(MediaMetadata.METADATA_KEY_DISPLAY_ICON, mTestBitmap);
+        Image artwork = new Image(mMockContext, metadata);
+        assertThat(mTestBitmap.sameAs(artwork.getImage())).isTrue();
+        assertThat(artwork.getSource()).isEqualTo(Image.SOURCE_BITMAP);
+        assertThat(artwork.getImageHandle()).isNull();
+    }
+
+    /**
+     * Make sure you can create an Image from a MediaMetadata object that contains cover artwork
+     * as an Art Uri
+     */
+    @Test
+    public void testCreateImageFromMediaMetadataWithArtUri() {
+        MediaMetadata metadata =
+                getMediaMetadataWithUri(MediaMetadata.METADATA_KEY_ART_URI, IMAGE_STRING_1);
+        Image artwork = new Image(mMockContext, metadata);
+        assertThat(mTestBitmap.sameAs(artwork.getImage())).isTrue();
+        assertThat(artwork.getSource()).isEqualTo(Image.SOURCE_URI);
+        assertThat(artwork.getImageHandle()).isNull();
+    }
+
+    /**
+     * Make sure you can create an Image from a MediaMetadata object that contains cover artwork
+     * as an Album Art Uri
+     */
+    @Test
+    public void testCreateImageFromMediaMetadataWithAlbumArtUri() {
+        MediaMetadata metadata =
+                getMediaMetadataWithUri(MediaMetadata.METADATA_KEY_ALBUM_ART_URI, IMAGE_STRING_1);
+        Image artwork = new Image(mMockContext, metadata);
+        assertThat(mTestBitmap.sameAs(artwork.getImage())).isTrue();
+        assertThat(artwork.getSource()).isEqualTo(Image.SOURCE_URI);
+        assertThat(artwork.getImageHandle()).isNull();
+    }
+
+    /**
+     * Make sure you can create an Image from a MediaMetadata object that contains cover artwork
+     * as a Display Icon Uri
+     */
+    @Test
+    public void testCreateImageFromMediaMetadataWithDisplayIconUri() {
+        MediaMetadata metadata =
+                getMediaMetadataWithUri(MediaMetadata.METADATA_KEY_DISPLAY_ICON_URI,
+                        IMAGE_STRING_1);
+        Image artwork = new Image(mMockContext, metadata);
+        assertThat(mTestBitmap.sameAs(artwork.getImage())).isTrue();
+        assertThat(artwork.getSource()).isEqualTo(Image.SOURCE_URI);
+        assertThat(artwork.getImageHandle()).isNull();
+    }
+
+    /**
+     * Make sure you can create an Image from a MediaMetadata object that contains no cover artwork
+     */
+    @Test
+    public void testCreateImageFromMediaMetadataWithoutArtwork() {
+        MediaMetadata metadata = getMediaMetadataWithoutArt();
+        Image artwork = new Image(mMockContext, metadata);
+        assertThat(artwork.getImage()).isNull();
+        assertThat(artwork.getSource()).isEqualTo(Image.SOURCE_NONE);
+        assertThat(artwork.getImageHandle()).isNull();
+    }
+
+    /**
+     * Make sure you can create an image from a MediaDescription object that contains cover artwork
+     * as a Uri
+     */
+    @Test
+    public void testCreateImageFromMediaDescriptionWithImage() {
+        MediaDescription description = getMediaDescriptionWithBitmap(mTestBitmap);
+        Image artwork = new Image(mMockContext, description);
+        assertThat(mTestBitmap.sameAs(artwork.getImage())).isTrue();
+        assertThat(artwork.getSource()).isEqualTo(Image.SOURCE_BITMAP);
+        assertThat(artwork.getImageHandle()).isNull();
+    }
+
+    /**
+     * Make sure you can create an image from a MediaDescription object that contains cover artwork
+     * as a Bitmap Image
+     */
+    @Test
+    public void testCreateImageFromMediaDescriptionWithUri() {
+        MediaDescription description = getMediaDescriptionWithUri(IMAGE_URI_1);
+        Image artwork = new Image(mMockContext, description);
+        assertThat(mTestBitmap.sameAs(artwork.getImage())).isTrue();
+        assertThat(artwork.getSource()).isEqualTo(Image.SOURCE_URI);
+        assertThat(artwork.getImageHandle()).isNull();
+    }
+
+    /**
+     * Make sure you can create an image from a MediaDescription object that contains no cover
+     * artwork
+     */
+    @Test
+    public void testCreateImageFromMediaDescriptionWithoutArtwork() {
+        MediaDescription description = getMediaDescriptionWithoutArt();
+        Image artwork = new Image(mMockContext, description);
+        assertThat(artwork.getImage()).isNull();
+        assertThat(artwork.getSource()).isEqualTo(Image.SOURCE_NONE);
+        assertThat(artwork.getImageHandle()).isNull();
+    }
+
+    /**
+     * Make sure you can create an Image from a Bundle that contains cover artwork as an Art Bitmap
+     */
+    @Test
+    public void testCreateImageFromBundleWithArt() {
+        Bundle bundle = getBundleWithBitmap(MediaMetadata.METADATA_KEY_ART, mTestBitmap);
+        Image artwork = new Image(mMockContext, bundle);
+        assertThat(mTestBitmap.sameAs(artwork.getImage())).isTrue();
+        assertThat(artwork.getSource()).isEqualTo(Image.SOURCE_BITMAP);
+        assertThat(artwork.getImageHandle()).isNull();
+    }
+
+    /**
+     * Make sure you can create an Image from a Bundle that contains cover artwork as an Album Art
+     * Bitmap
+     */
+    @Test
+    public void testCreateImageFromBundleWithAlbumArt() {
+        Bundle bundle = getBundleWithBitmap(MediaMetadata.METADATA_KEY_ALBUM_ART, mTestBitmap);
+        Image artwork = new Image(mMockContext, bundle);
+        assertThat(mTestBitmap.sameAs(artwork.getImage())).isTrue();
+        assertThat(artwork.getSource()).isEqualTo(Image.SOURCE_BITMAP);
+        assertThat(artwork.getImageHandle()).isNull();
+    }
+
+    /**
+     * Make sure you can create an Image from a Bundle that contains cover artwork as a Display
+     * Icon Bitmap
+     */
+    @Test
+    public void testCreateImageFromBundleWithDisplayIcon() {
+        Bundle bundle = getBundleWithBitmap(MediaMetadata.METADATA_KEY_DISPLAY_ICON, mTestBitmap);
+        Image artwork = new Image(mMockContext, bundle);
+        assertThat(mTestBitmap.sameAs(artwork.getImage())).isTrue();
+        assertThat(artwork.getSource()).isEqualTo(Image.SOURCE_BITMAP);
+        assertThat(artwork.getImageHandle()).isNull();
+    }
+
+    /**
+     * Make sure you can create an Image from a Bundle that contains cover artwork as an Art Uri
+     */
+    @Test
+    public void testCreateImageFromBundleWithArtUri() {
+        Bundle bundle = getBundleWithUri(MediaMetadata.METADATA_KEY_ART_URI, IMAGE_STRING_1);
+        Image artwork = new Image(mMockContext, bundle);
+        assertThat(mTestBitmap.sameAs(artwork.getImage())).isTrue();
+        assertThat(artwork.getSource()).isEqualTo(Image.SOURCE_URI);
+        assertThat(artwork.getImageHandle()).isNull();
+    }
+
+    /**
+     * Make sure you can create an Image from a Bundle that contains cover artwork as an Album Art
+     * Uri
+     */
+    @Test
+    public void testCreateImageFromBundleWithAlbumArtUri() {
+        Bundle bundle = getBundleWithUri(MediaMetadata.METADATA_KEY_ALBUM_ART_URI, IMAGE_STRING_1);
+        Image artwork = new Image(mMockContext, bundle);
+        assertThat(mTestBitmap.sameAs(artwork.getImage())).isTrue();
+        assertThat(artwork.getSource()).isEqualTo(Image.SOURCE_URI);
+        assertThat(artwork.getImageHandle()).isNull();
+    }
+
+    /**
+     * Make sure you can create an Image from a Bundle that contains cover artwork as a Display
+     * Icon Uri
+     */
+    @Test
+    public void testCreateImageFromBundleWithDisplayIconUri() {
+        Bundle bundle =
+                getBundleWithUri(MediaMetadata.METADATA_KEY_DISPLAY_ICON_URI, IMAGE_STRING_1);
+        Image artwork = new Image(mMockContext, bundle);
+        assertThat(mTestBitmap.sameAs(artwork.getImage())).isTrue();
+        assertThat(artwork.getSource()).isEqualTo(Image.SOURCE_URI);
+        assertThat(artwork.getImageHandle()).isNull();
+    }
+
+    /**
+     * Make sure you can create an Image from a Bundle that contains no cover artwork
+     */
+    @Test
+    public void testCreateImageFromBundleWithoutArtwork() {
+        Bundle bundle = new Bundle();
+        Image artwork = new Image(mMockContext, bundle);
+        assertThat(artwork.getImage()).isNull();
+        assertThat(artwork.getSource()).isEqualTo(Image.SOURCE_NONE);
+        assertThat(artwork.getImageHandle()).isNull();
+    }
+
+    /**
+     * Make sure you can create an image from a simple Uri
+     */
+    @Test
+    public void testCreateImageFromUri() {
+        Image artwork = new Image(mMockContext, IMAGE_URI_1);
+        assertThat(mTestBitmap.sameAs(artwork.getImage())).isTrue();
+        assertThat(artwork.getSource()).isEqualTo(Image.SOURCE_URI);
+        assertThat(artwork.getImageHandle()).isNull();
+    }
+
+    /**
+     * Make sure you can create an image from a simple Bitmap Image
+     */
+    @Test
+    public void testCreateImageFromBitmap() {
+        Image artwork = new Image(mMockContext, mTestBitmap);
+        assertThat(mTestBitmap.sameAs(artwork.getImage())).isTrue();
+        assertThat(artwork.getSource()).isEqualTo(Image.SOURCE_BITMAP);
+        assertThat(artwork.getImageHandle()).isNull();
+    }
+
+    /**
+     * Make sure you can get the image handle associated with this object when there is none set
+     */
+    @Test
+    public void testGetImageHandleWithEmptyHandle() {
+        Image artwork = new Image(mMockContext, mTestBitmap);
+        assertThat(artwork.getImageHandle()).isNull();
+    }
+
+    /**
+     * Make sure you can get and set the image handle associated with this object
+     */
+    @Test
+    public void testSetAndGetImageHandle() {
+        Image artwork = new Image(mMockContext, mTestBitmap);
+        artwork.setImageHandle(IMAGE_HANDLE_1);
+        assertThat(artwork.getImageHandle()).isEqualTo(IMAGE_HANDLE_1);
+    }
+
+    /**
+     * Make sure image URI resolution with erroneous resources doesn't crash and results in a null
+     * image.
+     */
+    @Test
+    public void testLoadImageFromUriWithSecurityException() {
+        Image artwork = new Image(mMockContext, IMAGE_URI_SECURITY_ERROR);
+        assertThat(artwork.getImageHandle()).isNull();
+        assertThat(artwork.getSource()).isEqualTo(Image.SOURCE_NONE);
+        assertThat(artwork.getImage()).isNull();
+    }
+
+    /**
+     * Make sure you can get a string representation of this Image
+     */
+    @Test
+    public void testToString() {
+        Image artwork = new Image(mMockContext, mTestBitmap);
+        assertThat(artwork.toString()).isNotNull();
+    }
+}
diff --git a/tests/unit/src/com/android/bluetooth/newavrcp/MediaPlayerListTest.java b/tests/unit/src/com/android/bluetooth/audio_util/MediaPlayerListTest.java
similarity index 92%
rename from tests/unit/src/com/android/bluetooth/newavrcp/MediaPlayerListTest.java
rename to tests/unit/src/com/android/bluetooth/audio_util/MediaPlayerListTest.java
index 7d02f77..44054f3 100644
--- a/tests/unit/src/com/android/bluetooth/newavrcp/MediaPlayerListTest.java
+++ b/tests/unit/src/com/android/bluetooth/audio_util/MediaPlayerListTest.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.bluetooth.avrcp;
+package com.android.bluetooth.audio_util;
 
 import static org.mockito.Mockito.*;
 
@@ -53,7 +53,7 @@
     private @Captor ArgumentCaptor<MediaPlayerWrapper.Callback> mPlayerWrapperCb;
     private @Captor ArgumentCaptor<MediaData> mMediaUpdateData;
     private @Mock Context mMockContext;
-    private @Mock AvrcpTargetService.ListCallback mAvrcpCallback;
+    private @Mock MediaPlayerList.MediaUpdateCallback mMediaUpdateCallback;
     private @Mock MediaController mMockController;
     private @Mock MediaPlayerWrapper mMockPlayerWrapper;
 
@@ -89,7 +89,6 @@
         when(mMockContext.registerReceiver(any(), any())).thenReturn(null);
         when(mMockContext.getApplicationContext()).thenReturn(mMockContext);
         when(mMockContext.getPackageManager()).thenReturn(mockPackageManager);
-        List<ResolveInfo> playerList = new ArrayList<ResolveInfo>();
         when(mockPackageManager.queryIntentServices(any(), anyInt())).thenReturn(null);
 
         Method method = BrowsablePlayerConnector.class.getDeclaredMethod("setInstanceForTesting",
@@ -97,7 +96,7 @@
         BrowsablePlayerConnector mockConnector = mock(BrowsablePlayerConnector.class);
         method.setAccessible(true);
         method.invoke(null, mockConnector);
-        mMediaPlayerList.init(mAvrcpCallback);
+        mMediaPlayerList.init(mMediaUpdateCallback);
 
         MediaControllerFactory.inject(mMockController);
         MediaPlayerWrapperFactory.inject(mMockPlayerWrapper);
@@ -144,7 +143,7 @@
         doReturn(prepareMediaData(PlaybackState.STATE_PAUSED))
             .when(mMockPlayerWrapper).getCurrentMediaData();
         mMediaPlayerList.injectAudioPlaybacActive(true);
-        verify(mAvrcpCallback).run(mMediaUpdateData.capture());
+        verify(mMediaUpdateCallback).run(mMediaUpdateData.capture());
         MediaData data = mMediaUpdateData.getValue();
         Assert.assertEquals(data.state.getState(), PlaybackState.STATE_PLAYING);
 
@@ -152,7 +151,7 @@
         MediaData currentMediaData = prepareMediaData(PlaybackState.STATE_PAUSED);
         doReturn(currentMediaData).when(mMockPlayerWrapper).getCurrentMediaData();
         mMediaPlayerList.injectAudioPlaybacActive(false);
-        verify(mAvrcpCallback, times(2)).run(mMediaUpdateData.capture());
+        verify(mMediaUpdateCallback, times(2)).run(mMediaUpdateData.capture());
         data = mMediaUpdateData.getValue();
         Assert.assertEquals(data.metadata, currentMediaData.metadata);
         Assert.assertEquals(data.state.toString(), currentMediaData.state.toString());
@@ -163,11 +162,11 @@
     public void testUpdateMediaDataForActivePlayerWhenAudioPlaybackIsNotActive() {
         MediaData currMediaData = prepareMediaData(PlaybackState.STATE_PLAYING);
         mActivePlayerCallback.mediaUpdatedCallback(currMediaData);
-        verify(mAvrcpCallback).run(currMediaData);
+        verify(mMediaUpdateCallback).run(currMediaData);
 
         currMediaData = prepareMediaData(PlaybackState.STATE_PAUSED);
         mActivePlayerCallback.mediaUpdatedCallback(currMediaData);
-        verify(mAvrcpCallback).run(currMediaData);
+        verify(mMediaUpdateCallback).run(currMediaData);
     }
 
     @Test
@@ -177,7 +176,7 @@
             .when(mMockPlayerWrapper).getCurrentMediaData();
         mMediaPlayerList.injectAudioPlaybacActive(true);
         mMediaPlayerList.injectAudioPlaybacActive(false);
-        verify(mAvrcpCallback, never()).run(any());
+        verify(mMediaUpdateCallback, never()).run(any());
     }
 
     @Test
@@ -185,11 +184,11 @@
         doReturn(prepareMediaData(PlaybackState.STATE_PLAYING))
             .when(mMockPlayerWrapper).getCurrentMediaData();
         mMediaPlayerList.injectAudioPlaybacActive(true);
-        verify(mAvrcpCallback, never()).run(any());
+        verify(mMediaUpdateCallback, never()).run(any());
 
         // Verify not update active player media data when audio playback is active
         mActivePlayerCallback.mediaUpdatedCallback(prepareMediaData(PlaybackState.STATE_PAUSED));
-        verify(mAvrcpCallback, never()).run(any());
+        verify(mMediaUpdateCallback, never()).run(any());
     }
 
 }
diff --git a/tests/unit/src/com/android/bluetooth/newavrcp/MediaPlayerWrapperTest.java b/tests/unit/src/com/android/bluetooth/audio_util/MediaPlayerWrapperTest.java
similarity index 88%
rename from tests/unit/src/com/android/bluetooth/newavrcp/MediaPlayerWrapperTest.java
rename to tests/unit/src/com/android/bluetooth/audio_util/MediaPlayerWrapperTest.java
index 1e41033..8a68ae6 100644
--- a/tests/unit/src/com/android/bluetooth/newavrcp/MediaPlayerWrapperTest.java
+++ b/tests/unit/src/com/android/bluetooth/audio_util/MediaPlayerWrapperTest.java
@@ -14,10 +14,15 @@
  * limitations under the License.
  */
 
-package com.android.bluetooth.avrcp;
+package com.android.bluetooth.audio_util;
 
 import static org.mockito.Mockito.*;
 
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
 import android.media.MediaDescription;
 import android.media.MediaMetadata;
 import android.media.session.MediaSession;
@@ -30,6 +35,8 @@
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.bluetooth.R;
+
 import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Test;
@@ -39,6 +46,7 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
+import java.io.InputStream;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
@@ -49,16 +57,21 @@
 public class MediaPlayerWrapperTest {
     private static final int MSG_TIMEOUT = 0;
 
+    private Context mTargetContext;
+    private Resources mTestResources;
     private HandlerThread mThread;
     private MediaMetadata.Builder mTestMetadata;
     private ArrayList<MediaDescription.Builder> mTestQueue;
     private PlaybackState.Builder mTestState;
+    private Bitmap mTestBitmap;
 
     @Captor ArgumentCaptor<MediaController.Callback> mControllerCbs;
     @Captor ArgumentCaptor<MediaData> mMediaUpdateData;
     @Mock Log.TerribleFailureHandler mFailHandler;
     @Mock MediaController mMockController;
     @Mock MediaPlayerWrapper.Callback mTestCbs;
+    @Mock Context mMockContext;
+    @Mock Resources mMockResources;
 
     List<MediaSession.QueueItem> getQueueFromDescriptions(
             List<MediaDescription.Builder> descriptions) {
@@ -77,6 +90,18 @@
     public void setUp() {
         MockitoAnnotations.initMocks(this);
 
+        mTargetContext = InstrumentationRegistry.getTargetContext();
+        try {
+            mTestResources = mTargetContext.getPackageManager()
+                    .getResourcesForApplication("com.android.bluetooth.tests");
+        } catch (PackageManager.NameNotFoundException e) {
+            Assert.fail("Setup Failure Unable to get resources" + e.toString());
+        }
+        mTestBitmap = loadImage(com.android.bluetooth.tests.R.raw.image_200_200);
+
+        when(mMockResources.getBoolean(R.bool.avrcp_target_cover_art_uri_images)).thenReturn(true);
+        when(mMockContext.getResources()).thenReturn(mMockResources);
+
         // Set failure handler to capture Log.wtf messages
         Log.setWtfHandler(mFailHandler);
 
@@ -90,7 +115,8 @@
                         .putString(MediaMetadata.METADATA_KEY_TITLE, "BT Test Song")
                         .putString(MediaMetadata.METADATA_KEY_ARTIST, "BT Test Artist")
                         .putString(MediaMetadata.METADATA_KEY_ALBUM, "BT Test Album")
-                        .putLong(MediaMetadata.METADATA_KEY_DURATION, 5000L);
+                        .putLong(MediaMetadata.METADATA_KEY_DURATION, 5000L)
+                        .putBitmap(MediaMetadata.METADATA_KEY_ART, mTestBitmap);
 
         mTestState =
                 new PlaybackState.Builder()
@@ -103,18 +129,21 @@
                         .setTitle("BT Test Song")
                         .setSubtitle("BT Test Artist")
                         .setDescription("BT Test Album")
+                        .setIconBitmap(mTestBitmap)
                         .setMediaId("100"));
         mTestQueue.add(
                 new MediaDescription.Builder()
                         .setTitle("BT Test Song 2")
                         .setSubtitle("BT Test Artist 2")
                         .setDescription("BT Test Album 2")
+                        .setIconBitmap(mTestBitmap)
                         .setMediaId("101"));
         mTestQueue.add(
                 new MediaDescription.Builder()
                         .setTitle("BT Test Song 3")
                         .setSubtitle("BT Test Artist 3")
                         .setDescription("BT Test Album 3")
+                        .setIconBitmap(mTestBitmap)
                         .setMediaId("102"));
 
         when(mMockController.getPackageName()).thenReturn("mMockController");
@@ -132,16 +161,22 @@
         MediaPlayerWrapper.sTesting = true;
     }
 
+    private Bitmap loadImage(int resId) {
+        InputStream imageInputStream = mTestResources.openRawResource(resId);
+        return BitmapFactory.decodeStream(imageInputStream);
+    }
+
     /*
      * Test to make sure that the wrapper fails to be built if passed invalid
      * data.
      */
     @Test
     public void testNullControllerLooper() {
-        MediaPlayerWrapper wrapper = MediaPlayerWrapperFactory.wrap(null, mThread.getLooper());
+        MediaPlayerWrapper wrapper =
+                MediaPlayerWrapperFactory.wrap(mMockContext, null, mThread.getLooper());
         Assert.assertNull(wrapper);
 
-        wrapper = MediaPlayerWrapperFactory.wrap(mMockController, null);
+        wrapper = MediaPlayerWrapperFactory.wrap(mMockContext, mMockController, null);
         Assert.assertNull(wrapper);
     }
 
@@ -152,7 +187,7 @@
     @Test
     public void testIsReady() {
         MediaPlayerWrapper wrapper =
-                MediaPlayerWrapperFactory.wrap(mMockController, mThread.getLooper());
+                MediaPlayerWrapperFactory.wrap(mMockContext, mMockController, mThread.getLooper());
         Assert.assertTrue(wrapper.isPlaybackStateReady());
         Assert.assertTrue(wrapper.isMetadataReady());
 
@@ -181,7 +216,7 @@
     public void testControllerUpdate() {
         // Create the wrapper object and register the looper with the timeout handler
         MediaPlayerWrapper wrapper =
-                MediaPlayerWrapperFactory.wrap(mMockController, mThread.getLooper());
+                MediaPlayerWrapperFactory.wrap(mMockContext, mMockController, mThread.getLooper());
         Assert.assertTrue(wrapper.isPlaybackStateReady());
         Assert.assertTrue(wrapper.isMetadataReady());
         wrapper.registerCallback(mTestCbs);
@@ -215,7 +250,7 @@
         // Create the wrapper object and register the looper with the timeout handler
         TestLooperManager looperManager = new TestLooperManager(mThread.getLooper());
         MediaPlayerWrapper wrapper =
-                MediaPlayerWrapperFactory.wrap(mMockController, mThread.getLooper());
+                MediaPlayerWrapperFactory.wrap(mMockContext, mMockController, mThread.getLooper());
         wrapper.registerCallback(mTestCbs);
 
         // Return null when getting the queue
@@ -236,7 +271,7 @@
         Assert.assertEquals(
                 "Returned Metadata isn't equal to given Metadata",
                 data.metadata,
-                Util.toMetadata(mTestMetadata.build()));
+                Util.toMetadata(mMockContext, mTestMetadata.build()));
         Assert.assertEquals(
                 "Returned PlaybackState isn't equal to original PlaybackState",
                 data.state.toString(),
@@ -258,7 +293,7 @@
         Assert.assertEquals(
                 "Returned Metadata isn't equal to given Metadata",
                 data.metadata,
-                Util.toMetadata(mTestMetadata.build()));
+                Util.toMetadata(mMockContext, mTestMetadata.build()));
         Assert.assertEquals("Returned Queue isn't empty", data.queue.size(), 0);
 
         // Verify that there are no timeout messages pending and there were no timeouts
@@ -277,7 +312,7 @@
         // Create the wrapper object and register the looper with the timeout handler
         TestLooperManager looperManager = new TestLooperManager(mThread.getLooper());
         MediaPlayerWrapper wrapper =
-                MediaPlayerWrapperFactory.wrap(mMockController, mThread.getLooper());
+                MediaPlayerWrapperFactory.wrap(mMockContext, mMockController, mThread.getLooper());
         wrapper.registerCallback(mTestCbs);
 
         // Return null when getting the queue
@@ -308,7 +343,7 @@
         Assert.assertEquals(
                 "Returned Metadata isn't equal to given Metadata",
                 data.metadata,
-                Util.toMetadata(mTestMetadata.build()));
+                Util.toMetadata(mMockContext, mTestMetadata.build()));
         Assert.assertEquals("Returned Queue isn't empty", data.queue.size(), 0);
 
         // Verify that there are no timeout messages pending and there were no timeouts
@@ -321,7 +356,7 @@
         // Create the wrapper object and register the looper with the timeout handler
         TestLooperManager looperManager = new TestLooperManager(mThread.getLooper());
         MediaPlayerWrapper wrapper =
-                MediaPlayerWrapperFactory.wrap(mMockController, mThread.getLooper());
+                MediaPlayerWrapperFactory.wrap(mMockContext, mMockController, mThread.getLooper());
         wrapper.registerCallback(mTestCbs);
 
         // Return null when getting the queue
@@ -342,7 +377,7 @@
         verify(mTestCbs, times(1)).mediaUpdatedCallback(mMediaUpdateData.capture());
         MediaData data = mMediaUpdateData.getValue();
         Assert.assertEquals("Returned metadata is incorrect", data.metadata,
-                Util.toMetadata(mTestMetadata.build()));
+                Util.toMetadata(mMockContext, mTestMetadata.build()));
     }
 
     @Test
@@ -350,7 +385,7 @@
         // Create the wrapper object and register the looper with the timeout handler
         TestLooperManager looperManager = new TestLooperManager(mThread.getLooper());
         MediaPlayerWrapper wrapper =
-                MediaPlayerWrapperFactory.wrap(mMockController, mThread.getLooper());
+                MediaPlayerWrapperFactory.wrap(mMockContext, mMockController, mThread.getLooper());
         wrapper.registerCallback(mTestCbs);
 
         // Return null when getting the queue
@@ -377,13 +412,13 @@
         // Create the wrapper object and register the looper with the timeout handler
         TestLooperManager looperManager = new TestLooperManager(mThread.getLooper());
         MediaPlayerWrapper wrapper =
-                MediaPlayerWrapperFactory.wrap(mMockController, mThread.getLooper());
+                MediaPlayerWrapperFactory.wrap(mMockContext, mMockController, mThread.getLooper());
         wrapper.registerCallback(mTestCbs);
 
         // Call getCurrentQueue() multiple times.
         for (int i = 0; i < 3; i++) {
             Assert.assertEquals(wrapper.getCurrentQueue(),
-                    Util.toMetadataList(getQueueFromDescriptions(mTestQueue)));
+                    Util.toMetadataList(mMockContext, getQueueFromDescriptions(mTestQueue)));
         }
 
         // Verify that getQueue() was only called twice. Once on creation and once during
@@ -400,7 +435,7 @@
         // Create the wrapper object and register the looper with the timeout handler
         TestLooperManager looperManager = new TestLooperManager(mThread.getLooper());
         MediaPlayerWrapper wrapper =
-                MediaPlayerWrapperFactory.wrap(mMockController, mThread.getLooper());
+                MediaPlayerWrapperFactory.wrap(mMockContext, mMockController, mThread.getLooper());
         wrapper.registerCallback(mTestCbs);
 
         // Return null when getting the queue
@@ -425,7 +460,7 @@
         Assert.assertEquals(
                 "Returned Metadata isn't equal to given Metadata",
                 data.metadata,
-                Util.toMetadata(mTestMetadata.build()));
+                Util.toMetadata(mMockContext, mTestMetadata.build()));
         Assert.assertEquals("Returned Queue isn't empty", data.queue.size(), 0);
 
         // Update PlaybackState returned by controller (Shouldn't trigger update)
@@ -457,7 +492,7 @@
         // Create the wrapper object and register the looper with the timeout handler
         TestLooperManager looperManager = new TestLooperManager(mThread.getLooper());
         MediaPlayerWrapper wrapper =
-                MediaPlayerWrapperFactory.wrap(mMockController, mThread.getLooper());
+                MediaPlayerWrapperFactory.wrap(mMockContext, mMockController, mThread.getLooper());
         wrapper.registerCallback(mTestCbs);
 
         // Cleanup the wrapper
@@ -477,7 +512,7 @@
         // Create the wrapper object and register the looper with the timeout handler
         TestLooperManager looperManager = new TestLooperManager(mThread.getLooper());
         MediaPlayerWrapper wrapper =
-                MediaPlayerWrapperFactory.wrap(mMockController, mThread.getLooper());
+                MediaPlayerWrapperFactory.wrap(mMockContext, mMockController, mThread.getLooper());
         wrapper.registerCallback(mTestCbs);
 
         // Grab the callbacks the wrapper registered with the controller
@@ -507,7 +542,7 @@
         // Create the wrapper object and register the looper with the timeout handler
         TestLooperManager looperManager = new TestLooperManager(mThread.getLooper());
         MediaPlayerWrapper wrapper =
-                MediaPlayerWrapperFactory.wrap(mMockController, mThread.getLooper());
+                MediaPlayerWrapperFactory.wrap(mMockContext, mMockController, mThread.getLooper());
         wrapper.registerCallback(mTestCbs);
 
         // Grab the callbacks the wrapper registered with the controller
@@ -530,6 +565,7 @@
                         .setTitle("New Title")
                         .setSubtitle("BT Test Artist")
                         .setDescription("BT Test Album")
+                        .setIconBitmap(mTestBitmap)
                         .setMediaId("103"));
         doReturn(getQueueFromDescriptions(mTestQueue)).when(mMockController).getQueue();
         controllerCallbacks.onQueueChanged(getQueueFromDescriptions(mTestQueue));
@@ -541,7 +577,7 @@
         Assert.assertEquals(
                 "Returned Metadata isn't equal to given Metadata",
                 data.metadata,
-                Util.toMetadata(mTestMetadata.build()));
+                Util.toMetadata(mMockContext, mTestMetadata.build()));
         Assert.assertEquals(
                 "Returned PlaybackState isn't equal to given PlaybackState",
                 data.state.toString(),
@@ -549,7 +585,7 @@
         Assert.assertEquals(
                 "Returned Queue isn't equal to given Queue",
                 data.queue,
-                Util.toMetadataList(getQueueFromDescriptions(mTestQueue)));
+                Util.toMetadataList(mMockContext, getQueueFromDescriptions(mTestQueue)));
 
         // Verify that there are no timeout messages pending and there were no timeouts
         Assert.assertFalse(wrapper.getTimeoutHandler().hasMessages(MSG_TIMEOUT));
@@ -567,7 +603,7 @@
                 InstrumentationRegistry.getInstrumentation()
                         .acquireLooperManager(mThread.getLooper());
         MediaPlayerWrapper wrapper =
-                MediaPlayerWrapperFactory.wrap(mMockController, mThread.getLooper());
+                MediaPlayerWrapperFactory.wrap(mMockContext, mMockController, mThread.getLooper());
         wrapper.registerCallback(mTestCbs);
 
         // Grab the callbacks the wrapper registered with the controller
@@ -591,7 +627,7 @@
         Assert.assertEquals(
                 "Returned Metadata isn't equal to given Metadata",
                 data.metadata,
-                Util.toMetadata(mTestMetadata.build()));
+                Util.toMetadata(mMockContext, mTestMetadata.build()));
         Assert.assertEquals(
                 "Returned PlaybackState isn't equal to given PlaybackState",
                 data.state.toString(),
@@ -599,7 +635,7 @@
         Assert.assertEquals(
                 "Returned Queue isn't equal to given Queue",
                 data.queue,
-                Util.toMetadataList(getQueueFromDescriptions(mTestQueue)));
+                Util.toMetadataList(mMockContext, getQueueFromDescriptions(mTestQueue)));
     }
 
     /*
@@ -617,7 +653,7 @@
                 InstrumentationRegistry.getInstrumentation()
                         .acquireLooperManager(mThread.getLooper());
         MediaPlayerWrapper wrapper =
-                MediaPlayerWrapperFactory.wrap(mMockController, mThread.getLooper());
+                MediaPlayerWrapperFactory.wrap(mMockContext, mMockController, mThread.getLooper());
         wrapper.registerCallback(mTestCbs);
 
         // Grab the callbacks the wrapper registered with the controller
@@ -672,14 +708,14 @@
             Assert.assertEquals(
                     "Returned Metadata isn't equal to given Metadata",
                     data.metadata,
-                    Util.toMetadata(m.build()));
+                    Util.toMetadata(mMockContext, m.build()));
             Assert.assertEquals(
                     "Returned PlaybackState isn't equal to given PlaybackState",
                     data.state.toString(),
                     s.build().toString());
             Assert.assertEquals("Returned Queue isn't equal to given Queue",
                     data.queue,
-                    Util.toMetadataList(q));
+                    Util.toMetadataList(mMockContext, q));
         }
 
         // Verify that there are no timeout messages pending and there were no timeouts
diff --git a/tests/unit/src/com/android/bluetooth/audio_util/MetadataTest.java b/tests/unit/src/com/android/bluetooth/audio_util/MetadataTest.java
new file mode 100644
index 0000000..ed8ab76
--- /dev/null
+++ b/tests/unit/src/com/android/bluetooth/audio_util/MetadataTest.java
@@ -0,0 +1,979 @@
+/*
+ * Copyright 2020 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.audio_util;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import static org.mockito.Mockito.when;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.content.res.AssetFileDescriptor;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.media.MediaDescription;
+import android.media.MediaMetadata;
+import android.media.browse.MediaBrowser.MediaItem;
+import android.media.session.MediaSession.QueueItem;
+import android.net.Uri;
+import android.os.Bundle;
+import android.test.mock.MockContentProvider;
+import android.test.mock.MockContentResolver;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.bluetooth.R;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.io.InputStream;
+
+@RunWith(AndroidJUnit4.class)
+public class MetadataTest {
+    private Context mTargetContext;
+
+    private @Mock Context mMockContext;
+    private @Mock Resources mMockResources;
+    private Resources mTestResources;
+    private MockContentResolver mTestContentResolver;
+
+    private static final String TEST_AUTHORITY = "com.android.bluetooth.avrcp.test";
+    private static final Uri TEST_CONTENT_URI = new Uri.Builder()
+            .scheme(ContentResolver.SCHEME_CONTENT)
+            .authority(TEST_AUTHORITY)
+            .build();
+
+    private static final String IMAGE_HANDLE_1 = "0000001";
+    private static final Uri IMAGE_URI_1 = TEST_CONTENT_URI.buildUpon()
+            .appendQueryParameter("handle", IMAGE_HANDLE_1)
+            .build();
+    private static final String IMAGE_STRING_1 = IMAGE_URI_1.toString();
+
+    private static final String DEFAULT_MEDIA_ID = "Not Provided";
+    private static final String DEFAULT_TITLE = "Not Provided";
+    private static final String DEFAULT_ARTIST = "";
+    private static final String DEFAULT_ALBUM = "";
+    private static final String DEFAULT_TRACK_NUM = "1";
+    private static final String DEFAULT_NUM_TRACKS = "1";
+    private static final String DEFAULT_GENRE = "";
+    private static final String DEFAULT_DURATION = "0";
+    private static final Image DEFAULT_IMAGE = null;
+
+    private static final String SONG_MEDIA_ID = "abc123";
+    private static final String SONG_TITLE = "BT Test Song";
+    private static final String SONG_ARTIST = "BT Test Artist";
+    private static final String SONG_ALBUM = "BT Test Album";
+    private static final String SONG_TRACK_NUM = "12";
+    private static final String SONG_NUM_TRACKS = "15";
+    private static final String SONG_GENRE = "BT Music";
+    private static final String SONG_DURATION = "5000";
+    private Image mSongImage = null; /* to be set to Image(mTestBitmap) once context is set */
+
+    private Bitmap mTestBitmap = null;
+    private Bitmap mTestBitmap2 = null;
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+
+        mTargetContext = InstrumentationRegistry.getTargetContext();
+        try {
+            mTestResources = mTargetContext.getPackageManager()
+                    .getResourcesForApplication("com.android.bluetooth.tests");
+        } catch (PackageManager.NameNotFoundException e) {
+            assertWithMessage("Setup Failure Unable to get resources" + e.toString()).fail();
+        }
+
+        mTestBitmap = loadImage(com.android.bluetooth.tests.R.raw.image_200_200);
+        mTestBitmap2 = loadImage(com.android.bluetooth.tests.R.raw.image_200_200_blue);
+
+        mTestContentResolver = new MockContentResolver(mTargetContext);
+        mTestContentResolver.addProvider(TEST_AUTHORITY, new MockContentProvider() {
+            @Override
+            public AssetFileDescriptor openTypedAssetFile(Uri url, String mimeType, Bundle opts) {
+                String handle = url.getQueryParameter("handle");
+                if (IMAGE_URI_1.equals(url)) {
+                    return mTestResources.openRawResourceFd(
+                            com.android.bluetooth.tests.R.raw.image_200_200);
+                }
+                return null;
+            }
+        });
+
+        when(mMockContext.getContentResolver()).thenReturn(mTestContentResolver);
+        when(mMockResources.getBoolean(R.bool.avrcp_target_cover_art_uri_images)).thenReturn(true);
+        when(mMockContext.getResources()).thenReturn(mMockResources);
+
+        mSongImage = new Image(mMockContext, mTestBitmap);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        mSongImage = null;
+        mTestContentResolver = null;
+        mTestBitmap = null;
+        mTestBitmap2 = null;
+        mTestResources = null;
+        mTargetContext = null;
+        mMockContext = null;
+    }
+
+    private Bitmap loadImage(int resId) {
+        InputStream imageInputStream = mTestResources.openRawResource(resId);
+        return BitmapFactory.decodeStream(imageInputStream);
+    }
+
+    private MediaMetadata getMediaMetadata() {
+        MediaMetadata.Builder builder = new MediaMetadata.Builder()
+                .putString(MediaMetadata.METADATA_KEY_MEDIA_ID, SONG_MEDIA_ID)
+                .putString(MediaMetadata.METADATA_KEY_TITLE, SONG_TITLE)
+                .putString(MediaMetadata.METADATA_KEY_ARTIST, SONG_ARTIST)
+                .putString(MediaMetadata.METADATA_KEY_ALBUM, SONG_ALBUM)
+                .putLong(MediaMetadata.METADATA_KEY_TRACK_NUMBER, Long.parseLong(SONG_TRACK_NUM))
+                .putLong(MediaMetadata.METADATA_KEY_NUM_TRACKS, Long.parseLong(SONG_NUM_TRACKS))
+                .putString(MediaMetadata.METADATA_KEY_GENRE, SONG_GENRE)
+                .putLong(MediaMetadata.METADATA_KEY_DURATION, Long.parseLong(SONG_DURATION));
+        return builder.build();
+    }
+
+    private MediaMetadata getMediaMetadataWithBitmap(String field, Bitmap image) {
+        MediaMetadata.Builder builder = new MediaMetadata.Builder()
+                .putString(MediaMetadata.METADATA_KEY_MEDIA_ID, SONG_MEDIA_ID)
+                .putString(MediaMetadata.METADATA_KEY_TITLE, SONG_TITLE)
+                .putString(MediaMetadata.METADATA_KEY_ARTIST, SONG_ARTIST)
+                .putString(MediaMetadata.METADATA_KEY_ALBUM, SONG_ALBUM)
+                .putLong(MediaMetadata.METADATA_KEY_TRACK_NUMBER, Long.parseLong(SONG_TRACK_NUM))
+                .putLong(MediaMetadata.METADATA_KEY_NUM_TRACKS, Long.parseLong(SONG_NUM_TRACKS))
+                .putString(MediaMetadata.METADATA_KEY_GENRE, SONG_GENRE)
+                .putLong(MediaMetadata.METADATA_KEY_DURATION, Long.parseLong(SONG_DURATION))
+                .putBitmap(field, image);
+        return builder.build();
+    }
+
+    private MediaMetadata getMediaMetadataWithUri(String field, Uri uri) {
+        MediaMetadata.Builder builder = new MediaMetadata.Builder()
+                .putString(MediaMetadata.METADATA_KEY_MEDIA_ID, SONG_MEDIA_ID)
+                .putString(MediaMetadata.METADATA_KEY_TITLE, SONG_TITLE)
+                .putString(MediaMetadata.METADATA_KEY_ARTIST, SONG_ARTIST)
+                .putString(MediaMetadata.METADATA_KEY_ALBUM, SONG_ALBUM)
+                .putLong(MediaMetadata.METADATA_KEY_TRACK_NUMBER, Long.parseLong(SONG_TRACK_NUM))
+                .putLong(MediaMetadata.METADATA_KEY_NUM_TRACKS, Long.parseLong(SONG_NUM_TRACKS))
+                .putString(MediaMetadata.METADATA_KEY_GENRE, SONG_GENRE)
+                .putLong(MediaMetadata.METADATA_KEY_DURATION, Long.parseLong(SONG_DURATION))
+                .putString(field, uri.toString());
+        return builder.build();
+    }
+
+    private MediaDescription getMediaDescription(Bitmap bitmap, Uri uri, Bundle extras) {
+        MediaDescription.Builder builder = new MediaDescription.Builder()
+                .setMediaId(SONG_MEDIA_ID)
+                .setTitle(SONG_TITLE)
+                .setSubtitle(SONG_ARTIST)
+                .setDescription(SONG_ALBUM);
+        if (bitmap != null) {
+            builder.setIconBitmap(bitmap);
+        }
+        if (uri != null) {
+            builder.setIconUri(uri);
+        }
+        if (extras != null) {
+            builder.setExtras(extras);
+        }
+        return builder.build();
+    }
+
+    private MediaItem getMediaItem(MediaDescription description) {
+        return new MediaItem(description, 0 /* not browsable/playable */);
+    }
+
+    private QueueItem getQueueItem(MediaDescription description) {
+        return new QueueItem(description, 1 /* queue ID */);
+    }
+
+    private Bundle getBundle() {
+        Bundle bundle = new Bundle();
+        bundle.putString(MediaMetadata.METADATA_KEY_MEDIA_ID, SONG_MEDIA_ID);
+        bundle.putString(MediaMetadata.METADATA_KEY_TITLE, SONG_TITLE);
+        bundle.putString(MediaMetadata.METADATA_KEY_ARTIST, SONG_ARTIST);
+        bundle.putString(MediaMetadata.METADATA_KEY_ALBUM, SONG_ALBUM);
+        bundle.putLong(MediaMetadata.METADATA_KEY_TRACK_NUMBER, Long.parseLong(SONG_TRACK_NUM));
+        bundle.putLong(MediaMetadata.METADATA_KEY_NUM_TRACKS, Long.parseLong(SONG_NUM_TRACKS));
+        bundle.putString(MediaMetadata.METADATA_KEY_GENRE, SONG_GENRE);
+        bundle.putLong(MediaMetadata.METADATA_KEY_DURATION, Long.parseLong(SONG_DURATION));
+        return bundle;
+    }
+
+    private Bundle getBundleWithBitmap(String field, Bitmap image) {
+        Bundle bundle = getBundle();
+        bundle.putParcelable(field, image);
+        return bundle;
+    }
+
+    private Bundle getBundleWithUri(String field, Uri uri) {
+        Bundle bundle = getBundle();
+        bundle.putString(field, uri.toString());
+        return bundle;
+    }
+
+    private void assertMetadata(String mediaId, String title, String artist, String album,
+            String trackNum, String numTracks, String genre, String duration,
+            Image image, Metadata metadata) {
+        assertThat(metadata.mediaId).isEqualTo(mediaId);
+        assertThat(metadata.title).isEqualTo(title);
+        assertThat(metadata.artist).isEqualTo(artist);
+        assertThat(metadata.album).isEqualTo(album);
+        assertThat(metadata.trackNum).isEqualTo(trackNum);
+        assertThat(metadata.numTracks).isEqualTo(numTracks);
+        assertThat(metadata.genre).isEqualTo(genre);
+        assertThat(metadata.duration).isEqualTo(duration);
+        assertThat(metadata.image).isEqualTo(image);
+    }
+
+    /**
+     * Make sure the media ID we set is transferred to Metadata object we build
+     */
+    @Test
+    public void testBuildMetadataSetMediaId() {
+        Metadata metadata = new Metadata.Builder().setMediaId(SONG_MEDIA_ID).build();
+        assertMetadata(SONG_MEDIA_ID, null, null, null, null, null, null, null, null, metadata);
+    }
+
+    /**
+     * Make sure you can build a Metadata from a MediaMetadata that has no art
+     */
+    @Test
+    public void testBuildMetadataFromMediaMetadata() {
+        MediaMetadata m = getMediaMetadata();
+        Metadata metadata = new Metadata.Builder().fromMediaMetadata(m).build();
+        assertMetadata(SONG_MEDIA_ID, SONG_TITLE, SONG_ARTIST, SONG_ALBUM, SONG_TRACK_NUM,
+                SONG_NUM_TRACKS, SONG_GENRE, SONG_DURATION, null, metadata);
+    }
+
+    /**
+     * Make sure you can build a Metadata from a MediaMetadata that has bitmap art
+     */
+    @Test
+    public void testBuildMetadataFromMediaMetadataWithArt() {
+        MediaMetadata m =
+                getMediaMetadataWithBitmap(MediaMetadata.METADATA_KEY_ART, mTestBitmap);
+        Metadata metadata = new Metadata.Builder().fromMediaMetadata(m).build();
+        assertMetadata(SONG_MEDIA_ID, SONG_TITLE, SONG_ARTIST, SONG_ALBUM, SONG_TRACK_NUM,
+                SONG_NUM_TRACKS, SONG_GENRE, SONG_DURATION, mSongImage, metadata);
+    }
+
+    /**
+     * Make sure you can build a Metadata from a MediaMetadata that has bitmap album art
+     */
+    @Test
+    public void testBuildMetadataFromMediaMetadataWithAlbumArt() {
+        MediaMetadata m =
+                getMediaMetadataWithBitmap(MediaMetadata.METADATA_KEY_ALBUM_ART, mTestBitmap);
+        Metadata metadata = new Metadata.Builder().fromMediaMetadata(m).build();
+        assertMetadata(SONG_MEDIA_ID, SONG_TITLE, SONG_ARTIST, SONG_ALBUM, SONG_TRACK_NUM,
+                SONG_NUM_TRACKS, SONG_GENRE, SONG_DURATION, mSongImage, metadata);
+    }
+
+    /**
+     * Make sure you can build a Metadata from a MediaMetadata that a display icon
+     */
+    @Test
+    public void testBuildMetadataFromMediaMetadataWithDisplayIcon() {
+        MediaMetadata m =
+                getMediaMetadataWithBitmap(MediaMetadata.METADATA_KEY_DISPLAY_ICON, mTestBitmap);
+        Metadata metadata = new Metadata.Builder().fromMediaMetadata(m).build();
+        assertMetadata(SONG_MEDIA_ID, SONG_TITLE, SONG_ARTIST, SONG_ALBUM, SONG_TRACK_NUM,
+                SONG_NUM_TRACKS, SONG_GENRE, SONG_DURATION, mSongImage, metadata);
+    }
+
+    /**
+     * Make sure you can build a Metadata from a MediaMetadata that has Uri based art
+     */
+    @Test
+    public void testBuildMetadataFromMediaMetadataWithUriArt() {
+        MediaMetadata m =
+                getMediaMetadataWithUri(MediaMetadata.METADATA_KEY_ART_URI, IMAGE_URI_1);
+        Metadata metadata = new Metadata.Builder()
+                .useContext(mMockContext)
+                .fromMediaMetadata(m)
+                .build();
+        assertMetadata(SONG_MEDIA_ID, SONG_TITLE, SONG_ARTIST, SONG_ALBUM, SONG_TRACK_NUM,
+                SONG_NUM_TRACKS, SONG_GENRE, SONG_DURATION, mSongImage, metadata);
+    }
+
+     /**
+     * Make sure you can build a Metadata from a MediaMetadata that has Uri based album art
+     */
+    @Test
+    public void testBuildMetadataFromMediaMetadataWithUriAlbumArt() {
+        MediaMetadata m =
+                getMediaMetadataWithUri(MediaMetadata.METADATA_KEY_ALBUM_ART_URI, IMAGE_URI_1);
+        Metadata metadata = new Metadata.Builder()
+                .useContext(mMockContext)
+                .fromMediaMetadata(m)
+                .build();
+        assertMetadata(SONG_MEDIA_ID, SONG_TITLE, SONG_ARTIST, SONG_ALBUM, SONG_TRACK_NUM,
+                SONG_NUM_TRACKS, SONG_GENRE, SONG_DURATION, mSongImage, metadata);
+    }
+
+     /**
+     * Make sure you can build a Metadata from a MediaMetadata that has a Uri based display icon
+     */
+    @Test
+    public void testBuildMetadataFromMediaMetadataWithUriDisplayIcon() {
+        MediaMetadata m =
+                getMediaMetadataWithUri(MediaMetadata.METADATA_KEY_DISPLAY_ICON_URI, IMAGE_URI_1);
+        Metadata metadata = new Metadata.Builder()
+                .useContext(mMockContext)
+                .fromMediaMetadata(m)
+                .build();
+        assertMetadata(SONG_MEDIA_ID, SONG_TITLE, SONG_ARTIST, SONG_ALBUM, SONG_TRACK_NUM,
+                SONG_NUM_TRACKS, SONG_GENRE, SONG_DURATION, mSongImage, metadata);
+    }
+
+    /**
+     * Make sure we're robust to build attempts that include Uri based art without a context
+     */
+    @Test
+    public void testBuildMetadataFromMediaMetadataWithUriNoContext() {
+        MediaMetadata m =
+                getMediaMetadataWithUri(MediaMetadata.METADATA_KEY_ART_URI, IMAGE_URI_1);
+        Metadata metadata = new Metadata.Builder()
+                .fromMediaMetadata(m)
+                .build();
+        assertMetadata(SONG_MEDIA_ID, SONG_TITLE, SONG_ARTIST, SONG_ALBUM, SONG_TRACK_NUM,
+                SONG_NUM_TRACKS, SONG_GENRE, SONG_DURATION, null, metadata);
+    }
+
+    /**
+     * Make sure building with a MediaMetadata that contains URI art when URI art is disabled
+     * yields no cover art.
+     */
+    @Test
+    public void testBuildMetadataFromMediaMetadataWithUriAndUrisDisabled() {
+        when(mMockResources.getBoolean(
+                R.bool.avrcp_target_cover_art_uri_images)).thenReturn(false);
+        MediaMetadata m =
+                getMediaMetadataWithUri(MediaMetadata.METADATA_KEY_ART_URI, IMAGE_URI_1);
+        Metadata metadata = new Metadata.Builder()
+                .useContext(mMockContext)
+                .fromMediaMetadata(m)
+                .build();
+        assertMetadata(SONG_MEDIA_ID, SONG_TITLE, SONG_ARTIST, SONG_ALBUM, SONG_TRACK_NUM,
+                SONG_NUM_TRACKS, SONG_GENRE, SONG_DURATION, null, metadata);
+    }
+
+    /**
+     * Make sure we're robust to building with a null MediaMetadata
+     */
+    @Test
+    public void testBuildMetadataFromNullMediaMetadata() {
+        Metadata metadata = new Metadata.Builder().fromMediaMetadata(null).build();
+        assertMetadata(null, null, null, null, null, null, null, null, null, metadata);
+    }
+
+    /**
+     * Make sure you can create a Metadata from a simple MediaDescription
+     */
+    @Test
+    public void testBuildMetadataFromMediaDescription() {
+        MediaDescription description = getMediaDescription(null, null, null);
+        Metadata metadata = new Metadata.Builder().fromMediaDescription(description).build();
+        assertMetadata(SONG_MEDIA_ID, SONG_TITLE, SONG_ARTIST, SONG_ALBUM, null, null, null, null,
+                null, metadata);
+    }
+
+    /**
+     * Make sure you can create a Metadata from a MediaDescription that contains cover artwork as a
+     * Bitmap Image
+     */
+    @Test
+    public void testBuildMetadataFromMediaDescriptionWithIconArt() {
+        MediaDescription description = getMediaDescription(mTestBitmap, null, null);
+        Metadata metadata = new Metadata.Builder().fromMediaDescription(description).build();
+        assertMetadata(SONG_MEDIA_ID, SONG_TITLE, SONG_ARTIST, SONG_ALBUM, null, null, null, null,
+                mSongImage, metadata);
+    }
+
+    /**
+     * Make sure you can create a Metadata from a MediaDescription that contains cover artwork as
+     * an icon Uri
+     */
+    @Test
+    public void testBuildMetadataFromMediaDescriptionWithIconUri() {
+        MediaDescription description = getMediaDescription(null, IMAGE_URI_1, null);
+        Metadata metadata = new Metadata.Builder()
+                .useContext(mMockContext)
+                .fromMediaDescription(description)
+                .build();
+        assertMetadata(SONG_MEDIA_ID, SONG_TITLE, SONG_ARTIST, SONG_ALBUM, null, null, null, null,
+                mSongImage, metadata);
+    }
+
+    /**
+     * Make sure we're robust to attempts to create Uri based images without a context set
+     */
+    @Test
+    public void testBuildMetadataFromMediaDescriptionWithArtNullContext() {
+        MediaDescription description = getMediaDescription(null, IMAGE_URI_1, null);
+        Metadata metadata = new Metadata.Builder().fromMediaDescription(description).build();
+        assertMetadata(SONG_MEDIA_ID, SONG_TITLE, SONG_ARTIST, SONG_ALBUM, null, null, null, null,
+                null, metadata);
+    }
+
+    /**
+     * Make sure you can create a Metadata object from a MediaDesciption with a bundle of extras
+     * that provide more detailed information about the item.
+     */
+    @Test
+    public void testBuildMetadataFromMediaDescriptionWithExtras() {
+        Bundle extras = getBundleWithBitmap(MediaMetadata.METADATA_KEY_ART, mTestBitmap);
+        MediaDescription description = getMediaDescription(null, null, extras);
+        Metadata metadata = new Metadata.Builder().fromMediaDescription(description).build();
+        assertMetadata(SONG_MEDIA_ID, SONG_TITLE, SONG_ARTIST, SONG_ALBUM, SONG_TRACK_NUM,
+                SONG_NUM_TRACKS, SONG_GENRE, SONG_DURATION, mSongImage, metadata);
+    }
+
+    /**
+     * Make sure we're robust to null MediaDescriptions
+     */
+    @Test
+    public void testBuildMetadataFromNullMediaDescription() {
+        Metadata metadata = new Metadata.Builder().fromMediaDescription(null).build();
+        assertMetadata(null, null, null, null, null, null, null, null, null, metadata);
+    }
+
+    /**
+     * Make sure you can create a Metadata from a simple bundle of MediaMetadata based key-values
+     */
+    @Test
+    public void testBuildMetadataFromBundle() {
+        Bundle bundle = getBundle();
+        Metadata metadata = new Metadata.Builder().fromBundle(bundle).build();
+        assertMetadata(SONG_MEDIA_ID, SONG_TITLE, SONG_ARTIST, SONG_ALBUM, SONG_TRACK_NUM,
+                SONG_NUM_TRACKS, SONG_GENRE, SONG_DURATION, null, metadata);
+    }
+
+    /**
+     * Make sure you can create a Metadata from a simple bundle of MediaMetadata based key-values
+     * where the bundle contains bitmap art
+     */
+    @Test
+    public void testBuildMetadataFromBundleWithArt() {
+        Bundle bundle = getBundleWithBitmap(MediaMetadata.METADATA_KEY_ART, mTestBitmap);
+        Metadata metadata = new Metadata.Builder().fromBundle(bundle).build();
+        assertMetadata(SONG_MEDIA_ID, SONG_TITLE, SONG_ARTIST, SONG_ALBUM, SONG_TRACK_NUM,
+                SONG_NUM_TRACKS, SONG_GENRE, SONG_DURATION, mSongImage, metadata);
+    }
+
+    /**
+     * Make sure you can create a Metadata from a simple bundle of MediaMetadata based key-values
+     * where the bundle contains bitmap album art
+     */
+    @Test
+    public void testBuildMetadataFromBundleWithAlbumArt() {
+        Bundle bundle = getBundleWithBitmap(MediaMetadata.METADATA_KEY_ALBUM_ART, mTestBitmap);
+        Metadata metadata = new Metadata.Builder().fromBundle(bundle).build();
+        assertMetadata(SONG_MEDIA_ID, SONG_TITLE, SONG_ARTIST, SONG_ALBUM, SONG_TRACK_NUM,
+                SONG_NUM_TRACKS, SONG_GENRE, SONG_DURATION, mSongImage, metadata);
+    }
+
+    /**
+     * Make sure you can create a Metadata from a simple bundle of MediaMetadata based key-values
+     * where the bundle contains a bitmap display icon
+     */
+    @Test
+    public void testBuildMetadataFromBundleWithDisplayIcon() {
+        Bundle bundle = getBundleWithBitmap(MediaMetadata.METADATA_KEY_DISPLAY_ICON, mTestBitmap);
+        Metadata metadata = new Metadata.Builder().fromBundle(bundle).build();
+        assertMetadata(SONG_MEDIA_ID, SONG_TITLE, SONG_ARTIST, SONG_ALBUM, SONG_TRACK_NUM,
+                SONG_NUM_TRACKS, SONG_GENRE, SONG_DURATION, mSongImage, metadata);
+    }
+
+    /**
+     * Make sure you can create a Metadata from a simple bundle of MediaMetadata based key-values
+     * where the bundle contains uri art
+     */
+    @Test
+    public void testBuildMetadataFromBundleWithUriArt() {
+        Bundle bundle = getBundleWithUri(MediaMetadata.METADATA_KEY_ART_URI, IMAGE_URI_1);
+        Metadata metadata = new Metadata.Builder()
+                .useContext(mMockContext)
+                .fromBundle(bundle)
+                .build();
+        assertMetadata(SONG_MEDIA_ID, SONG_TITLE, SONG_ARTIST, SONG_ALBUM, SONG_TRACK_NUM,
+                SONG_NUM_TRACKS, SONG_GENRE, SONG_DURATION, mSongImage, metadata);
+    }
+
+    /**
+     * Make sure you can create a Metadata from a simple bundle of MediaMetadata based key-values
+     * where the bundle contains uri album art
+     */
+    @Test
+    public void testBuildMetadataFromBundleWithUriAlbumArt() {
+        Bundle bundle = getBundleWithUri(MediaMetadata.METADATA_KEY_ALBUM_ART_URI, IMAGE_URI_1);
+        Metadata metadata = new Metadata.Builder()
+                .useContext(mMockContext)
+                .fromBundle(bundle)
+                .build();
+        assertMetadata(SONG_MEDIA_ID, SONG_TITLE, SONG_ARTIST, SONG_ALBUM, SONG_TRACK_NUM,
+                SONG_NUM_TRACKS, SONG_GENRE, SONG_DURATION, mSongImage, metadata);
+    }
+
+    /**
+     * Make sure you can create a Metadata from a simple bundle of MediaMetadata based key-values
+     * where the bundle contains a uri display icon
+     */
+    @Test
+    public void testBuildMetadataFromBundleWithUriDisplayIcon() {
+        Bundle bundle = getBundleWithUri(MediaMetadata.METADATA_KEY_DISPLAY_ICON_URI, IMAGE_URI_1);
+        Metadata metadata = new Metadata.Builder()
+                .useContext(mMockContext)
+                .fromBundle(bundle)
+                .build();
+        assertMetadata(SONG_MEDIA_ID, SONG_TITLE, SONG_ARTIST, SONG_ALBUM, SONG_TRACK_NUM,
+                SONG_NUM_TRACKS, SONG_GENRE, SONG_DURATION, mSongImage, metadata);
+    }
+
+    /**
+     * Make sure we're robust to building with a Bundle that contains URI art and no context object
+     */
+    @Test
+    public void testBuildMetadataFromBundleWithUriNoContext() {
+        Bundle bundle = getBundleWithUri(MediaMetadata.METADATA_KEY_DISPLAY_ICON_URI, IMAGE_URI_1);
+        Metadata metadata = new Metadata.Builder()
+                .fromBundle(bundle)
+                .build();
+        assertMetadata(SONG_MEDIA_ID, SONG_TITLE, SONG_ARTIST, SONG_ALBUM, SONG_TRACK_NUM,
+                SONG_NUM_TRACKS, SONG_GENRE, SONG_DURATION, null, metadata);
+    }
+
+    /**
+     * Make sure building with a Bundle that contains URI art when URI art is disabled yields no
+     * cover art.
+     */
+    @Test
+    public void testBuildMetadataFromBundleWithUriAndUrisDisabled() {
+        when(mMockResources.getBoolean(
+                R.bool.avrcp_target_cover_art_uri_images)).thenReturn(false);
+        Bundle bundle = getBundleWithUri(MediaMetadata.METADATA_KEY_DISPLAY_ICON_URI, IMAGE_URI_1);
+        Metadata metadata = new Metadata.Builder()
+                .useContext(mMockContext)
+                .fromBundle(bundle)
+                .build();
+        assertMetadata(SONG_MEDIA_ID, SONG_TITLE, SONG_ARTIST, SONG_ALBUM, SONG_TRACK_NUM,
+                SONG_NUM_TRACKS, SONG_GENRE, SONG_DURATION, null, metadata);
+    }
+
+    /**
+     * Make sure we're robust to empty Bundles
+     */
+    @Test
+    public void testBuildMetadataFromEmptyBundle() {
+        Bundle bundle = new Bundle();
+        Metadata metadata = new Metadata.Builder().fromBundle(bundle).build();
+        assertMetadata(null, null, null, null, null, null, null, null, null, metadata);
+    }
+
+    /**
+     * Make sure we're robust to null Bundles
+     */
+    @Test
+    public void testBuildMetadataFromNullBundle() {
+        Metadata metadata = new Metadata.Builder().fromBundle(null).build();
+        assertMetadata(null, null, null, null, null, null, null, null, null, metadata);
+    }
+
+    /**
+     * Make a Metadata with a simple MediaItem
+     */
+    @Test
+    public void testBuildMetadataFromMediaItem() {
+        MediaDescription description = getMediaDescription(null, null, null);
+        MediaItem item = getMediaItem(description);
+        Metadata metadata = new Metadata.Builder().fromMediaItem(item).build();
+        assertMetadata(SONG_MEDIA_ID, SONG_TITLE, SONG_ARTIST, SONG_ALBUM, null, null, null, null,
+                null, metadata);
+    }
+
+    /**
+     * Make a Metadata with a MediaItem that has icon art
+     */
+    @Test
+    public void testBuildMetadataFromMediaItemWithIconArt() {
+        MediaDescription description = getMediaDescription(mTestBitmap, null, null);
+        MediaItem item = getMediaItem(description);
+        Metadata metadata = new Metadata.Builder().fromMediaItem(item).build();
+        assertMetadata(SONG_MEDIA_ID, SONG_TITLE, SONG_ARTIST, SONG_ALBUM, null, null, null, null,
+                mSongImage, metadata);
+    }
+
+    /**
+     * Make a Metadata with a MediaItem that has an icon uri
+     */
+    @Test
+    public void testBuildMetadataFromMediaItemWithIconUri() {
+        MediaDescription description = getMediaDescription(null, IMAGE_URI_1, null);
+        MediaItem item = getMediaItem(description);
+        Metadata metadata = new Metadata.Builder()
+                .useContext(mMockContext)
+                .fromMediaItem(item)
+                .build();
+        assertMetadata(SONG_MEDIA_ID, SONG_TITLE, SONG_ARTIST, SONG_ALBUM, null, null, null, null,
+                mSongImage, metadata);
+    }
+
+    /**
+     * Make a Metadata with a MediaItem that has an icon uri, but don't use a context
+     */
+    @Test
+    public void testBuildMetadataFromMediaItemWithIconUriNoContext() {
+        MediaDescription description = getMediaDescription(null, IMAGE_URI_1, null);
+        MediaItem item = getMediaItem(description);
+        Metadata metadata = new Metadata.Builder().fromMediaItem(item).build();
+        assertMetadata(SONG_MEDIA_ID, SONG_TITLE, SONG_ARTIST, SONG_ALBUM, null, null, null, null,
+                null, metadata);
+    }
+
+    /**
+     * Make sure building with a MediaItem that contains URI art when URI art is disabled yields no
+     * cover art.
+     */
+    @Test
+    public void testBuildMetadataFromMediaItemWithIconUriAndUrisDisabled() {
+        when(mMockResources.getBoolean(
+                R.bool.avrcp_target_cover_art_uri_images)).thenReturn(false);
+        MediaDescription description = getMediaDescription(null, IMAGE_URI_1, null);
+        MediaItem item = getMediaItem(description);
+        Metadata metadata = new Metadata.Builder()
+                .useContext(mMockContext)
+                .fromMediaItem(item)
+                .build();
+        assertMetadata(SONG_MEDIA_ID, SONG_TITLE, SONG_ARTIST, SONG_ALBUM, null, null, null, null,
+                null, metadata);
+    }
+
+    /**
+     * Make a Metadata with a MediaItem that has extras
+     */
+    @Test
+    public void testBuildMetadataFromMediaItemWithExtras() {
+        Bundle extras = getBundleWithBitmap(MediaMetadata.METADATA_KEY_ART, mTestBitmap);
+        MediaDescription description = getMediaDescription(null, null, extras);
+        MediaItem item = getMediaItem(description);
+        Metadata metadata = new Metadata.Builder().fromMediaItem(item).build();
+        assertMetadata(SONG_MEDIA_ID, SONG_TITLE, SONG_ARTIST, SONG_ALBUM, SONG_TRACK_NUM,
+                SONG_NUM_TRACKS, SONG_GENRE, SONG_DURATION, mSongImage, metadata);
+    }
+
+    /**
+     * Make a Metadata with a null MediaItem
+     */
+    @Test
+    public void testBuildMetadataFromNullMediaItem() {
+        Metadata metadata = new Metadata.Builder().fromMediaItem(null).build();
+        assertMetadata(null, null, null, null, null, null, null, null, null, metadata);
+    }
+
+    /**
+     * Make a Metadata from a simple QueueItem
+     */
+    @Test
+    public void testBuildMetadataFromQueueItem() {
+        MediaDescription description = getMediaDescription(null, null, null);
+        QueueItem queueItem = getQueueItem(description);
+        Metadata metadata = new Metadata.Builder().fromQueueItem(queueItem).build();
+        assertMetadata(SONG_MEDIA_ID, SONG_TITLE, SONG_ARTIST, SONG_ALBUM, null, null, null, null,
+                null, metadata);
+    }
+
+    /**
+     * Make a Metadata from a QueueItem with icon art
+     */
+    @Test
+    public void testBuildMetadataFromQueueItemWithIconArt() {
+        MediaDescription description = getMediaDescription(mTestBitmap, null, null);
+        QueueItem queueItem = getQueueItem(description);
+        Metadata metadata = new Metadata.Builder().fromQueueItem(queueItem).build();
+        assertMetadata(SONG_MEDIA_ID, SONG_TITLE, SONG_ARTIST, SONG_ALBUM, null, null, null, null,
+                mSongImage, metadata);
+    }
+
+    /**
+     * Make a Metadata from a QueueItem with an icon uri
+     */
+    @Test
+    public void testBuildMetadataFromQueueItemWithIconUri() {
+        MediaDescription description = getMediaDescription(null, IMAGE_URI_1, null);
+        QueueItem queueItem = getQueueItem(description);
+        Metadata metadata = new Metadata.Builder()
+                .useContext(mMockContext)
+                .fromQueueItem(queueItem)
+                .build();
+        assertMetadata(SONG_MEDIA_ID, SONG_TITLE, SONG_ARTIST, SONG_ALBUM, null, null, null, null,
+                mSongImage, metadata);
+    }
+
+    /**
+     * Make a Metadata from a QueueItem with an icon uri, but don't use a context
+     */
+    @Test
+    public void testBuildMetadataFromQueueItemWithIconUriNoContext() {
+        MediaDescription description = getMediaDescription(null, IMAGE_URI_1, null);
+        QueueItem queueItem = getQueueItem(description);
+        Metadata metadata = new Metadata.Builder().fromQueueItem(queueItem).build();
+        assertMetadata(SONG_MEDIA_ID, SONG_TITLE, SONG_ARTIST, SONG_ALBUM, null, null, null, null,
+                null, metadata);
+    }
+
+    /**
+     * Make sure building with a QueueItem that contains URI art when URI art is disabled yields
+     * no cover art.
+     */
+    @Test
+    public void testBuildMetadataFromQueueItemWithIconUriandUrisDisabled() {
+        when(mMockResources.getBoolean(
+                R.bool.avrcp_target_cover_art_uri_images)).thenReturn(false);
+        MediaDescription description = getMediaDescription(null, IMAGE_URI_1, null);
+        QueueItem queueItem = getQueueItem(description);
+        Metadata metadata = new Metadata.Builder()
+                .useContext(mMockContext)
+                .fromQueueItem(queueItem)
+                .build();
+        assertMetadata(SONG_MEDIA_ID, SONG_TITLE, SONG_ARTIST, SONG_ALBUM, null, null, null, null,
+                null, metadata);
+    }
+
+    /**
+     * Make a Metadata from a QueueItem with extras
+     */
+    @Test
+    public void testBuildMetadataFromQueueItemWithExtras() {
+        Bundle extras = getBundleWithBitmap(MediaMetadata.METADATA_KEY_ART, mTestBitmap);
+        MediaDescription description = getMediaDescription(null, null, extras);
+        QueueItem queueItem = getQueueItem(description);
+        Metadata metadata = new Metadata.Builder().fromQueueItem(queueItem).build();
+        assertMetadata(SONG_MEDIA_ID, SONG_TITLE, SONG_ARTIST, SONG_ALBUM, SONG_TRACK_NUM,
+                SONG_NUM_TRACKS, SONG_GENRE, SONG_DURATION, mSongImage, metadata);
+    }
+
+    /**
+     * Make a Metadata with a null QueueItem
+     */
+    @Test
+    public void testBuildMetadataFromNullQueueItem() {
+        Metadata metadata = new Metadata.Builder().fromQueueItem(null).build();
+        assertMetadata(null, null, null, null, null, null, null, null, null, metadata);
+    }
+
+    /**
+     * Build a Metadata using the defaults and nothing else. Ensure it sets all the default values
+     */
+    @Test
+    public void testBuildMetadataUseDefaults() {
+        Metadata metadata = new Metadata.Builder().useDefaults().build();
+        assertMetadata(DEFAULT_MEDIA_ID, DEFAULT_TITLE, DEFAULT_ARTIST, DEFAULT_ALBUM,
+                DEFAULT_TRACK_NUM, DEFAULT_NUM_TRACKS, DEFAULT_GENRE, DEFAULT_DURATION,
+                DEFAULT_IMAGE, metadata);
+    }
+
+    /**
+     * Build a Metadata using the defaults and an object with partial fields. Ensure the resulting
+     * Metadata has the proper mix of defaults and extracted fields.
+     */
+    @Test
+    public void testBuildMetadataUseDefaultsAndPartialFields() {
+        MediaDescription description = getMediaDescription(null, null, null);
+        Metadata metadata = new Metadata.Builder()
+                .useDefaults()
+                .fromMediaDescription(description)
+                .build();
+        assertMetadata(SONG_MEDIA_ID, SONG_TITLE, SONG_ARTIST, SONG_ALBUM,
+                DEFAULT_TRACK_NUM, DEFAULT_NUM_TRACKS, DEFAULT_GENRE, DEFAULT_DURATION,
+                DEFAULT_IMAGE, metadata);
+    }
+
+    /**
+     * Build a Metadata using the defaults and an object with all fields. Ensure the resulting
+     * Metadata has the proper no defaults left over.
+     */
+    @Test
+    public void testBuildMetadataUseDefaultsAndAllFields() {
+        MediaMetadata m =
+                getMediaMetadataWithBitmap(MediaMetadata.METADATA_KEY_ART, mTestBitmap);
+        Metadata metadata = new Metadata.Builder().useDefaults().fromMediaMetadata(m).build();
+        assertMetadata(SONG_MEDIA_ID, SONG_TITLE, SONG_ARTIST, SONG_ALBUM, SONG_TRACK_NUM,
+                SONG_NUM_TRACKS, SONG_GENRE, SONG_DURATION, mSongImage, metadata);
+    }
+
+    /**
+     * Build a Metadata using the defaults and an object with missing fields. Ensure the resulting
+     * Metadata has the proper mix of defaults and extracted fields.
+     */
+    @Test
+    public void testBuildMetadataUseDefaultsAndMissingFields() {
+        Bundle bundle = new Bundle();
+        bundle.putString(MediaMetadata.METADATA_KEY_TITLE, SONG_TITLE);
+        bundle.putLong(MediaMetadata.METADATA_KEY_NUM_TRACKS, Long.parseLong(SONG_NUM_TRACKS));
+        bundle.putString(MediaMetadata.METADATA_KEY_GENRE, SONG_GENRE);
+        bundle.putParcelable(MediaMetadata.METADATA_KEY_ART, mTestBitmap);
+        Metadata metadata = new Metadata.Builder().useDefaults().fromBundle(bundle).build();
+        assertMetadata(DEFAULT_MEDIA_ID, SONG_TITLE, DEFAULT_ARTIST, DEFAULT_ALBUM,
+                DEFAULT_TRACK_NUM, SONG_NUM_TRACKS, SONG_GENRE, DEFAULT_DURATION, mSongImage,
+                metadata);
+    }
+
+    /**
+     * Build a Metadata using the defaults. Call useDefaults at the end after other fields have
+     * been extracted from the media framework objects. Ensure we don't overwrite existing values
+     * with defaults. Ensure that the metadata created is the same (by field) as the one where
+     * useDefaults is called first.
+     */
+    @Test
+    public void testBuildMetadataUseDefaultsOrderDoesntMatter() {
+        Bundle bundle = new Bundle();
+        bundle.putString(MediaMetadata.METADATA_KEY_TITLE, SONG_TITLE);
+        bundle.putLong(MediaMetadata.METADATA_KEY_NUM_TRACKS, Long.parseLong(SONG_NUM_TRACKS));
+        bundle.putString(MediaMetadata.METADATA_KEY_GENRE, SONG_GENRE);
+        bundle.putParcelable(MediaMetadata.METADATA_KEY_ART, mTestBitmap);
+        Metadata metadata = new Metadata.Builder().fromBundle(bundle).useDefaults().build();
+        Metadata metadata2 = new Metadata.Builder().useDefaults().fromBundle(bundle).build();
+        assertMetadata(DEFAULT_MEDIA_ID, SONG_TITLE, DEFAULT_ARTIST, DEFAULT_ALBUM,
+                DEFAULT_TRACK_NUM, SONG_NUM_TRACKS, SONG_GENRE, DEFAULT_DURATION, mSongImage,
+                metadata);
+        assertMetadata(DEFAULT_MEDIA_ID, SONG_TITLE, DEFAULT_ARTIST, DEFAULT_ALBUM,
+                DEFAULT_TRACK_NUM, SONG_NUM_TRACKS, SONG_GENRE, DEFAULT_DURATION, mSongImage,
+                metadata2);
+    }
+
+    /**
+     * Make sure you can clone a Metadata
+     */
+    @Test
+    public void testClone() {
+        MediaMetadata m =
+                getMediaMetadataWithBitmap(MediaMetadata.METADATA_KEY_ART, mTestBitmap);
+        Metadata metadata = new Metadata.Builder().fromMediaMetadata(m).build();
+        Metadata metadata2 = metadata.clone();
+        assertThat(metadata.mediaId).isEqualTo(metadata2.mediaId);
+        assertThat(metadata.title).isEqualTo(metadata2.title);
+        assertThat(metadata.artist).isEqualTo(metadata2.artist);
+        assertThat(metadata.album).isEqualTo(metadata2.album);
+        assertThat(metadata.trackNum).isEqualTo(metadata2.trackNum);
+        assertThat(metadata.numTracks).isEqualTo(metadata2.numTracks);
+        assertThat(metadata.genre).isEqualTo(metadata2.genre);
+        assertThat(metadata.duration).isEqualTo(metadata2.duration);
+        assertThat(metadata.image).isEqualTo(metadata2.image);
+        assertThat(metadata).isEqualTo(metadata2);
+    }
+
+    /**
+     * Make sure two Metadata objects are different if title doesn't match
+     */
+    @Test
+    public void testEqualsDifferentTitle() {
+        MediaMetadata m =
+                getMediaMetadataWithBitmap(MediaMetadata.METADATA_KEY_ART, mTestBitmap);
+        Metadata metadata = new Metadata.Builder().fromMediaMetadata(m).build();
+        Metadata metadata2 = metadata.clone();
+        metadata2.title = null;
+        assertThat(metadata).isNotEqualTo(metadata2);
+    }
+
+    /**
+     * Make sure two Metadata objects are different if artist doesn't match
+     */
+    @Test
+    public void testEqualsDifferentArtist() {
+        MediaMetadata m =
+                getMediaMetadataWithBitmap(MediaMetadata.METADATA_KEY_ART, mTestBitmap);
+        Metadata metadata = new Metadata.Builder().fromMediaMetadata(m).build();
+        Metadata metadata2 = metadata.clone();
+        metadata2.artist = DEFAULT_ARTIST;
+        assertThat(metadata).isNotEqualTo(metadata2);
+    }
+
+    /**
+     * Make sure two Metadata objects are different if album doesn't match
+     */
+    @Test
+    public void testEqualsDifferentAlbum() {
+        MediaMetadata m =
+                getMediaMetadataWithBitmap(MediaMetadata.METADATA_KEY_ART, mTestBitmap);
+        Metadata metadata = new Metadata.Builder().fromMediaMetadata(m).build();
+        Metadata metadata2 = metadata.clone();
+        metadata2.album = DEFAULT_ALBUM;
+        assertThat(metadata).isNotEqualTo(metadata2);
+    }
+
+    /**
+     * Make sure two Metadata objects are different if trackNum doesn't match
+     */
+    @Test
+    public void testEqualsDifferentTrackNum() {
+        MediaMetadata m =
+                getMediaMetadataWithBitmap(MediaMetadata.METADATA_KEY_ART, mTestBitmap);
+        Metadata metadata = new Metadata.Builder().fromMediaMetadata(m).build();
+        Metadata metadata2 = metadata.clone();
+        metadata2.trackNum = DEFAULT_TRACK_NUM;
+        assertThat(metadata).isNotEqualTo(metadata2);
+    }
+
+    /**
+     * Make sure two Metadata objects are different if numTracks doesn't match
+     */
+    @Test
+    public void testEqualsDifferentNumTracks() {
+        MediaMetadata m =
+                getMediaMetadataWithBitmap(MediaMetadata.METADATA_KEY_ART, mTestBitmap);
+        Metadata metadata = new Metadata.Builder().fromMediaMetadata(m).build();
+        Metadata metadata2 = metadata.clone();
+        metadata2.numTracks = DEFAULT_NUM_TRACKS;
+        assertThat(metadata).isNotEqualTo(metadata2);
+    }
+
+    /**
+     * Make sure two Metadata objects are different if image doesn't match
+     */
+    @Test
+    public void testEqualsDifferentImage() {
+        MediaMetadata m =
+                getMediaMetadataWithBitmap(MediaMetadata.METADATA_KEY_ART, mTestBitmap);
+        MediaMetadata m2 =
+                getMediaMetadataWithBitmap(MediaMetadata.METADATA_KEY_ART, mTestBitmap2);
+        Metadata metadata = new Metadata.Builder().fromMediaMetadata(m).build();
+        Metadata metadata2 = new Metadata.Builder().fromMediaMetadata(m2).build();
+        assertThat(metadata).isNotEqualTo(metadata2);
+    }
+
+    /**
+     * Make sure you can get any non-null string representation of Metadata
+     */
+    @Test
+    public void testToString() {
+        Metadata metadata = new Metadata.Builder().build();
+        assertThat(metadata.toString()).isNotNull();
+    }
+}
diff --git a/tests/unit/src/com/android/bluetooth/avrcp/AvrcpBipObexServerTest.java b/tests/unit/src/com/android/bluetooth/avrcp/AvrcpBipObexServerTest.java
new file mode 100644
index 0000000..1652388
--- /dev/null
+++ b/tests/unit/src/com/android/bluetooth/avrcp/AvrcpBipObexServerTest.java
@@ -0,0 +1,479 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.bluetooth.avrcp;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.bluetooth.audio_util.Image;
+import com.android.bluetooth.avrcpcontroller.BipEncoding;
+import com.android.bluetooth.avrcpcontroller.BipImageDescriptor;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.ByteArrayOutputStream;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+import javax.obex.HeaderSet;
+import javax.obex.Operation;
+import javax.obex.ResponseCodes;
+
+@RunWith(AndroidJUnit4.class)
+public class AvrcpBipObexServerTest {
+    private static final String TYPE_GET_LINKED_THUMBNAIL = "x-bt/img-thm";
+    private static final String TYPE_GET_IMAGE_PROPERTIES = "x-bt/img-properties";
+    private static final String TYPE_GET_IMAGE = "x-bt/img-img";
+    private static final String TYPE_BAD = "x-bt/bad-type";
+
+    private static final byte HEADER_ID_IMG_HANDLE = 0x30;
+    private static final byte HEADER_ID_IMG_DESCRIPTOR = 0x71;
+
+    private static final byte[] BLUETOOTH_UUID_AVRCP_COVER_ART = new byte[] {
+        (byte) 0x71,
+        (byte) 0x63,
+        (byte) 0xDD,
+        (byte) 0x54,
+        (byte) 0x4A,
+        (byte) 0x7E,
+        (byte) 0x11,
+        (byte) 0xE2,
+        (byte) 0xB4,
+        (byte) 0x7C,
+        (byte) 0x00,
+        (byte) 0x50,
+        (byte) 0xC2,
+        (byte) 0x49,
+        (byte) 0x00,
+        (byte) 0x48
+    };
+
+    private static final byte[] NOT_BLUETOOTH_UUID_AVRCP_COVER_ART = new byte[] {
+        (byte) 0x00,
+        (byte) 0x00,
+        (byte) 0x00,
+        (byte) 0x00,
+        (byte) 0x00,
+        (byte) 0x00,
+        (byte) 0x00,
+        (byte) 0x00,
+        (byte) 0x00,
+        (byte) 0x00,
+        (byte) 0x00,
+        (byte) 0x00,
+        (byte) 0x00,
+        (byte) 0x00,
+        (byte) 0x00,
+        (byte) 0x00
+    };
+
+    private static final String IMAGE_HANDLE_1 = "0000001";
+    private static final String IMAGE_HANDLE_UNSTORED = "0000256";
+    private static final String IMAGE_HANDLE_INVALID = "abc1234"; // no non-numeric characters
+
+    private Context mTargetContext;
+    private Resources mTestResources;
+    private CoverArt mCoverArt;
+
+    private AvrcpCoverArtService mAvrcpCoverArtService = null;
+    private AvrcpBipObexServer.Callback mCallback = null;
+
+    private HeaderSet mRequest = null;
+    private HeaderSet mReply = null;
+    private ByteArrayOutputStream mOutputStream = null;
+
+    private AvrcpBipObexServer mAvrcpBipObexServer;
+
+    @Before
+    public void setUp() throws Exception {
+        mTargetContext = InstrumentationRegistry.getTargetContext();
+        try {
+            mTestResources = mTargetContext.getPackageManager()
+                    .getResourcesForApplication("com.android.bluetooth.tests");
+        } catch (PackageManager.NameNotFoundException e) {
+            assertWithMessage("Setup Failure Unable to get resources" + e.toString()).fail();
+        }
+
+        mCoverArt = loadCoverArt(com.android.bluetooth.tests.R.raw.image_200_200);
+
+        mAvrcpCoverArtService = mock(AvrcpCoverArtService.class);
+        mCallback = mock(AvrcpBipObexServer.Callback.class);
+
+        mRequest = new HeaderSet();
+        mReply = new HeaderSet();
+        mOutputStream = new ByteArrayOutputStream();
+
+        mAvrcpBipObexServer = new AvrcpBipObexServer(mAvrcpCoverArtService, mCallback);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        mAvrcpBipObexServer = null;
+        mOutputStream = null;
+        mReply = null;
+        mRequest = null;
+        mCallback = null;
+        mAvrcpCoverArtService = null;
+        mCoverArt = null;
+        mTargetContext = null;
+        mTestResources = null;
+    }
+
+    private CoverArt loadCoverArt(int resId) {
+        InputStream imageInputStream = mTestResources.openRawResource(resId);
+        Bitmap bitmap = BitmapFactory.decodeStream(imageInputStream);
+        Image image = new Image(null, bitmap);
+        return new CoverArt(image);
+    }
+
+    private void setCoverArtAvailableAtHandle(String handle, CoverArt art) {
+        art.setImageHandle(handle);
+        when(mAvrcpCoverArtService.getImage(handle)).thenReturn(art);
+    }
+
+    /**
+     * Creates a mocked operation that can be used by our server as a client request
+     *
+     * Our server will use:
+     *  - getReceivedHeader
+     *  - sendHeaders
+     *  - getMaxPacketSize
+     *  - openOutputStream
+     */
+    private Operation makeOperation(HeaderSet requestHeaders, OutputStream os) throws Exception {
+        Operation op = mock(Operation.class);
+        when(op.getReceivedHeader()).thenReturn(requestHeaders);
+        when(op.getMaxPacketSize()).thenReturn(256);
+        when(op.openOutputStream()).thenReturn(os);
+        return op;
+    }
+
+    private byte[] makeDescriptor(int encoding, int width, int height) {
+        return new BipImageDescriptor.Builder()
+                .setEncoding(encoding)
+                .setFixedDimensions(width, height)
+                .build().serialize();
+    }
+
+    /**
+     * Make sure we let a connection through with a valid UUID
+     */
+    @Test
+    public void testConnectWithValidUuidHeader() throws Exception {
+        mRequest.setHeader(HeaderSet.TARGET, BLUETOOTH_UUID_AVRCP_COVER_ART);
+        int responseCode = mAvrcpBipObexServer.onConnect(mRequest, mReply);
+        verify(mCallback, times(1)).onConnected();
+        assertThat(responseCode).isEqualTo(ResponseCodes.OBEX_HTTP_OK);
+    }
+
+    /**
+     * Make sure we deny a connection when there is an invalid UUID
+     */
+    @Test
+    public void testConnectWithInvalidUuidHeader() throws Exception {
+        mRequest.setHeader(HeaderSet.TARGET, NOT_BLUETOOTH_UUID_AVRCP_COVER_ART);
+        int responseCode = mAvrcpBipObexServer.onConnect(mRequest, mReply);
+        verify(mCallback, never()).onConnected();
+        assertThat(responseCode).isEqualTo(ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE);
+    }
+
+    /**
+     * Make sure onDisconnect notifies the callbacks in the proper way
+     */
+    @Test
+    public void testDisonnect() {
+        mAvrcpBipObexServer.onDisconnect(mRequest, mReply);
+        verify(mCallback, times(1)).onDisconnected();
+    }
+
+    /**
+     * Make sure onClose notifies the callbacks in the proper way
+     */
+    @Test
+    public void testOnClose() {
+        mAvrcpBipObexServer.onClose();
+        verify(mCallback, times(1)).onClose();
+    }
+
+    /**
+     * Make sure onGet handles null headers gracefully
+     */
+    @Test
+    public void testOnGetNoHeaders() throws Exception {
+        Operation op = makeOperation(null, mOutputStream);
+        int responseCode = mAvrcpBipObexServer.onGet(op);
+        assertThat(responseCode).isEqualTo(ResponseCodes.OBEX_HTTP_BAD_REQUEST);
+    }
+
+    /**
+     * Make sure onGet handles bad type gracefully
+     */
+    @Test
+    public void testOnGetBadType() throws Exception {
+        mRequest.setHeader(HeaderSet.TYPE, TYPE_BAD);
+        Operation op = makeOperation(mRequest, mOutputStream);
+        int responseCode = mAvrcpBipObexServer.onGet(op);
+        assertThat(responseCode).isEqualTo(ResponseCodes.OBEX_HTTP_BAD_REQUEST);
+    }
+
+    /**
+     * Make sure onGet handles no type gracefully
+     */
+    @Test
+    public void testOnGetNoType() throws Exception {
+        mRequest.setHeader(HeaderSet.TYPE, null);
+        Operation op = makeOperation(mRequest, mOutputStream);
+        int responseCode = mAvrcpBipObexServer.onGet(op);
+        assertThat(responseCode).isEqualTo(ResponseCodes.OBEX_HTTP_BAD_REQUEST);
+    }
+
+    /**
+     * Make sure a getImageThumbnail request with a valid handle works
+     */
+    @Test
+    public void testGetLinkedThumbnailWithValidHandle() throws Exception {
+        mRequest.setHeader(HeaderSet.TYPE, TYPE_GET_LINKED_THUMBNAIL);
+        mRequest.setHeader(HEADER_ID_IMG_HANDLE, IMAGE_HANDLE_1);
+        setCoverArtAvailableAtHandle(IMAGE_HANDLE_1, mCoverArt);
+        Operation op = makeOperation(mRequest, mOutputStream);
+        int responseCode = mAvrcpBipObexServer.onGet(op);
+        assertThat(responseCode).isEqualTo(ResponseCodes.OBEX_HTTP_OK);
+    }
+
+    /**
+     * Make sure a getImageThumbnail request with a unstored handle returns OBEX_HTTP_NOT_FOUND
+     */
+    @Test
+    public void testGetLinkedThumbnailWithValidUnstoredHandle() throws Exception {
+        mRequest.setHeader(HeaderSet.TYPE, TYPE_GET_LINKED_THUMBNAIL);
+        mRequest.setHeader(HEADER_ID_IMG_HANDLE, IMAGE_HANDLE_UNSTORED);
+        Operation op = makeOperation(mRequest, mOutputStream);
+        int responseCode = mAvrcpBipObexServer.onGet(op);
+        assertThat(responseCode).isEqualTo(ResponseCodes.OBEX_HTTP_NOT_FOUND);
+    }
+
+    /**
+     * Make sure a getImageThumbnail request with an invalidly formatted handle returns
+     * OBEX_HTTP_BAD_REQUEST
+     */
+    @Test
+    public void testGetLinkedThumbnailWithInvalidHandle() throws Exception {
+        mRequest.setHeader(HeaderSet.TYPE, TYPE_GET_LINKED_THUMBNAIL);
+        mRequest.setHeader(HEADER_ID_IMG_HANDLE, IMAGE_HANDLE_INVALID);
+        Operation op = makeOperation(mRequest, mOutputStream);
+        int responseCode = mAvrcpBipObexServer.onGet(op);
+        assertThat(responseCode).isEqualTo(ResponseCodes.OBEX_HTTP_PRECON_FAILED);
+    }
+
+    /**
+     * Make sure a getImageThumbnail request with an invalidly formatted handle returns
+     * OBEX_HTTP_BAD_REQUEST
+     */
+    @Test
+    public void testGetLinkedThumbnailWithNullHandle() throws Exception {
+        mRequest.setHeader(HeaderSet.TYPE, TYPE_GET_LINKED_THUMBNAIL);
+        mRequest.setHeader(HEADER_ID_IMG_HANDLE, null);
+        Operation op = makeOperation(mRequest, mOutputStream);
+        int responseCode = mAvrcpBipObexServer.onGet(op);
+        assertThat(responseCode).isEqualTo(ResponseCodes.OBEX_HTTP_BAD_REQUEST);
+    }
+
+    /**
+     * Make sure a getImageProperties request with a valid handle returns a valie properties object
+     */
+    @Test
+    public void testGetImagePropertiesWithValidHandle() throws Exception {
+        mRequest.setHeader(HeaderSet.TYPE, TYPE_GET_IMAGE_PROPERTIES);
+        mRequest.setHeader(HEADER_ID_IMG_HANDLE, IMAGE_HANDLE_1);
+        setCoverArtAvailableAtHandle(IMAGE_HANDLE_1, mCoverArt);
+        Operation op = makeOperation(mRequest, mOutputStream);
+        int responseCode = mAvrcpBipObexServer.onGet(op);
+        assertThat(responseCode).isEqualTo(ResponseCodes.OBEX_HTTP_OK);
+    }
+
+    /**
+     * Make sure a getImageProperties request with a unstored handle returns OBEX_HTTP_NOT_FOUND
+     */
+    @Test
+    public void testGetImagePropertiesWithValidUnstoredHandle() throws Exception {
+        mRequest.setHeader(HeaderSet.TYPE, TYPE_GET_IMAGE_PROPERTIES);
+        mRequest.setHeader(HEADER_ID_IMG_HANDLE, IMAGE_HANDLE_UNSTORED);
+        Operation op = makeOperation(mRequest, mOutputStream);
+        int responseCode = mAvrcpBipObexServer.onGet(op);
+        assertThat(responseCode).isEqualTo(ResponseCodes.OBEX_HTTP_NOT_FOUND);
+    }
+
+    /**
+     * Make sure a getImageProperties request with an invalidly formatted handle returns
+     * OBEX_HTTP_BAD_REQUEST
+     */
+    @Test
+    public void testGetImagePropertiesWithInvalidHandle() throws Exception {
+        mRequest.setHeader(HeaderSet.TYPE, TYPE_GET_IMAGE_PROPERTIES);
+        mRequest.setHeader(HEADER_ID_IMG_HANDLE, IMAGE_HANDLE_INVALID);
+        Operation op = makeOperation(mRequest, mOutputStream);
+        int responseCode = mAvrcpBipObexServer.onGet(op);
+        assertThat(responseCode).isEqualTo(ResponseCodes.OBEX_HTTP_PRECON_FAILED);
+    }
+
+    /**
+     * Make sure a getImageProperties request with an invalidly formatted handle returns
+     * OBEX_HTTP_BAD_REQUEST
+     */
+    @Test
+    public void testGetImagePropertiesWithNullHandle() throws Exception {
+        mRequest.setHeader(HeaderSet.TYPE, TYPE_GET_IMAGE_PROPERTIES);
+        mRequest.setHeader(HEADER_ID_IMG_HANDLE, null);
+        Operation op = makeOperation(mRequest, mOutputStream);
+        int responseCode = mAvrcpBipObexServer.onGet(op);
+        assertThat(responseCode).isEqualTo(ResponseCodes.OBEX_HTTP_BAD_REQUEST);
+    }
+
+    /**
+     * Make sure a GetImage request with a null descriptor returns a native image
+     */
+    @Test
+    public void testGetImageWithValidHandleAndNullDescriptor() throws Exception {
+        mRequest.setHeader(HeaderSet.TYPE, TYPE_GET_IMAGE);
+        mRequest.setHeader(HEADER_ID_IMG_HANDLE, IMAGE_HANDLE_1);
+        mRequest.setHeader(HEADER_ID_IMG_DESCRIPTOR, null);
+        setCoverArtAvailableAtHandle(IMAGE_HANDLE_1, mCoverArt);
+        Operation op = makeOperation(mRequest, mOutputStream);
+        int responseCode = mAvrcpBipObexServer.onGet(op);
+        assertThat(responseCode).isEqualTo(ResponseCodes.OBEX_HTTP_OK);
+    }
+
+    /**
+     * Make sure a GetImage request with a valid descriptor returns an image
+     */
+    @Test
+    public void testGetImageWithValidHandleAndValidDescriptor() throws Exception {
+        mRequest.setHeader(HeaderSet.TYPE, TYPE_GET_IMAGE);
+        mRequest.setHeader(HEADER_ID_IMG_HANDLE, IMAGE_HANDLE_1);
+        mRequest.setHeader(HEADER_ID_IMG_DESCRIPTOR, makeDescriptor(BipEncoding.JPEG, 200, 200));
+        setCoverArtAvailableAtHandle(IMAGE_HANDLE_1, mCoverArt);
+        Operation op = makeOperation(mRequest, mOutputStream);
+        int responseCode = mAvrcpBipObexServer.onGet(op);
+        assertThat(responseCode).isEqualTo(ResponseCodes.OBEX_HTTP_OK);
+    }
+
+    /**
+     * Make sure a GetImage request with a valid, but unsupported descriptor, returns NOT_ACCEPTABLE
+     */
+    @Test
+    public void testGetImageWithValidHandleAndInvalidDescriptor() throws Exception {
+        mRequest.setHeader(HeaderSet.TYPE, TYPE_GET_IMAGE);
+        mRequest.setHeader(HEADER_ID_IMG_HANDLE, IMAGE_HANDLE_1);
+        mRequest.setHeader(HEADER_ID_IMG_DESCRIPTOR,
+                makeDescriptor(BipEncoding.WBMP /* No Android support, won't work */, 200, 200));
+        setCoverArtAvailableAtHandle(IMAGE_HANDLE_1, mCoverArt);
+        Operation op = makeOperation(mRequest, mOutputStream);
+        int responseCode = mAvrcpBipObexServer.onGet(op);
+        assertThat(responseCode).isEqualTo(ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE);
+    }
+
+    /**
+     * Make sure a GetImage request with a unstored handle returns OBEX_HTTP_NOT_FOUND
+     */
+    @Test
+    public void testGetImageWithValidUnstoredHandle() throws Exception {
+        mRequest.setHeader(HeaderSet.TYPE, TYPE_GET_IMAGE);
+        mRequest.setHeader(HEADER_ID_IMG_HANDLE, IMAGE_HANDLE_UNSTORED);
+        mRequest.setHeader(HEADER_ID_IMG_DESCRIPTOR, makeDescriptor(BipEncoding.JPEG, 200, 200));
+        Operation op = makeOperation(mRequest, mOutputStream);
+        int responseCode = mAvrcpBipObexServer.onGet(op);
+        assertThat(responseCode).isEqualTo(ResponseCodes.OBEX_HTTP_NOT_FOUND);
+    }
+
+    /**
+     * Make sure a getImage request with an invalidly formatted handle returns OBEX_HTTP_BAD_REQUEST
+     */
+    @Test
+    public void testGetImageWithInvalidHandle() throws Exception {
+        mRequest.setHeader(HeaderSet.TYPE, TYPE_GET_IMAGE);
+        mRequest.setHeader(HEADER_ID_IMG_HANDLE, IMAGE_HANDLE_INVALID);
+        mRequest.setHeader(HEADER_ID_IMG_DESCRIPTOR, makeDescriptor(BipEncoding.JPEG, 200, 200));
+        Operation op = makeOperation(mRequest, mOutputStream);
+        int responseCode = mAvrcpBipObexServer.onGet(op);
+        assertThat(responseCode).isEqualTo(ResponseCodes.OBEX_HTTP_PRECON_FAILED);
+    }
+
+    /**
+     * Make sure a getImage request with a null handle returns OBEX_HTTP_BAD_REQUEST
+     */
+    @Test
+    public void testGetImageWithNullHandle() throws Exception {
+        mRequest.setHeader(HeaderSet.TYPE, TYPE_GET_IMAGE);
+        mRequest.setHeader(HEADER_ID_IMG_HANDLE, null);
+        mRequest.setHeader(HEADER_ID_IMG_DESCRIPTOR, makeDescriptor(BipEncoding.JPEG, 200, 200));
+        Operation op = makeOperation(mRequest, mOutputStream);
+        int responseCode = mAvrcpBipObexServer.onGet(op);
+        assertThat(responseCode).isEqualTo(ResponseCodes.OBEX_HTTP_BAD_REQUEST);
+    }
+
+    /**
+     * Make sure onPut is not a supported action
+     */
+    @Test
+    public void testOnPut() {
+        Operation op = null;
+        int responseCode = mAvrcpBipObexServer.onPut(op);
+        assertThat(responseCode).isEqualTo(ResponseCodes.OBEX_HTTP_NOT_IMPLEMENTED);
+    }
+
+    /**
+     * Make sure onAbort is not a supported action
+     */
+    @Test
+    public void testOnAbort() {
+        HeaderSet request = null;
+        HeaderSet reply = null;
+        int responseCode = mAvrcpBipObexServer.onAbort(request, reply);
+        assertThat(responseCode).isEqualTo(ResponseCodes.OBEX_HTTP_NOT_IMPLEMENTED);
+    }
+
+    /**
+     * Make sure onSetPath is not a supported action
+     */
+    @Test
+    public void testOnSetPath() {
+        HeaderSet request = null;
+        HeaderSet reply = null;
+        boolean backup = false;
+        boolean create = false;
+        int responseCode = mAvrcpBipObexServer.onSetPath(request, reply, backup, create);
+        assertThat(responseCode).isEqualTo(ResponseCodes.OBEX_HTTP_NOT_IMPLEMENTED);
+    }
+}
diff --git a/tests/unit/src/com/android/bluetooth/avrcp/AvrcpCoverArtStorageTest.java b/tests/unit/src/com/android/bluetooth/avrcp/AvrcpCoverArtStorageTest.java
new file mode 100644
index 0000000..f415662
--- /dev/null
+++ b/tests/unit/src/com/android/bluetooth/avrcp/AvrcpCoverArtStorageTest.java
@@ -0,0 +1,179 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.bluetooth.avrcp;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.bluetooth.audio_util.Image;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.InputStream;
+
+@RunWith(AndroidJUnit4.class)
+public class AvrcpCoverArtStorageTest {
+    private Context mTargetContext;
+    private Resources mTestResources;
+
+    private AvrcpCoverArtStorage mAvrcpCoverArtStorage;
+
+    @Before
+    public void setUp() throws Exception {
+        mTargetContext = InstrumentationRegistry.getTargetContext();
+        try {
+            mTestResources = mTargetContext.getPackageManager()
+                    .getResourcesForApplication("com.android.bluetooth.tests");
+        } catch (PackageManager.NameNotFoundException e) {
+            assertWithMessage("Setup Failure Unable to get resources" + e.toString()).fail();
+        }
+
+        mAvrcpCoverArtStorage = new AvrcpCoverArtStorage(2);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        mAvrcpCoverArtStorage.clear();
+        mAvrcpCoverArtStorage = null;
+        mTestResources = null;
+        mTargetContext = null;
+    }
+
+    private CoverArt getCoverArt(int resId) {
+        InputStream imageInputStream = mTestResources.openRawResource(resId);
+        Bitmap bitmap = BitmapFactory.decodeStream(imageInputStream);
+        Image image = new Image(null, bitmap);
+        return new CoverArt(image);
+    }
+
+    /**
+     * Make sure you can store and get an image handle for an image
+     */
+    @Test
+    public void testStoreImage() {
+        CoverArt artwork = getCoverArt(com.android.bluetooth.tests.R.raw.image_200_200);
+        String handle = mAvrcpCoverArtStorage.storeImage(artwork);
+        assertThat(handle).isNotNull();
+        assertThat(mAvrcpCoverArtStorage.getImage(handle)).isEqualTo(artwork);
+    }
+
+    /**
+     * Make sure an attempt to store an image that is already stored yields the previous handle
+     */
+    @Test
+    public void testStoreImageThatIsAlreadyStored() {
+        CoverArt artwork = getCoverArt(com.android.bluetooth.tests.R.raw.image_200_200);
+        String handle = mAvrcpCoverArtStorage.storeImage(artwork);
+        assertThat(handle).isNotNull();
+        assertThat(mAvrcpCoverArtStorage.storeImage(artwork)).isEqualTo(handle);
+    }
+
+    /**
+     * Make sure you can store and get an image handle for a second image thats not yet stored
+     */
+    @Test
+    public void testStoreSecondImage() {
+        CoverArt artwork_green = getCoverArt(com.android.bluetooth.tests.R.raw.image_200_200);
+        CoverArt artwork_blue = getCoverArt(com.android.bluetooth.tests.R.raw.image_200_200_blue);
+        String handle_green = mAvrcpCoverArtStorage.storeImage(artwork_green);
+        String handle_blue = mAvrcpCoverArtStorage.storeImage(artwork_blue);
+        assertThat(handle_green).isNotNull();
+        assertThat(handle_blue).isNotNull();
+        assertThat(handle_green).isNotEqualTo(handle_blue);
+        assertThat(mAvrcpCoverArtStorage.getImage(handle_green)).isEqualTo(artwork_green);
+        assertThat(mAvrcpCoverArtStorage.getImage(handle_blue)).isEqualTo(artwork_blue);
+    }
+
+    /**
+     * Make sure you can store and get an image handle for a third image thats not yet stored.
+     *
+     * Since the cache size is set to 2 for these tests, this third image should force the least
+     * recently used image to be removed. This test has the LRU image as the first one entered.
+     */
+    @Test
+    public void testStoreThirdImageWithLruAsFirstImage() {
+        CoverArt artwork_green = getCoverArt(com.android.bluetooth.tests.R.raw.image_200_200);
+        CoverArt artwork_blue = getCoverArt(com.android.bluetooth.tests.R.raw.image_200_200_blue);
+        CoverArt artwork_orange =
+                getCoverArt(com.android.bluetooth.tests.R.raw.image_200_200_orange);
+
+        // Store image 1, 2 and 3, evicting image 1 when image 3 is stored
+        String handle_green = mAvrcpCoverArtStorage.storeImage(artwork_green);
+        String handle_blue = mAvrcpCoverArtStorage.storeImage(artwork_blue);
+        String handle_orange = mAvrcpCoverArtStorage.storeImage(artwork_orange);
+
+        // Check to make sure handles make sense
+        assertThat(handle_green).isNotNull();
+        assertThat(handle_blue).isNotNull();
+        assertThat(handle_orange).isNotNull();
+        assertThat(handle_green).isNotEqualTo(handle_blue);
+        assertThat(handle_green).isNotEqualTo(handle_orange);
+        assertThat(handle_blue).isNotEqualTo(handle_orange);
+
+        // Make sure images 2 and 3 are available and image 1 is not
+        assertThat(mAvrcpCoverArtStorage.getImage(handle_orange)).isEqualTo(artwork_orange);
+        assertThat(mAvrcpCoverArtStorage.getImage(handle_blue)).isEqualTo(artwork_blue);
+        assertThat(mAvrcpCoverArtStorage.getImage(handle_green)).isNull();
+    }
+
+    /**
+     * Make sure you can store and get an image handle for a third image thats not yet stored.
+     *
+     * Since the cache size is set to 2 for these tests, this third image should force the least
+     * recently used image to be removed. This test has the LRU image as the second one entered.
+     */
+    @Test
+    public void testStoreThirdImageWithLruAsSecondImage() {
+        CoverArt artwork_green = getCoverArt(com.android.bluetooth.tests.R.raw.image_200_200);
+        CoverArt artwork_blue = getCoverArt(com.android.bluetooth.tests.R.raw.image_200_200_blue);
+        CoverArt artwork_orange =
+                getCoverArt(com.android.bluetooth.tests.R.raw.image_200_200_orange);
+
+        // Store images 1 and 2, touch image 1 by getting it
+        String handle_green = mAvrcpCoverArtStorage.storeImage(artwork_green);
+        String handle_blue = mAvrcpCoverArtStorage.storeImage(artwork_blue);
+        mAvrcpCoverArtStorage.getImage(handle_green);
+
+        // Store image 3, evicting image 2
+        String handle_orange = mAvrcpCoverArtStorage.storeImage(artwork_orange);
+
+        // Check that handles make sense
+        assertThat(handle_green).isNotNull();
+        assertThat(handle_blue).isNotNull();
+        assertThat(handle_orange).isNotNull();
+        assertThat(handle_green).isNotEqualTo(handle_blue);
+        assertThat(handle_green).isNotEqualTo(handle_orange);
+        assertThat(handle_blue).isNotEqualTo(handle_orange);
+
+        // Make sure image 1 and image 3 are available readily, image 2 is evicted
+        assertThat(mAvrcpCoverArtStorage.getImage(handle_orange)).isEqualTo(artwork_orange);
+        assertThat(mAvrcpCoverArtStorage.getImage(handle_green)).isEqualTo(artwork_green);
+        assertThat(mAvrcpCoverArtStorage.getImage(handle_blue)).isNull();
+    }
+}
diff --git a/tests/unit/src/com/android/bluetooth/avrcp/CoverArtTest.java b/tests/unit/src/com/android/bluetooth/avrcp/CoverArtTest.java
new file mode 100644
index 0000000..b78bca5
--- /dev/null
+++ b/tests/unit/src/com/android/bluetooth/avrcp/CoverArtTest.java
@@ -0,0 +1,311 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.bluetooth.avrcp;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.bluetooth.audio_util.Image;
+import com.android.bluetooth.avrcpcontroller.BipEncoding;
+import com.android.bluetooth.avrcpcontroller.BipImageDescriptor;
+import com.android.bluetooth.avrcpcontroller.BipImageFormat;
+import com.android.bluetooth.avrcpcontroller.BipImageProperties;
+import com.android.bluetooth.avrcpcontroller.BipPixel;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.InputStream;
+import java.util.Arrays;
+
+@RunWith(AndroidJUnit4.class)
+public class CoverArtTest {
+    private Context mTargetContext;
+    private Resources mTestResources;
+
+    private static final BipPixel PIXEL_THUMBNAIL = BipPixel.createFixed(200, 200);
+    private static final String IMAGE_HANDLE_1 = "0000001";
+
+    private Bitmap m200by200Image = null;
+    private Bitmap m200by200ImageBlue = null;
+
+    private Image mImage = null;
+    private Image mImage2 = null;
+
+    @Before
+    public void setUp() throws Exception {
+        mTargetContext = InstrumentationRegistry.getTargetContext();
+        try {
+            mTestResources = mTargetContext.getPackageManager()
+                    .getResourcesForApplication("com.android.bluetooth.tests");
+        } catch (PackageManager.NameNotFoundException e) {
+            assertWithMessage("Setup Failure Unable to get resources" + e.toString()).fail();
+        }
+
+        m200by200Image = loadImage(com.android.bluetooth.tests.R.raw.image_200_200);
+        m200by200ImageBlue = loadImage(com.android.bluetooth.tests.R.raw.image_200_200_blue);
+        mImage = new Image(null, m200by200Image);
+        mImage2 = new Image(null, m200by200ImageBlue);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        mImage2 = null;
+        mImage = null;
+        m200by200ImageBlue = null;
+        m200by200Image = null;
+        mTestResources = null;
+        mTargetContext = null;
+    }
+
+    private Bitmap loadImage(int resId) {
+        InputStream imageInputStream = mTestResources.openRawResource(resId);
+        return BitmapFactory.decodeStream(imageInputStream);
+    }
+
+    private InputStream toInputSteam(Bitmap bitmap) {
+        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+        bitmap.compress(Bitmap.CompressFormat.PNG, 0, outputStream);
+        byte[] imageBytes = outputStream.toByteArray();
+        return new ByteArrayInputStream(imageBytes);
+    }
+
+    private Bitmap toBitmap(byte[] imageBytes) {
+        ByteArrayInputStream inputStream = new ByteArrayInputStream(imageBytes);
+        return BitmapFactory.decodeStream(inputStream);
+    }
+
+    private BipImageDescriptor getDescriptor(int encoding, int width, int height) {
+        return new BipImageDescriptor.Builder()
+                .setEncoding(encoding)
+                .setFixedDimensions(width, height)
+                .build();
+    }
+
+    private boolean containsThumbnailFormat(BipImageProperties properties) {
+        if (properties == null) return false;
+
+        for (BipImageFormat format : properties.getNativeFormats()) {
+            BipEncoding encoding = format.getEncoding();
+            BipPixel pixel = format.getPixel();
+            if (encoding == null || pixel == null) continue;
+            if (encoding.getType() == BipEncoding.JPEG && PIXEL_THUMBNAIL.equals(pixel)) {
+                return true;
+            }
+        }
+
+        for (BipImageFormat format : properties.getVariantFormats()) {
+            BipEncoding encoding = format.getEncoding();
+            BipPixel pixel = format.getPixel();
+            if (encoding == null || pixel == null) continue;
+            if (encoding.getType() == BipEncoding.JPEG && PIXEL_THUMBNAIL.equals(pixel)) {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    private boolean isThumbnailFormat(Bitmap image) {
+        if (image == null) return false;
+        return (200 == image.getHeight() && 200 == image.getWidth());
+    }
+
+    /**
+     * Make sure you can create an image from an Image object
+     */
+    @Test
+    public void testCreateCoverArtFromImage() {
+        CoverArt artwork = new CoverArt(mImage);
+        assertThat(artwork.getImage()).isNotNull();
+    }
+
+    /**
+     * Make sure you get an image hash from a valid image
+     */
+    @Test
+    public void testGetImageHash() {
+        CoverArt artwork = new CoverArt(mImage);
+        String hash = artwork.getImageHash();
+        assertThat(hash).isNotNull();
+    }
+
+    /**
+     * Make sure you get the same image hash from several calls to the same object
+     */
+    @Test
+    public void testGetImageHashSameForMultipleCalls() {
+        CoverArt artwork = new CoverArt(mImage);
+        String hash = artwork.getImageHash();
+        assertThat(hash).isNotNull();
+        assertThat(artwork.getImageHash()).isEqualTo(hash); // extra call 1
+        assertThat(artwork.getImageHash()).isEqualTo(hash); // extra call 2
+    }
+
+    /**
+     * Make sure you get the same image hash from separate objects created from the same image
+     */
+    @Test
+    public void testGetImageHashSameForSameImages() {
+        CoverArt artwork = new CoverArt(mImage);
+        CoverArt artwork2 = new CoverArt(mImage);
+        String hash = artwork.getImageHash();
+        String hash2 = artwork2.getImageHash();
+
+        assertThat(hash).isNotNull();
+        assertThat(hash2).isNotNull();
+        assertThat(hash).isEqualTo(hash2);
+    }
+
+    /**
+     * Make sure you get different image hashes from separate objects created from different images
+     */
+    @Test
+    public void testGetImageHashDifferentForDifferentImages() {
+        CoverArt artwork = new CoverArt(mImage);
+        CoverArt artwork2 = new CoverArt(mImage2);
+        String hash = artwork.getImageHash();
+        String hash2 = artwork2.getImageHash();
+
+        assertThat(hash).isNotNull();
+        assertThat(hash2).isNotNull();
+        assertThat(hash).isNotEqualTo(hash2);
+    }
+
+    /**
+     * Make sure you get an image when asking for the native image
+     */
+    @Test
+    public void testGetNativeImage() {
+        CoverArt artwork = new CoverArt(mImage);
+        byte[] image = artwork.getImage();
+        assertThat(image).isNotNull();
+    }
+
+    /**
+     * Make sure you getThumbnailImage returns an image as a 200 by 200 JPEG
+     */
+    @Test
+    public void testGetThumbnailImage() {
+        CoverArt artwork = new CoverArt(mImage);
+        byte[] imageBytes = artwork.getThumbnail();
+        assertThat(imageBytes).isNotNull();
+        Bitmap image = toBitmap(imageBytes);
+        assertThat(isThumbnailFormat(image)).isTrue();
+    }
+
+    /**
+     * Make sure you can set the image handle associated with this object
+     */
+    @Test
+    public void testGetAndSetImageHandle() {
+        CoverArt artwork = new CoverArt(mImage);
+        assertThat(artwork.getImageHandle()).isNull();
+        artwork.setImageHandle(IMAGE_HANDLE_1);
+        assertThat(artwork.getImageHandle()).isEqualTo(IMAGE_HANDLE_1);
+    }
+
+    /**
+     * Make sure a getImageProperties() yields a set of image properties. The thumbnail format
+     * MUST be contained in that set
+     */
+    @Test
+    public void testGetImageProperties() {
+        CoverArt artwork = new CoverArt(mImage);
+        artwork.setImageHandle(IMAGE_HANDLE_1);
+        BipImageProperties properties = artwork.getImageProperties();
+        assertThat(properties).isNotNull();
+        assertThat(containsThumbnailFormat(properties)).isTrue();
+    }
+
+    /**
+     * Make sure a getImage(<valid descriptor>) yield an image in the format you asked for
+     */
+    @Test
+    public void testGetImageWithValidDescriptor() {
+        CoverArt artwork = new CoverArt(mImage);
+        BipImageDescriptor descriptor = getDescriptor(BipEncoding.JPEG, 200, 200);
+        byte[] image = artwork.getImage(descriptor);
+        assertThat(image).isNotNull();
+    }
+
+    /**
+     * Make sure a getImage(<thumbnail descriptor>) yields the image in the thumbnail format
+     */
+    @Test
+    public void testGetImageWithThumbnailDescriptor() {
+        CoverArt artwork = new CoverArt(mImage);
+        BipImageDescriptor descriptor = getDescriptor(BipEncoding.JPEG, 200, 200);
+        byte[] imageBytes = artwork.getImage(descriptor);
+        assertThat(imageBytes).isNotNull();
+        Bitmap image = toBitmap(imageBytes);
+        assertThat(isThumbnailFormat(image)).isTrue();
+    }
+
+    /**
+     * Make sure a getImage(<invalid descriptor>) yields a null
+     */
+    @Test
+    public void testGetImageWithInvalidDescriptor() {
+        CoverArt artwork = new CoverArt(mImage);
+        BipImageDescriptor descriptor = getDescriptor(BipEncoding.BMP, 1200, 1200);
+        byte[] image = artwork.getImage(descriptor);
+        assertThat(image).isNull();
+    }
+
+    /**
+     * Make sure a getImage(<null descriptor>) yields the native image
+     */
+    @Test
+    public void testGetImageWithoutDescriptor() {
+        CoverArt artwork = new CoverArt(mImage);
+        byte[] image = artwork.getImage(null);
+        byte[] nativeImage = artwork.getImage();
+        assertThat(Arrays.equals(nativeImage, image)).isTrue();
+    }
+
+    /**
+     * Make sure we can get a valid string representation of the CoverArt
+     */
+    @Test
+    public void testGetSize() {
+        CoverArt artwork = new CoverArt(mImage);
+        assertThat(artwork.size() > 0).isTrue();
+    }
+
+    /**
+     * Make sure we can get a valid string representation of the CoverArt
+     */
+    @Test
+    public void testToString() {
+        CoverArt artwork = new CoverArt(mImage);
+        assertThat(artwork.toString()).isNotNull();
+    }
+}
diff --git a/tests/unit/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerServiceTest.java b/tests/unit/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerServiceTest.java
index e9440f5..4f4fd5a 100644
--- a/tests/unit/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerServiceTest.java
+++ b/tests/unit/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerServiceTest.java
@@ -15,6 +15,9 @@
  */
 package com.android.bluetooth.avrcpcontroller;
 
+import static org.mockito.Mockito.anyString;
+import static org.mockito.Mockito.doReturn;
+
 import android.bluetooth.BluetoothAdapter;
 import android.content.Context;
 
@@ -56,6 +59,7 @@
                         .getBoolean(R.bool.profile_supported_avrcp_controller));
         MockitoAnnotations.initMocks(this);
         TestUtils.setAdapterService(mAdapterService);
+        doReturn(true, false).when(mAdapterService).isStartedProfile(anyString());
         TestUtils.startService(mServiceRule, AvrcpControllerService.class);
         mService = AvrcpControllerService.getAvrcpControllerService();
         Assert.assertNotNull(mService);
diff --git a/tests/unit/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerStateMachineTest.java b/tests/unit/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerStateMachineTest.java
index 07e1a29..41a7696 100644
--- a/tests/unit/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerStateMachineTest.java
+++ b/tests/unit/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerStateMachineTest.java
@@ -15,6 +15,7 @@
  */
 package com.android.bluetooth.avrcpcontroller;
 
+import static android.Manifest.permission.BLUETOOTH_CONNECT;
 import static org.mockito.Mockito.*;
 
 import android.bluetooth.BluetoothAdapter;
@@ -25,9 +26,13 @@
 import android.content.Intent;
 import android.content.res.Resources;
 import android.media.AudioManager;
+import android.os.Bundle;
 import android.os.Looper;
+import android.support.v4.media.MediaMetadataCompat;
 import android.support.v4.media.session.MediaControllerCompat;
+import android.support.v4.media.session.MediaSessionCompat;
 import android.support.v4.media.session.PlaybackStateCompat;
+import android.util.SparseArray;
 
 import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.FlakyTest;
@@ -39,7 +44,7 @@
 import com.android.bluetooth.TestUtils;
 import com.android.bluetooth.a2dpsink.A2dpSinkService;
 import com.android.bluetooth.btservice.AdapterService;
-import com.android.bluetooth.btservice.ProfileService;
+import com.android.bluetooth.btservice.storage.DatabaseManager;
 
 import org.hamcrest.core.IsInstanceOf;
 import org.junit.After;
@@ -64,32 +69,25 @@
     private static final int CONNECT_TIMEOUT_TEST_MILLIS = 1000;
     private static final int KEY_DOWN = 0;
     private static final int KEY_UP = 1;
-    private AvrcpControllerStateMachine mAvrcpStateMachine;
+
     private BluetoothAdapter mAdapter;
     private Context mTargetContext;
-    private BluetoothDevice mTestDevice;
-    private ArgumentCaptor<Intent> mIntentArgument = ArgumentCaptor.forClass(Intent.class);
-    private byte[] mTestAddress = new byte[]{00, 01, 02, 03, 04, 05};
 
     @Rule public final ServiceTestRule mAvrcpServiceRule = new ServiceTestRule();
     @Rule public final ServiceTestRule mA2dpServiceRule = new ServiceTestRule();
+    @Mock private AdapterService mA2dpAdapterService;
+    @Mock private AdapterService mAvrcpAdapterService;
+    @Mock private A2dpSinkService mA2dpSinkService;
+    @Mock private DatabaseManager mDatabaseManager;
+    @Mock private AudioManager mAudioManager;
+    @Mock private Resources mMockResources;
+    private ArgumentCaptor<Intent> mIntentArgument = ArgumentCaptor.forClass(Intent.class);
+    @Mock private AvrcpControllerService mAvrcpControllerService;
+    @Mock private AvrcpCoverArtManager mCoverArtManager;
 
-    @Mock
-    private AdapterService mAvrcpAdapterService;
-
-    @Mock
-    private AdapterService mA2dpAdapterService;
-
-    @Mock
-    private AudioManager mAudioManager;
-    @Mock
-    private AvrcpControllerService mAvrcpControllerService;
-    @Mock
-    private A2dpSinkService mA2dpSinkService;
-
-    @Mock
-    private Resources mMockResources;
-
+    private byte[] mTestAddress = new byte[]{01, 01, 01, 01, 01, 01};
+    private BluetoothDevice mTestDevice = null;
+    private AvrcpControllerStateMachine mAvrcpStateMachine = null;
 
     @Before
     public void setUp() throws Exception {
@@ -102,33 +100,44 @@
         }
         Assert.assertNotNull(Looper.myLooper());
 
-        // Setup mocks and test assets
         MockitoAnnotations.initMocks(this);
-        TestUtils.setAdapterService(mAvrcpAdapterService);
-        TestUtils.startService(mAvrcpServiceRule, AvrcpControllerService.class);
-        TestUtils.clearAdapterService(mAvrcpAdapterService);
+
+        // Start a real A2dpSinkService so we can replace the static instance with our mock
+        doReturn(mDatabaseManager).when(mA2dpAdapterService).getDatabase();
+        doReturn(true).when(mA2dpAdapterService).isStartedProfile(anyString());
         TestUtils.setAdapterService(mA2dpAdapterService);
         TestUtils.startService(mA2dpServiceRule, A2dpSinkService.class);
-        when(mA2dpSinkService.setActiveDeviceNative(any())).thenReturn(true);
-
-        when(mMockResources.getBoolean(R.bool.a2dp_sink_automatically_request_audio_focus))
-                .thenReturn(true);
-        doReturn(mMockResources).when(mAvrcpControllerService).getResources();
         A2dpSinkService.setA2dpSinkService(mA2dpSinkService);
+        TestUtils.clearAdapterService(mA2dpAdapterService);
+
+        // Start an AvrcpControllerService to get a real BluetoothMediaBrowserService up
+        doReturn(true).when(mAvrcpAdapterService).isStartedProfile(anyString());
+        TestUtils.setAdapterService(mAvrcpAdapterService);
+        TestUtils.startService(mAvrcpServiceRule, AvrcpControllerService.class);
+
+        // Mock an AvrcpControllerService to give to all state machines
+        doReturn(BluetoothProfile.STATE_DISCONNECTED).when(mCoverArtManager).getState(any());
         doReturn(15).when(mAudioManager).getStreamMaxVolume(anyInt());
         doReturn(8).when(mAudioManager).getStreamVolume(anyInt());
         doReturn(true).when(mAudioManager).isVolumeFixed();
+        when(mMockResources.getBoolean(R.bool.a2dp_sink_automatically_request_audio_focus))
+                .thenReturn(true);
+        doReturn(mMockResources).when(mAvrcpControllerService).getResources();
         doReturn(mAudioManager).when(mAvrcpControllerService)
                 .getSystemService(Context.AUDIO_SERVICE);
+        doReturn(mCoverArtManager).when(mAvrcpControllerService).getCoverArtManager();
+        mAvrcpControllerService.sBrowseTree = new BrowseTree(null);
+
+        // Ensure our MediaBrowserService starts with a blank state
+        BluetoothMediaBrowserService.reset();
 
         // This line must be called to make sure relevant objects are initialized properly
         mAdapter = BluetoothAdapter.getDefaultAdapter();
-        // Get a device for testing
+
+        // Set up device and state machine under test
         mTestDevice = mAdapter.getRemoteDevice(mTestAddress);
-        mAvrcpControllerService.start();
-        mAvrcpControllerService.sBrowseTree = new BrowseTree(null);
-        mAvrcpStateMachine = new AvrcpControllerStateMachine(mTestDevice, mAvrcpControllerService);
-        mAvrcpStateMachine.start();
+        mAvrcpStateMachine = makeStateMachine(mTestDevice);
+        setActiveDevice(mTestDevice);
     }
 
     @After
@@ -136,29 +145,226 @@
         if (!mTargetContext.getResources().getBoolean(R.bool.profile_supported_avrcp_controller)) {
             return;
         }
+        destroyStateMachine(mAvrcpStateMachine);
+        TestUtils.clearAdapterService(mAvrcpAdapterService);
+    }
 
-        mAvrcpStateMachine.disconnect();
+    /**
+     * Create a state machine to test
+     */
+    private AvrcpControllerStateMachine makeStateMachine(BluetoothDevice device) {
+        AvrcpControllerStateMachine sm =
+                 new AvrcpControllerStateMachine(device, mAvrcpControllerService);
+        sm.start();
+        return sm;
+    }
+
+    /**
+     * Destroy a state machine you created to test
+     */
+    private void destroyStateMachine(AvrcpControllerStateMachine sm) {
+        if (sm == null || sm.getState() == BluetoothProfile.STATE_DISCONNECTED) return;
+
+        sm.disconnect();
+        TestUtils.waitForLooperToFinishScheduledTask(sm.getHandler().getLooper());
+
+        // is disconnected
+        Assert.assertEquals(sm.getState(), BluetoothProfile.STATE_DISCONNECTED);
+
+        // told mAvrcpControllerService to remove it
+        // verify(mAvrcpControllerService).removeStateMachine(eq(sm));
+    }
+
+    /**
+     * Set up which device the AvrcpControllerService will report as active
+     */
+    private void setActiveDevice(BluetoothDevice device) {
+        doReturn(device).when(mAvrcpControllerService).getActiveDevice();
+        if (mTestDevice.equals(device)) {
+            mAvrcpStateMachine.setDeviceState(AvrcpControllerService.DEVICE_STATE_ACTIVE);
+        } else {
+            mAvrcpStateMachine.setDeviceState(AvrcpControllerService.DEVICE_STATE_INACTIVE);
+            BluetoothMediaBrowserService.reset();
+        }
+    }
+
+    /**
+     * Setup Connected State for a given state machine
+     *
+     * @return number of times mAvrcpControllerService.sendBroadcastAsUser() has been invoked
+     */
+    private int setUpConnectedState(boolean control, boolean browsing) {
+
+        Assert.assertThat(mAvrcpStateMachine.getCurrentState(),
+                IsInstanceOf.instanceOf(AvrcpControllerStateMachine.Disconnected.class));
+
+        mAvrcpStateMachine.connect(StackEvent.connectionStateChanged(control, browsing));
+
         TestUtils.waitForLooperToFinishScheduledTask(mAvrcpStateMachine.getHandler().getLooper());
-        Assert.assertFalse(mAvrcpStateMachine.isActive());
+        verify(mAvrcpControllerService, timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(2)).sendBroadcast(
+                mIntentArgument.capture(), eq(BLUETOOTH_CONNECT),
+                any(Bundle.class));
+        Assert.assertThat(mAvrcpStateMachine.getCurrentState(),
+                IsInstanceOf.instanceOf(AvrcpControllerStateMachine.Connected.class));
+        Assert.assertEquals(mAvrcpStateMachine.getState(), BluetoothProfile.STATE_CONNECTED);
 
-        TestUtils.clearAdapterService(mA2dpAdapterService);
+        return BluetoothProfile.STATE_CONNECTED;
+    }
+
+    private AvrcpItem makeTrack(String title, String artist, String album, long trackNum,
+            long totalTracks, String genre, long duration, String imageHandle) {
+        AvrcpItem.Builder builder = new AvrcpItem.Builder();
+        builder.setItemType(AvrcpItem.TYPE_MEDIA);
+        builder.setType(AvrcpItem.MEDIA_AUDIO);
+        builder.setDevice(mTestDevice);
+        builder.setPlayable(true);
+        builder.setUid(0);
+        builder.setUuid("AVRCP-ITEM-TEST-UUID");
+
+        builder.setTitle(title);
+        builder.setArtistName(artist);
+        builder.setAlbumName(album);
+        builder.setTrackNumber(trackNum);
+        builder.setTotalNumberOfTracks(totalTracks);
+        builder.setGenre(genre);
+        builder.setPlayingTime(duration);
+        if (imageHandle != null) {
+            builder.setCoverArtHandle(imageHandle);
+        }
+
+        return builder.build();
+    }
+
+    private AvrcpPlayer makePlayer(BluetoothDevice device, int playerId, String playerName,
+            int playerType, byte[] playerFeatures, int playStatus) {
+        AvrcpPlayer.Builder apb = new AvrcpPlayer.Builder();
+        apb.setDevice(device);
+        apb.setPlayerId(playerId);
+        apb.setName(playerName);
+        apb.setPlayerType(playerType);
+        apb.setSupportedFeatures(playerFeatures);
+        apb.setPlayStatus(playStatus);
+        return apb.build();
+    }
+
+    /**
+     * Send a message to the state machine that the track has changed. Must be connected to
+     * do this.
+     */
+    private void setCurrentTrack(AvrcpItem track) {
+        mAvrcpStateMachine.sendMessage(AvrcpControllerStateMachine.MESSAGE_PROCESS_TRACK_CHANGED,
+                track);
+        TestUtils.waitForLooperToFinishScheduledTask(mAvrcpStateMachine.getHandler().getLooper());
+        Assert.assertEquals(mAvrcpStateMachine.getCurrentTrack(), track);
+    }
+
+    /**
+     * Set the current play status (Play, Pause, etc.) of the device
+     */
+    private void setPlaybackState(int state) {
+        mAvrcpStateMachine.sendMessage(
+                AvrcpControllerStateMachine.MESSAGE_PROCESS_PLAY_STATUS_CHANGED, state);
+        TestUtils.waitForLooperToFinishScheduledTask(mAvrcpStateMachine.getHandler().getLooper());
+    }
+
+    /**
+     * Set the current playback position of the device
+     */
+    private void setPlaybackPosition(int position, int duration) {
+        mAvrcpStateMachine.sendMessage(
+                AvrcpControllerStateMachine.MESSAGE_PROCESS_PLAY_POS_CHANGED, duration, position);
+        TestUtils.waitForLooperToFinishScheduledTask(mAvrcpStateMachine.getHandler().getLooper());
+    }
+
+    /**
+     * Make an AvrcpItem suitable for being included in the Now Playing list for the test device
+     */
+    private AvrcpItem makeNowPlayingItem(long uid, String name) {
+        AvrcpItem.Builder aib = new AvrcpItem.Builder();
+        aib.setDevice(mTestDevice);
+        aib.setItemType(AvrcpItem.TYPE_MEDIA);
+        aib.setType(AvrcpItem.MEDIA_AUDIO);
+        aib.setTitle(name);
+        aib.setUid(uid);
+        aib.setUuid(UUID.randomUUID().toString());
+        aib.setPlayable(true);
+        return aib.build();
+    }
+
+    /**
+     * Get the current Now Playing list for the test device
+     */
+    private List<AvrcpItem> getNowPlayingList() {
+        BrowseTree.BrowseNode nowPlaying = mAvrcpStateMachine.findNode("NOW_PLAYING");
+        List<AvrcpItem> nowPlayingList = new ArrayList<AvrcpItem>();
+        for (BrowseTree.BrowseNode child : nowPlaying.getChildren()) {
+            nowPlayingList.add(child.mItem);
+        }
+        return nowPlayingList;
+    }
+
+    /**
+     * Set the current Now Playing list for the test device
+     */
+    private void setNowPlayingList(List<AvrcpItem> nowPlayingList) {
+        BrowseTree.BrowseNode nowPlaying = mAvrcpStateMachine.findNode("NOW_PLAYING");
+        mAvrcpStateMachine.requestContents(nowPlaying);
+        mAvrcpStateMachine.sendMessage(
+                AvrcpControllerStateMachine.MESSAGE_PROCESS_GET_FOLDER_ITEMS, nowPlayingList);
+        mAvrcpStateMachine.sendMessage(
+                AvrcpControllerStateMachine.MESSAGE_PROCESS_GET_FOLDER_ITEMS_OUT_OF_RANGE);
+
+        // Wait for the now playing list to be propagated
+        TestUtils.waitForLooperToFinishScheduledTask(mAvrcpStateMachine.getHandler().getLooper());
+
+        // Make sure its set by re grabbing the node and checking its contents are cached
+        nowPlaying = mAvrcpStateMachine.findNode("NOW_PLAYING");
+        Assert.assertTrue(nowPlaying.isCached());
+        assertNowPlayingList(nowPlayingList);
+    }
+
+    /**
+     * Assert that the Now Playing list is a particular value
+     */
+    private void assertNowPlayingList(List<AvrcpItem> expected) {
+        List<AvrcpItem> current = getNowPlayingList();
+        Assert.assertEquals(expected.size(), current.size());
+        for (int i = 0; i < expected.size(); i++) {
+            Assert.assertEquals(expected.get(i), current.get(i));
+        }
     }
 
     /**
      * Test to confirm that the state machine is capable of cycling through the 4
-     * connection states, and that upon completion, it cleans up aftwards.
+     * connection states, and that upon completion, it cleans up afterwards.
      */
     @Test
     public void testDisconnect() {
         int numBroadcastsSent = setUpConnectedState(true, true);
-        StackEvent event =
-                StackEvent.connectionStateChanged(false, false);
+        testDisconnectInternal(numBroadcastsSent);
+    }
 
+    /**
+     * Test to confirm that the state machine is capable of cycling through the 4
+     * connection states with no crashes, even if the {@link AvrcpControllerService} is stopped and
+     * the {@code sBrowseTree} is null. This could happen if BT is disabled as the profile is being
+     * disconnected.
+     */
+    @Test
+    public void testDisconnectWithNullBrowseTree() {
+        int numBroadcastsSent = setUpConnectedState(true, true);
+        mAvrcpControllerService.stop();
+
+        testDisconnectInternal(numBroadcastsSent);
+    }
+
+    private void testDisconnectInternal(int numBroadcastsSent) {
         mAvrcpStateMachine.disconnect();
         numBroadcastsSent += 2;
         verify(mAvrcpControllerService,
                 timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(numBroadcastsSent)).sendBroadcast(
-                mIntentArgument.capture(), eq(ProfileService.BLUETOOTH_PERM));
+                mIntentArgument.capture(), eq(BLUETOOTH_CONNECT),
+                any(Bundle.class));
         Assert.assertEquals(mTestDevice, mIntentArgument.getValue().getParcelableExtra(
                 BluetoothDevice.EXTRA_DEVICE));
         Assert.assertEquals(BluetoothAvrcpController.ACTION_CONNECTION_STATE_CHANGED,
@@ -169,10 +375,6 @@
                 IsInstanceOf.instanceOf(AvrcpControllerStateMachine.Disconnected.class));
         Assert.assertEquals(mAvrcpStateMachine.getState(), BluetoothProfile.STATE_DISCONNECTED);
         verify(mAvrcpControllerService).removeStateMachine(eq(mAvrcpStateMachine));
-        MediaControllerCompat.TransportControls transportControls =
-                BluetoothMediaBrowserService.getTransportControls();
-        Assert.assertEquals(PlaybackStateCompat.STATE_ERROR,
-                BluetoothMediaBrowserService.getPlaybackState());
     }
 
     /**
@@ -186,13 +388,12 @@
         Assert.assertNotNull(transportControls);
         Assert.assertEquals(PlaybackStateCompat.STATE_NONE,
                 BluetoothMediaBrowserService.getPlaybackState());
-        StackEvent event =
-                StackEvent.connectionStateChanged(false, false);
         mAvrcpStateMachine.disconnect();
         numBroadcastsSent += 2;
         verify(mAvrcpControllerService,
                 timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(numBroadcastsSent)).sendBroadcast(
-                mIntentArgument.capture(), eq(ProfileService.BLUETOOTH_PERM));
+                mIntentArgument.capture(), eq(BLUETOOTH_CONNECT),
+                any(Bundle.class));
         Assert.assertEquals(mTestDevice, mIntentArgument.getValue().getParcelableExtra(
                 BluetoothDevice.EXTRA_DEVICE));
         Assert.assertEquals(BluetoothAvrcpController.ACTION_CONNECTION_STATE_CHANGED,
@@ -203,8 +404,6 @@
                 IsInstanceOf.instanceOf(AvrcpControllerStateMachine.Disconnected.class));
         Assert.assertEquals(mAvrcpStateMachine.getState(), BluetoothProfile.STATE_DISCONNECTED);
         verify(mAvrcpControllerService).removeStateMachine(eq(mAvrcpStateMachine));
-        Assert.assertEquals(PlaybackStateCompat.STATE_ERROR,
-                BluetoothMediaBrowserService.getPlaybackState());
     }
 
     /**
@@ -218,13 +417,12 @@
         Assert.assertEquals(1, mAvrcpControllerService.sBrowseTree.mRootNode.getChildrenCount());
         Assert.assertEquals(PlaybackStateCompat.STATE_NONE,
                 BluetoothMediaBrowserService.getPlaybackState());
-        StackEvent event =
-                StackEvent.connectionStateChanged(false, false);
         mAvrcpStateMachine.disconnect();
         numBroadcastsSent += 2;
         verify(mAvrcpControllerService,
                 timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(numBroadcastsSent)).sendBroadcast(
-                mIntentArgument.capture(), eq(ProfileService.BLUETOOTH_PERM));
+                mIntentArgument.capture(), eq(BLUETOOTH_CONNECT),
+                any(Bundle.class));
         Assert.assertEquals(mTestDevice, mIntentArgument.getValue().getParcelableExtra(
                 BluetoothDevice.EXTRA_DEVICE));
         Assert.assertEquals(BluetoothAvrcpController.ACTION_CONNECTION_STATE_CHANGED,
@@ -235,10 +433,6 @@
                 IsInstanceOf.instanceOf(AvrcpControllerStateMachine.Disconnected.class));
         Assert.assertEquals(mAvrcpStateMachine.getState(), BluetoothProfile.STATE_DISCONNECTED);
         verify(mAvrcpControllerService).removeStateMachine(eq(mAvrcpStateMachine));
-        MediaControllerCompat.TransportControls transportControls =
-                BluetoothMediaBrowserService.getTransportControls();
-        Assert.assertEquals(PlaybackStateCompat.STATE_ERROR,
-                BluetoothMediaBrowserService.getPlaybackState());
     }
 
     /**
@@ -256,10 +450,7 @@
     public void testDump() {
         StringBuilder sb = new StringBuilder();
         mAvrcpStateMachine.dump(sb);
-        Assert.assertEquals(sb.toString(),
-                "  mDevice: " + mTestDevice.toString()
-                + "(null) name=AvrcpControllerStateMachine state=Disconnected\n"
-                + "  isActive: false\n");
+        Assert.assertNotNull(sb.toString());
     }
 
     /**
@@ -492,7 +683,7 @@
         //Provide back a player object
         byte[] playerFeatures =
                 new byte[]{0, 0, 0, 0, 0, (byte) 0xb7, 0x01, 0x0c, 0x0a, 0, 0, 0, 0, 0, 0, 0};
-        AvrcpPlayer playerOne = new AvrcpPlayer(mTestDevice, 1, playerName, playerFeatures, 1, 1);
+        AvrcpPlayer playerOne = makePlayer(mTestDevice, 1, playerName, 1, playerFeatures, 1);
         List<AvrcpPlayer> testPlayers = new ArrayList<>();
         testPlayers.add(playerOne);
         mAvrcpStateMachine.sendMessage(AvrcpControllerStateMachine.MESSAGE_PROCESS_GET_PLAYER_ITEMS,
@@ -524,61 +715,141 @@
     }
 
     /**
-     * Make an AvrcpItem suitable for being included in the Now Playing list for the test device
+     * Test our reaction to an available players changed event
+     *
+     * Verify that we issue a command to fetch the new available players
      */
-    private AvrcpItem makeNowPlayingItem(long uid, String name) {
-        AvrcpItem.Builder aib = new AvrcpItem.Builder();
-        aib.setDevice(mTestDevice);
-        aib.setItemType(AvrcpItem.TYPE_MEDIA);
-        aib.setType(AvrcpItem.MEDIA_AUDIO);
-        aib.setTitle(name);
-        aib.setUid(uid);
-        aib.setUuid(UUID.randomUUID().toString());
-        aib.setPlayable(true);
-        return aib.build();
+    @Test
+    public void testAvailablePlayersChanged() {
+        setUpConnectedState(true, true);
+        final String rootName = "__ROOT__";
+
+        // Send an available players have changed event
+        mAvrcpStateMachine.sendMessage(
+                AvrcpControllerStateMachine.MESSAGE_PROCESS_AVAILABLE_PLAYER_CHANGED);
+
+        // Verify we've uncached our browse root and made the call to fetch new players
+        Assert.assertFalse(mAvrcpStateMachine.findNode(rootName).isCached());
+        verify(mAvrcpControllerService,
+                timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(1)).getPlayerListNative(eq(mTestAddress),
+                eq(0), eq(19));
     }
 
     /**
-     * Get the current Now Playing list for the test device
+     * Test how we handle receiving an available players list that contains the player we know to
+     * be the addressed player
      */
-    private List<AvrcpItem> getNowPlayingList() {
-        BrowseTree.BrowseNode nowPlaying = mAvrcpStateMachine.findNode("NOW_PLAYING");
-        List<AvrcpItem> nowPlayingList = new ArrayList<AvrcpItem>();
-        for (BrowseTree.BrowseNode child : nowPlaying.getChildren()) {
-            nowPlayingList.add(child.mItem);
-        }
-        return nowPlayingList;
-    }
+    @Test
+    public void testAvailablePlayersReceived_AddressedPlayerExists() {
+        setUpConnectedState(true, true);
+        final String rootName = "__ROOT__";
 
-    /**
-     * Set the current Now Playing list for the test device
-     */
-    private void setNowPlayingList(List<AvrcpItem> nowPlayingList) {
-        BrowseTree.BrowseNode nowPlaying = mAvrcpStateMachine.findNode("NOW_PLAYING");
-        mAvrcpStateMachine.requestContents(nowPlaying);
+        // Set an addressed player that will be in the available players set
         mAvrcpStateMachine.sendMessage(
-                AvrcpControllerStateMachine.MESSAGE_PROCESS_GET_FOLDER_ITEMS, nowPlayingList);
-        mAvrcpStateMachine.sendMessage(
-                AvrcpControllerStateMachine.MESSAGE_PROCESS_GET_FOLDER_ITEMS_OUT_OF_RANGE);
+                AvrcpControllerStateMachine.MESSAGE_PROCESS_ADDRESSED_PLAYER_CHANGED, 1);
+        TestUtils.waitForLooperToFinishScheduledTask(mAvrcpStateMachine.getHandler().getLooper());
+        clearInvocations(mAvrcpControllerService);
 
-        // Wait for the now playing list to be propagated
+        // Send an available players have changed event
+        mAvrcpStateMachine.sendMessage(
+                AvrcpControllerStateMachine.MESSAGE_PROCESS_AVAILABLE_PLAYER_CHANGED);
+
+        // Verify we've uncached our browse root and made the call to fetch new players
+        Assert.assertFalse(mAvrcpStateMachine.findNode(rootName).isCached());
+        verify(mAvrcpControllerService,
+                timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(1)).getPlayerListNative(eq(mTestAddress),
+                eq(0), eq(19));
+
+        // Send available players set that contains our addressed player
+        byte[] playerFeatures =
+                new byte[]{0, 0, 0, 0, 0, (byte) 0xb7, 0x01, 0x0c, 0x0a, 0, 0, 0, 0, 0, 0, 0};
+        AvrcpPlayer playerOne = makePlayer(mTestDevice, 1, "Player 1", 1, playerFeatures, 1);
+        AvrcpPlayer playerTwo = makePlayer(mTestDevice, 2, "Player 2", 1, playerFeatures, 1);
+        List<AvrcpPlayer> testPlayers = new ArrayList<>();
+        testPlayers.add(playerOne);
+        testPlayers.add(playerTwo);
+        mAvrcpStateMachine.sendMessage(AvrcpControllerStateMachine.MESSAGE_PROCESS_GET_PLAYER_ITEMS,
+                testPlayers);
+
+        // Wait for them to be processed
         TestUtils.waitForLooperToFinishScheduledTask(mAvrcpStateMachine.getHandler().getLooper());
 
-        // Make sure its set by re grabbing the node and checking its contents are cached
-        nowPlaying = mAvrcpStateMachine.findNode("NOW_PLAYING");
-        Assert.assertTrue(nowPlaying.isCached());
-        assertNowPlayingList(nowPlayingList);
+        // Verify we processed the first players properly. Note the addressed player should always
+        // be in the available player set.
+        Assert.assertTrue(mAvrcpStateMachine.findNode(rootName).isCached());
+        SparseArray<AvrcpPlayer> players = mAvrcpStateMachine.getAvailablePlayers();
+        Assert.assertTrue(players.contains(mAvrcpStateMachine.getAddressedPlayerId()));
+        Assert.assertEquals(testPlayers.size(), players.size());
+        for (AvrcpPlayer player : testPlayers) {
+            Assert.assertTrue(players.contains(player.getId()));
+        }
+
+        // Verify we request metadata, playback state and now playing list
+        assertNowPlayingList(new ArrayList<AvrcpItem>());
+        verify(mAvrcpControllerService,
+                timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(1)).getNowPlayingListNative(
+                eq(mTestAddress), eq(0), eq(19));
+        verify(mAvrcpControllerService,
+                timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(1)).getCurrentMetadataNative(
+                eq(mTestAddress));
+        verify(mAvrcpControllerService,
+                timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(1)).getPlaybackStateNative(
+                eq(mTestAddress));
     }
 
     /**
-     * Assert that the Now Playing list is a particular value
+     * Test how we handle receiving an available players list that does not contain the player we
+     * know to be the addressed player
      */
-    private void assertNowPlayingList(List<AvrcpItem> expected) {
-        List<AvrcpItem> current = getNowPlayingList();
-        Assert.assertEquals(expected.size(), current.size());
-        for (int i = 0; i < expected.size(); i++) {
-            Assert.assertEquals(expected.get(i), current.get(i));
+    @Test
+    public void testAvailablePlayersReceived_AddressedPlayerDoesNotExist() {
+        setUpConnectedState(true, true);
+        final String rootName = "__ROOT__";
+
+        // Send an available players have changed event
+        mAvrcpStateMachine.sendMessage(
+                AvrcpControllerStateMachine.MESSAGE_PROCESS_AVAILABLE_PLAYER_CHANGED);
+
+        // Verify we've uncached our browse root and made the call to fetch new players
+        Assert.assertFalse(mAvrcpStateMachine.findNode(rootName).isCached());
+        verify(mAvrcpControllerService,
+                timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(1)).getPlayerListNative(eq(mTestAddress),
+                eq(0), eq(19));
+
+        // Send available players set that does not contain the addressed player
+        byte[] playerFeatures =
+                new byte[]{0, 0, 0, 0, 0, (byte) 0xb7, 0x01, 0x0c, 0x0a, 0, 0, 0, 0, 0, 0, 0};
+        AvrcpPlayer playerOne = makePlayer(mTestDevice, 1, "Player 1", 1, playerFeatures, 1);
+        AvrcpPlayer playerTwo = makePlayer(mTestDevice, 2, "Player 2", 1, playerFeatures, 1);
+        List<AvrcpPlayer> testPlayers = new ArrayList<>();
+        testPlayers.add(playerOne);
+        testPlayers.add(playerTwo);
+        mAvrcpStateMachine.sendMessage(AvrcpControllerStateMachine.MESSAGE_PROCESS_GET_PLAYER_ITEMS,
+                testPlayers);
+
+        // Wait for them to be processed
+        TestUtils.waitForLooperToFinishScheduledTask(mAvrcpStateMachine.getHandler().getLooper());
+
+        // Verify we processed the players properly. Note the addressed player is currently the
+        // default player and is not in the available player set sent. This means we'll have an
+        // extra player at ID -1.
+        Assert.assertTrue(mAvrcpStateMachine.findNode(rootName).isCached());
+        SparseArray<AvrcpPlayer> players = mAvrcpStateMachine.getAvailablePlayers();
+        Assert.assertTrue(players.contains(mAvrcpStateMachine.getAddressedPlayerId()));
+        Assert.assertEquals(testPlayers.size() + 1, players.size());
+        for (AvrcpPlayer player : testPlayers) {
+            Assert.assertTrue(players.contains(player.getId()));
         }
+
+        // Verify we do not request metadata, playback state and now playing list because we're
+        // sure the addressed player and metadata we have isn't impacted by the new players
+        verify(mAvrcpControllerService,
+                timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(0)).getNowPlayingListNative(
+                any(), anyInt(), anyInt());
+        verify(mAvrcpControllerService,
+                timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(0)).getCurrentMetadataNative(any());
+        verify(mAvrcpControllerService,
+                timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(0)).getPlaybackStateNative(any());
     }
 
     /**
@@ -586,7 +857,7 @@
      * Verify when the addressed media player changes browsing data updates
      */
     @Test
-    public void testPlayerChanged() {
+    public void testAddressedPlayerChangedToNewKnownPlayer() {
         setUpConnectedState(true, true);
         final String rootName = "__ROOT__";
 
@@ -604,8 +875,8 @@
         //Provide back two player objects, IDs 1 and 2
         byte[] playerFeatures =
                 new byte[]{0, 0, 0, 0, 0, (byte) 0xb7, 0x01, 0x0c, 0x0a, 0, 0, 0, 0, 0, 0, 0};
-        AvrcpPlayer playerOne = new AvrcpPlayer(mTestDevice, 1, "Player 1", playerFeatures, 1, 1);
-        AvrcpPlayer playerTwo = new AvrcpPlayer(mTestDevice, 2, "Player 2", playerFeatures, 1, 1);
+        AvrcpPlayer playerOne = makePlayer(mTestDevice, 1, "Player 1", 1, playerFeatures, 1);
+        AvrcpPlayer playerTwo = makePlayer(mTestDevice, 2, "Player 2", 1, playerFeatures, 1);
         List<AvrcpPlayer> testPlayers = new ArrayList<>();
         testPlayers.add(playerOne);
         testPlayers.add(playerTwo);
@@ -617,20 +888,33 @@
         nowPlayingList.add(makeNowPlayingItem(1, "Song 1"));
         nowPlayingList.add(makeNowPlayingItem(2, "Song 2"));
         setNowPlayingList(nowPlayingList);
+        clearInvocations(mAvrcpControllerService);
 
         //Change players and verify that BT attempts to update the results
         mAvrcpStateMachine.sendMessage(
                 AvrcpControllerStateMachine.MESSAGE_PROCESS_ADDRESSED_PLAYER_CHANGED, 2);
         TestUtils.waitForLooperToFinishScheduledTask(mAvrcpStateMachine.getHandler().getLooper());
 
+        // The addressed player should always be in the available player set
+        Assert.assertEquals(2, mAvrcpStateMachine.getAddressedPlayerId());
+        SparseArray<AvrcpPlayer> players = mAvrcpStateMachine.getAvailablePlayers();
+        Assert.assertTrue(players.contains(mAvrcpStateMachine.getAddressedPlayerId()));
+
         //Make sure the Now Playing list is now cleared
         assertNowPlayingList(new ArrayList<AvrcpItem>());
 
-        //Verify that a player change to a player with Now Playing support causes a refresh. This
-        //should be called twice, once to give data and once to ensure we're out of elements
+        //Verify that a player change to a player with Now Playing support causes a refresh.
         verify(mAvrcpControllerService,
-                timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(2)).getNowPlayingListNative(
+                timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(1)).getNowPlayingListNative(
                 eq(mTestAddress), eq(0), eq(19));
+
+        //Verify we request metadata and playback state
+        verify(mAvrcpControllerService,
+                timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(1)).getCurrentMetadataNative(
+                eq(mTestAddress));
+        verify(mAvrcpControllerService,
+                timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(1)).getPlaybackStateNative(
+                eq(mTestAddress));
     }
 
     /**
@@ -639,7 +923,7 @@
      * Verify that the contents of a player are fetched upon request
      */
     @Test
-    public void testPlayerChangedToUnknownPlayer() {
+    public void testAddressedPlayerChangedToUnknownPlayer() {
         setUpConnectedState(true, true);
         final String rootName = "__ROOT__";
 
@@ -650,14 +934,11 @@
         //Request fetch the list of players
         BrowseTree.BrowseNode playerNodes = mAvrcpStateMachine.findNode(rootNode.getID());
         mAvrcpStateMachine.requestContents(rootNode);
-        verify(mAvrcpControllerService,
-                timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(1)).getPlayerListNative(eq(mTestAddress),
-                eq(0), eq(19));
 
         //Provide back a player object
         byte[] playerFeatures =
                 new byte[]{0, 0, 0, 0, 0, (byte) 0xb7, 0x01, 0x0c, 0x0a, 0, 0, 0, 0, 0, 0, 0};
-        AvrcpPlayer playerOne = new AvrcpPlayer(mTestDevice, 1, "Player 1", playerFeatures, 1, 1);
+        AvrcpPlayer playerOne = makePlayer(mTestDevice, 1, "Player 1", 1, playerFeatures, 1);
         List<AvrcpPlayer> testPlayers = new ArrayList<>();
         testPlayers.add(playerOne);
         mAvrcpStateMachine.sendMessage(
@@ -674,12 +955,79 @@
                 AvrcpControllerStateMachine.MESSAGE_PROCESS_ADDRESSED_PLAYER_CHANGED, 4);
         TestUtils.waitForLooperToFinishScheduledTask(mAvrcpStateMachine.getHandler().getLooper());
 
-        //Make sure the Now Playing list is now cleared
+        //Make sure the Now Playing list is now cleared and we requested metadata
         assertNowPlayingList(new ArrayList<AvrcpItem>());
+        verify(mAvrcpControllerService,
+                timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(1)).getCurrentMetadataNative(
+                eq(mTestAddress));
+        verify(mAvrcpControllerService,
+                timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(1)).getPlaybackStateNative(
+                eq(mTestAddress));
+    }
 
-        //Make sure the root node is no longer cached
-        rootNode = mAvrcpStateMachine.findNode(rootName);
-        Assert.assertFalse(rootNode.isCached());
+    /**
+     * Test what we do when we receive an addressed player change to a player with the same ID as
+     * the current addressed play.
+     *
+     * Verify we assume nothing and re-fetch the current metadata and playback status.
+     */
+    @Test
+    public void testAddressedPlayerChangedToSamePlayerId() {
+        setUpConnectedState(true, true);
+        final String rootName = "__ROOT__";
+
+        // Set the addressed player so we can change to the same one
+        mAvrcpStateMachine.sendMessage(
+                AvrcpControllerStateMachine.MESSAGE_PROCESS_ADDRESSED_PLAYER_CHANGED, 1);
+
+        // Wait until idle so Now Playing List is queried for, resolve it
+        TestUtils.waitForLooperToBeIdle(mAvrcpStateMachine.getHandler().getLooper());
+        mAvrcpStateMachine.sendMessage(
+                AvrcpControllerStateMachine.MESSAGE_PROCESS_GET_FOLDER_ITEMS_OUT_OF_RANGE);
+        TestUtils.waitForLooperToFinishScheduledTask(mAvrcpStateMachine.getHandler().getLooper());
+
+        //Get the root of the device
+        BrowseTree.BrowseNode rootNode = mAvrcpStateMachine.findNode(rootName);
+        Assert.assertEquals(rootName + mTestDevice.toString(), rootNode.getID());
+
+        //Request fetch the list of players
+        BrowseTree.BrowseNode playerNodes = mAvrcpStateMachine.findNode(rootNode.getID());
+        mAvrcpStateMachine.requestContents(rootNode);
+
+        // Send available players set that contains our addressed player
+        byte[] playerFeatures =
+                new byte[]{0, 0, 0, 0, 0, (byte) 0xb7, 0x01, 0x0c, 0x0a, 0, 0, 0, 0, 0, 0, 0};
+        AvrcpPlayer playerOne = makePlayer(mTestDevice, 1, "Player 1", 1, playerFeatures, 1);
+        AvrcpPlayer playerTwo = makePlayer(mTestDevice, 2, "Player 2", 1, playerFeatures, 1);
+        List<AvrcpPlayer> testPlayers = new ArrayList<>();
+        testPlayers.add(playerOne);
+        testPlayers.add(playerTwo);
+        mAvrcpStateMachine.sendMessage(AvrcpControllerStateMachine.MESSAGE_PROCESS_GET_PLAYER_ITEMS,
+                testPlayers);
+
+        // Wait until idle so Now Playing List is queried for again, resolve it again
+        TestUtils.waitForLooperToBeIdle(mAvrcpStateMachine.getHandler().getLooper());
+        mAvrcpStateMachine.sendMessage(
+                AvrcpControllerStateMachine.MESSAGE_PROCESS_GET_FOLDER_ITEMS_OUT_OF_RANGE);
+        TestUtils.waitForLooperToFinishScheduledTask(mAvrcpStateMachine.getHandler().getLooper());
+        clearInvocations(mAvrcpControllerService);
+
+        // Send an addressed player changed to the same player ID
+        mAvrcpStateMachine.sendMessage(
+                AvrcpControllerStateMachine.MESSAGE_PROCESS_ADDRESSED_PLAYER_CHANGED, 1);
+        TestUtils.waitForLooperToBeIdle(mAvrcpStateMachine.getHandler().getLooper());
+
+        // Verify we make no assumptions about the player ID and still fetch metadata, play status
+        // and now playing list (since player 1 supports it)
+        verify(mAvrcpControllerService,
+                timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(1)).getNowPlayingListNative(
+                eq(mTestAddress), eq(0), eq(19));
+        verify(mAvrcpControllerService,
+                timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(1)).getCurrentMetadataNative(
+                eq(mTestAddress));
+        verify(mAvrcpControllerService,
+                timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(1)).getPlaybackStateNative(
+                eq(mTestAddress));
     }
 
     /**
@@ -809,109 +1157,210 @@
     }
 
     /**
-     * Test receiving a playback status of playing from a device that isn't active
-     *
-     * Verify we do not send a pause command and never attempt to request audio focus
+     * Test that isActive() reports the proper value when we're active
      */
     @Test
-    public void testPlaybackWhileNotActiveDevice() {
-        byte[] secondTestAddress = new byte[]{00, 01, 02, 03, 04, 06};
-        BluetoothDevice secondTestDevice = mAdapter.getRemoteDevice(secondTestAddress);
-        AvrcpControllerStateMachine secondAvrcpStateMachine =
-                new AvrcpControllerStateMachine(secondTestDevice, mAvrcpControllerService);
-        secondAvrcpStateMachine.start();
+    public void testIsActive_deviceActive() {
+        Assert.assertTrue(mAvrcpStateMachine.isActive());
+    }
 
+    /**
+     * Test that isActive() reports the proper value when we're inactive
+     */
+    @Test
+    public void testIsActive_deviceInactive() {
+        setActiveDevice(null);
+        Assert.assertFalse(mAvrcpStateMachine.isActive());
+    }
+
+    /**
+     * Test becoming active from the inactive state
+     */
+    @Test
+    public void testBecomeActive() {
+        // Note device starts as active in setUp() and state cascades come the CONNECTED state
         setUpConnectedState(true, true);
-        secondAvrcpStateMachine.connect(StackEvent.connectionStateChanged(true, true));
-        TestUtils.waitForLooperToFinishScheduledTask(secondAvrcpStateMachine.getHandler()
-                .getLooper());
+        Assert.assertTrue(mAvrcpStateMachine.isActive());
 
-        Assert.assertTrue(secondAvrcpStateMachine.isActive());
+        // Make the device inactive
+        setActiveDevice(null);
+        TestUtils.waitForLooperToFinishScheduledTask(mAvrcpStateMachine.getHandler().getLooper());
         Assert.assertFalse(mAvrcpStateMachine.isActive());
 
-        mAvrcpStateMachine.sendMessage(
-                AvrcpControllerStateMachine.MESSAGE_PROCESS_PLAY_STATUS_CHANGED,
-                PlaybackStateCompat.STATE_PLAYING);
+        // Change device state while inactive
+        AvrcpItem track = makeTrack("title", "artist", "album", 1, 10, "none", 10, null);
+        List<AvrcpItem> nowPlayingList = new ArrayList<AvrcpItem>();
+        AvrcpItem queueItem1 = makeNowPlayingItem(0, "title");
+        AvrcpItem queueItem2 = makeNowPlayingItem(1, "title 2");
+        nowPlayingList.add(queueItem1);
+        nowPlayingList.add(queueItem2);
+        setCurrentTrack(track);
+        setPlaybackState(PlaybackStateCompat.STATE_PAUSED);
+        setPlaybackPosition(7, 10);
+        setNowPlayingList(nowPlayingList);
+
+        // Make device active
+        setActiveDevice(mTestDevice);
+        TestUtils.waitForLooperToFinishScheduledTask(mAvrcpStateMachine.getHandler().getLooper());
+        Assert.assertTrue(mAvrcpStateMachine.isActive());
+
+        // See that state from BluetoothMediaBrowserService is updated
+        MediaSessionCompat session = BluetoothMediaBrowserService.getSession();
+        Assert.assertNotNull(session);
+        MediaControllerCompat controller = session.getController();
+        Assert.assertNotNull(controller);
+
+        MediaMetadataCompat metadata = controller.getMetadata();
+        Assert.assertNotNull(metadata);
+        Assert.assertEquals("title", metadata.getString(MediaMetadataCompat.METADATA_KEY_TITLE));
+        Assert.assertEquals("artist", metadata.getString(MediaMetadataCompat.METADATA_KEY_ARTIST));
+        Assert.assertEquals("album", metadata.getString(MediaMetadataCompat.METADATA_KEY_ALBUM));
+        Assert.assertEquals(1, metadata.getLong(MediaMetadataCompat.METADATA_KEY_TRACK_NUMBER));
+        Assert.assertEquals(10, metadata.getLong(MediaMetadataCompat.METADATA_KEY_NUM_TRACKS));
+        Assert.assertEquals("none", metadata.getString(MediaMetadataCompat.METADATA_KEY_GENRE));
+        Assert.assertEquals(10, metadata.getLong(MediaMetadataCompat.METADATA_KEY_DURATION));
+
+        PlaybackStateCompat playbackState = controller.getPlaybackState();
+        Assert.assertNotNull(playbackState);
+        Assert.assertEquals(PlaybackStateCompat.STATE_PAUSED, playbackState.getState());
+        Assert.assertEquals(7, playbackState.getPosition());
+
+        List<MediaSessionCompat.QueueItem> queue = controller.getQueue();
+        Assert.assertNotNull(queue);
+        Assert.assertEquals(2, queue.size());
+        Assert.assertEquals("title", queue.get(0).getDescription().getTitle().toString());
+        Assert.assertEquals("title 2", queue.get(1).getDescription().getTitle().toString());
+    }
+
+    /**
+     * Test becoming inactive from the active state
+     */
+    @Test
+    public void testBecomeInactive() {
+        // Note device starts as active in setUp()
+        setUpConnectedState(true, true);
+        Assert.assertTrue(mAvrcpStateMachine.isActive());
+
+        // Set the active device to something else, verify we're inactive and send a pause upon
+        // becoming inactive
+        setActiveDevice(null);
         TestUtils.waitForLooperToFinishScheduledTask(mAvrcpStateMachine.getHandler().getLooper());
         verify(mAvrcpControllerService,
                 timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(1)).sendPassThroughCommandNative(
                 eq(mTestAddress), eq(AvrcpControllerService.PASS_THRU_CMD_ID_PAUSE), eq(KEY_DOWN));
-        verify(mA2dpSinkService, never()).requestAudioFocus(mTestDevice, true);
-
-        secondAvrcpStateMachine.disconnect();
-        TestUtils.waitForLooperToFinishScheduledTask(secondAvrcpStateMachine.getHandler()
-                .getLooper());
-        Assert.assertFalse(secondAvrcpStateMachine.isActive());
         Assert.assertFalse(mAvrcpStateMachine.isActive());
     }
 
     /**
-     * Test that the correct device becomes active
-     *
-     * The first connected device is automatically active, additional ones are not.
-     * After an explicit play command a device becomes active.
+     * Test receiving a track change update when we're not the active device
      */
     @Test
-    public void testActiveDeviceManagement() {
-        // Setup structures and verify initial conditions
-        final String rootName = "__ROOT__";
-        final String playerName = "Player 1";
-        byte[] secondTestAddress = new byte[]{00, 01, 02, 03, 04, 06};
-        BluetoothDevice secondTestDevice = mAdapter.getRemoteDevice(secondTestAddress);
-        AvrcpControllerStateMachine secondAvrcpStateMachine =
-                new AvrcpControllerStateMachine(secondTestDevice, mAvrcpControllerService);
-        secondAvrcpStateMachine.start();
-        Assert.assertFalse(mAvrcpStateMachine.isActive());
-
-        // Connect device 1 and 2 and verify second one is set as active
+    public void testTrackChangeWhileNotActiveDevice() {
         setUpConnectedState(true, true);
+
+        // Set the active device to something else, verify we're inactive and send a pause upon
+        // becoming inactive
+        setActiveDevice(null);
         TestUtils.waitForLooperToFinishScheduledTask(mAvrcpStateMachine.getHandler().getLooper());
-        Assert.assertTrue(mAvrcpStateMachine.isActive());
-
-        secondAvrcpStateMachine.connect(StackEvent.connectionStateChanged(true, true));
-        TestUtils.waitForLooperToFinishScheduledTask(mAvrcpStateMachine.getHandler().getLooper());
-        TestUtils.waitForLooperToFinishScheduledTask(secondAvrcpStateMachine.getHandler()
-                .getLooper());
-
         Assert.assertFalse(mAvrcpStateMachine.isActive());
-        Assert.assertTrue(secondAvrcpStateMachine.isActive());
 
-        // Request the second device to play an item and verify active device switched
-        BrowseTree.BrowseNode results = mAvrcpStateMachine.findNode(rootName);
-        Assert.assertEquals(rootName + mTestDevice.toString(), results.getID());
-        BrowseTree.BrowseNode playerNodes = mAvrcpStateMachine.findNode(results.getID());
-        secondAvrcpStateMachine.playItem(playerNodes);
-        TestUtils.waitForLooperToFinishScheduledTask(secondAvrcpStateMachine.getHandler()
-                .getLooper());
-        Assert.assertFalse(mAvrcpStateMachine.isActive());
-        Assert.assertTrue(secondAvrcpStateMachine.isActive());
+        // Change track while inactive
+        AvrcpItem track = makeTrack("title", "artist", "album", 1, 10, "none", 10, null);
+        setCurrentTrack(track);
 
-        secondAvrcpStateMachine.disconnect();
-        TestUtils.waitForLooperToFinishScheduledTask(secondAvrcpStateMachine.getHandler()
-                .getLooper());
-        Assert.assertFalse(secondAvrcpStateMachine.isActive());
-        Assert.assertFalse(mAvrcpStateMachine.isActive());
+        // Since we're not active, verify BluetoothMediaBrowserService does not have these values
+        MediaSessionCompat session = BluetoothMediaBrowserService.getSession();
+        Assert.assertNotNull(session);
+        MediaControllerCompat controller = session.getController();
+        Assert.assertNotNull(controller);
+
+        MediaMetadataCompat metadata = controller.getMetadata();
+        Assert.assertNull(metadata); // track starts as null and shouldn't change
     }
 
     /**
-     * Setup Connected State
-     *
-     * @return number of times mAvrcpControllerService.sendBroadcastAsUser() has been invoked
+     * Test receiving a playback status of playing when we're not the active device
      */
-    private int setUpConnectedState(boolean control, boolean browsing) {
-        // Put test state machine into connected state
-        Assert.assertThat(mAvrcpStateMachine.getCurrentState(),
-                IsInstanceOf.instanceOf(AvrcpControllerStateMachine.Disconnected.class));
+    @Test
+    public void testPlaybackWhileNotActiveDevice() {
+        setUpConnectedState(true, true);
 
-        mAvrcpStateMachine.connect(StackEvent.connectionStateChanged(control, browsing));
+        // Set the active device to something else, verify we're inactive
+        setActiveDevice(null);
         TestUtils.waitForLooperToFinishScheduledTask(mAvrcpStateMachine.getHandler().getLooper());
-        verify(mAvrcpControllerService, timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(2)).sendBroadcast(
-                mIntentArgument.capture(), eq(ProfileService.BLUETOOTH_PERM));
-        Assert.assertThat(mAvrcpStateMachine.getCurrentState(),
-                IsInstanceOf.instanceOf(AvrcpControllerStateMachine.Connected.class));
-        Assert.assertEquals(mAvrcpStateMachine.getState(), BluetoothProfile.STATE_CONNECTED);
+        Assert.assertFalse(mAvrcpStateMachine.isActive());
+        clearInvocations(mAvrcpControllerService);
 
-        return BluetoothProfile.STATE_CONNECTED;
+        // Now that we're inactive, receive a playback status of playing
+        setPlaybackState(PlaybackStateCompat.STATE_PLAYING);
+
+        // Verify we send a pause, never request audio focus, and the playback state on
+        // BluetoothMediaBrowserService never updates.
+        verify(mAvrcpControllerService,
+                timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(1)).sendPassThroughCommandNative(
+                eq(mTestAddress), eq(AvrcpControllerService.PASS_THRU_CMD_ID_PAUSE), eq(KEY_DOWN));
+        verify(mA2dpSinkService, never()).requestAudioFocus(mTestDevice, true);
+        Assert.assertEquals(PlaybackStateCompat.STATE_ERROR,
+                BluetoothMediaBrowserService.getPlaybackState());
     }
 
+    /**
+     * Test receiving a play position update when we're not the active device
+     */
+    @Test
+    public void testPlayPositionChangeWhileNotActiveDevice() {
+        setUpConnectedState(true, true);
+
+        // Set the active device to something else, verify we're inactive
+        setActiveDevice(null);
+        TestUtils.waitForLooperToFinishScheduledTask(mAvrcpStateMachine.getHandler().getLooper());
+        Assert.assertFalse(mAvrcpStateMachine.isActive());
+        clearInvocations(mAvrcpControllerService);
+
+        // Now that we're inactive, receive a play position change
+        setPlaybackPosition(1, 10);
+
+        // Since we're not active, verify BluetoothMediaBrowserService does not have these values
+        MediaSessionCompat session = BluetoothMediaBrowserService.getSession();
+        Assert.assertNotNull(session);
+        MediaControllerCompat controller = session.getController();
+        Assert.assertNotNull(controller);
+
+        PlaybackStateCompat playbackState = controller.getPlaybackState();
+        Assert.assertNotNull(playbackState);
+        Assert.assertEquals(0, playbackState.getPosition());
+    }
+
+    /**
+     * Test receiving a now playing list update when we're not the active device
+     */
+    @Test
+    public void testNowPlayingListChangeWhileNotActiveDevice() {
+        setUpConnectedState(true, true);
+
+        // Set the active device to something else, verify we're inactive and send a pause upon
+        // becoming inactive
+        setActiveDevice(null);
+        TestUtils.waitForLooperToFinishScheduledTask(mAvrcpStateMachine.getHandler().getLooper());
+        Assert.assertFalse(mAvrcpStateMachine.isActive());
+
+        // Change queue while inactive
+        List<AvrcpItem> nowPlayingList = new ArrayList<AvrcpItem>();
+        AvrcpItem queueItem1 = makeNowPlayingItem(0, "title");
+        AvrcpItem queueItem2 = makeNowPlayingItem(1, "title 2");
+        AvrcpItem queueItem3 = makeNowPlayingItem(1, "title 3");
+        nowPlayingList.add(queueItem1);
+        nowPlayingList.add(queueItem2);
+        nowPlayingList.add(queueItem3);
+        setNowPlayingList(nowPlayingList);
+
+        // Since we're not active, verify BluetoothMediaBrowserService does not have these values
+        MediaSessionCompat session = BluetoothMediaBrowserService.getSession();
+        Assert.assertNotNull(session);
+        MediaControllerCompat controller = session.getController();
+        Assert.assertNotNull(controller);
+
+        List<MediaSessionCompat.QueueItem> queue = controller.getQueue();
+        Assert.assertNull(queue);
+    }
 }
diff --git a/tests/unit/src/com/android/bluetooth/avrcpcontroller/AvrcpItemTest.java b/tests/unit/src/com/android/bluetooth/avrcpcontroller/AvrcpItemTest.java
index b6aacf6..eb1421e 100644
--- a/tests/unit/src/com/android/bluetooth/avrcpcontroller/AvrcpItemTest.java
+++ b/tests/unit/src/com/android/bluetooth/avrcpcontroller/AvrcpItemTest.java
@@ -72,7 +72,7 @@
         long totalTracks = 12;
         String genre = "Viking Metal";
         long playingTime = 301;
-        String artHandle = "abc123";
+        String artHandle = "0000001";
         Uri uri = Uri.parse("content://somewhere");
         Uri uri2 = Uri.parse("content://somewhereelse");
 
@@ -119,7 +119,7 @@
         String totalTracks = "12";
         String genre = "Viking Metal";
         String playingTime = "301";
-        String artHandle = "abc123";
+        String artHandle = "0000001";
 
         int[] attrIds = new int[]{
             MEDIA_ATTRIBUTE_TITLE,
@@ -171,7 +171,7 @@
         String totalTracks = "12";
         String genre = "Viking Metal";
         String playingTime = "301";
-        String artHandle = "abc123";
+        String artHandle = "0000001";
 
         int[] attrIds = new int[]{
             MEDIA_ATTRIBUTE_TITLE,
@@ -223,6 +223,214 @@
     }
 
     @Test
+    public void buildAvrcpItemFromAvrcpAttributes_imageHandleTooShort() {
+        String title = "Aaaaargh";
+        String artist = "Bluetooth";
+        String album = "The Best Protocol";
+        String trackNumber = "1";
+        String totalTracks = "12";
+        String genre = "Viking Metal";
+        String playingTime = "301";
+        String artHandle = "000001"; // length 6 and not 7
+
+        int[] attrIds = new int[]{
+            MEDIA_ATTRIBUTE_TITLE,
+            MEDIA_ATTRIBUTE_ARTIST_NAME,
+            MEDIA_ATTRIBUTE_ALBUM_NAME,
+            MEDIA_ATTRIBUTE_TRACK_NUMBER,
+            MEDIA_ATTRIBUTE_TOTAL_TRACK_NUMBER,
+            MEDIA_ATTRIBUTE_GENRE,
+            MEDIA_ATTRIBUTE_PLAYING_TIME,
+            MEDIA_ATTRIBUTE_COVER_ART_HANDLE
+        };
+
+        String[] attrMap = new String[]{
+            title,
+            artist,
+            album,
+            trackNumber,
+            totalTracks,
+            genre,
+            playingTime,
+            artHandle
+        };
+
+        AvrcpItem.Builder builder = new AvrcpItem.Builder();
+        builder.fromAvrcpAttributeArray(attrIds, attrMap);
+        AvrcpItem item = builder.build();
+
+        Assert.assertEquals(null, item.getDevice());
+        Assert.assertEquals(false, item.isPlayable());
+        Assert.assertEquals(false, item.isBrowsable());
+        Assert.assertEquals(0, item.getUid());
+        Assert.assertEquals(null, item.getUuid());
+        Assert.assertEquals(null, item.getDisplayableName());
+        Assert.assertEquals(title, item.getTitle());
+        Assert.assertEquals(artist, item.getArtistName());
+        Assert.assertEquals(album, item.getAlbumName());
+        Assert.assertEquals(1, item.getTrackNumber());
+        Assert.assertEquals(12, item.getTotalNumberOfTracks());
+        Assert.assertEquals(null, item.getCoverArtHandle());
+        Assert.assertEquals(null, item.getCoverArtLocation());
+    }
+
+    @Test
+    public void buildAvrcpItemFromAvrcpAttributes_imageHandleEmpty() {
+        String title = "Aaaaargh";
+        String artist = "Bluetooth";
+        String album = "The Best Protocol";
+        String trackNumber = "1";
+        String totalTracks = "12";
+        String genre = "Viking Metal";
+        String playingTime = "301";
+        String artHandle = "";
+
+        int[] attrIds = new int[]{
+            MEDIA_ATTRIBUTE_TITLE,
+            MEDIA_ATTRIBUTE_ARTIST_NAME,
+            MEDIA_ATTRIBUTE_ALBUM_NAME,
+            MEDIA_ATTRIBUTE_TRACK_NUMBER,
+            MEDIA_ATTRIBUTE_TOTAL_TRACK_NUMBER,
+            MEDIA_ATTRIBUTE_GENRE,
+            MEDIA_ATTRIBUTE_PLAYING_TIME,
+            MEDIA_ATTRIBUTE_COVER_ART_HANDLE
+        };
+
+        String[] attrMap = new String[]{
+            title,
+            artist,
+            album,
+            trackNumber,
+            totalTracks,
+            genre,
+            playingTime,
+            artHandle
+        };
+
+        AvrcpItem.Builder builder = new AvrcpItem.Builder();
+        builder.fromAvrcpAttributeArray(attrIds, attrMap);
+        AvrcpItem item = builder.build();
+
+        Assert.assertEquals(null, item.getDevice());
+        Assert.assertEquals(false, item.isPlayable());
+        Assert.assertEquals(false, item.isBrowsable());
+        Assert.assertEquals(0, item.getUid());
+        Assert.assertEquals(null, item.getUuid());
+        Assert.assertEquals(null, item.getDisplayableName());
+        Assert.assertEquals(title, item.getTitle());
+        Assert.assertEquals(artist, item.getArtistName());
+        Assert.assertEquals(album, item.getAlbumName());
+        Assert.assertEquals(1, item.getTrackNumber());
+        Assert.assertEquals(12, item.getTotalNumberOfTracks());
+        Assert.assertEquals(null, item.getCoverArtHandle());
+        Assert.assertEquals(null, item.getCoverArtLocation());
+    }
+
+    @Test
+    public void buildAvrcpItemFromAvrcpAttributes_imageHandleNull() {
+        String title = "Aaaaargh";
+        String artist = "Bluetooth";
+        String album = "The Best Protocol";
+        String trackNumber = "1";
+        String totalTracks = "12";
+        String genre = "Viking Metal";
+        String playingTime = "301";
+        String artHandle = null;
+
+        int[] attrIds = new int[]{
+            MEDIA_ATTRIBUTE_TITLE,
+            MEDIA_ATTRIBUTE_ARTIST_NAME,
+            MEDIA_ATTRIBUTE_ALBUM_NAME,
+            MEDIA_ATTRIBUTE_TRACK_NUMBER,
+            MEDIA_ATTRIBUTE_TOTAL_TRACK_NUMBER,
+            MEDIA_ATTRIBUTE_GENRE,
+            MEDIA_ATTRIBUTE_PLAYING_TIME,
+            MEDIA_ATTRIBUTE_COVER_ART_HANDLE
+        };
+
+        String[] attrMap = new String[]{
+            title,
+            artist,
+            album,
+            trackNumber,
+            totalTracks,
+            genre,
+            playingTime,
+            artHandle
+        };
+
+        AvrcpItem.Builder builder = new AvrcpItem.Builder();
+        builder.fromAvrcpAttributeArray(attrIds, attrMap);
+        AvrcpItem item = builder.build();
+
+        Assert.assertEquals(null, item.getDevice());
+        Assert.assertEquals(false, item.isPlayable());
+        Assert.assertEquals(false, item.isBrowsable());
+        Assert.assertEquals(0, item.getUid());
+        Assert.assertEquals(null, item.getUuid());
+        Assert.assertEquals(null, item.getDisplayableName());
+        Assert.assertEquals(title, item.getTitle());
+        Assert.assertEquals(artist, item.getArtistName());
+        Assert.assertEquals(album, item.getAlbumName());
+        Assert.assertEquals(1, item.getTrackNumber());
+        Assert.assertEquals(12, item.getTotalNumberOfTracks());
+        Assert.assertEquals(null, item.getCoverArtHandle());
+        Assert.assertEquals(null, item.getCoverArtLocation());
+    }
+
+    @Test
+    public void buildAvrcpItemFromAvrcpAttributes_imageHandleNotDigits() {
+        String title = "Aaaaargh";
+        String artist = "Bluetooth";
+        String album = "The Best Protocol";
+        String trackNumber = "1";
+        String totalTracks = "12";
+        String genre = "Viking Metal";
+        String playingTime = "301";
+        String artHandle = "123abcd";
+
+        int[] attrIds = new int[]{
+            MEDIA_ATTRIBUTE_TITLE,
+            MEDIA_ATTRIBUTE_ARTIST_NAME,
+            MEDIA_ATTRIBUTE_ALBUM_NAME,
+            MEDIA_ATTRIBUTE_TRACK_NUMBER,
+            MEDIA_ATTRIBUTE_TOTAL_TRACK_NUMBER,
+            MEDIA_ATTRIBUTE_GENRE,
+            MEDIA_ATTRIBUTE_PLAYING_TIME,
+            MEDIA_ATTRIBUTE_COVER_ART_HANDLE
+        };
+
+        String[] attrMap = new String[]{
+            title,
+            artist,
+            album,
+            trackNumber,
+            totalTracks,
+            genre,
+            playingTime,
+            artHandle
+        };
+
+        AvrcpItem.Builder builder = new AvrcpItem.Builder();
+        builder.fromAvrcpAttributeArray(attrIds, attrMap);
+        AvrcpItem item = builder.build();
+
+        Assert.assertEquals(null, item.getDevice());
+        Assert.assertEquals(false, item.isPlayable());
+        Assert.assertEquals(false, item.isBrowsable());
+        Assert.assertEquals(0, item.getUid());
+        Assert.assertEquals(null, item.getUuid());
+        Assert.assertEquals(null, item.getDisplayableName());
+        Assert.assertEquals(title, item.getTitle());
+        Assert.assertEquals(artist, item.getArtistName());
+        Assert.assertEquals(album, item.getAlbumName());
+        Assert.assertEquals(1, item.getTrackNumber());
+        Assert.assertEquals(12, item.getTotalNumberOfTracks());
+        Assert.assertEquals(null, item.getCoverArtHandle());
+        Assert.assertEquals(null, item.getCoverArtLocation());
+    }
+
+    @Test
     public void updateCoverArtLocation() {
         Uri uri = Uri.parse("content://somewhere");
         Uri uri2 = Uri.parse("content://somewhereelse");
@@ -246,7 +454,7 @@
         long totalTracks = 12;
         String genre = "Viking Metal";
         long playingTime = 301;
-        String artHandle = "abc123";
+        String artHandle = "0000001";
         Uri uri = Uri.parse("content://somewhere");
 
         AvrcpItem.Builder builder = new AvrcpItem.Builder();
@@ -303,7 +511,7 @@
         long totalTracks = 12;
         String genre = "Viking Metal";
         long playingTime = 301;
-        String artHandle = "abc123";
+        String artHandle = "0000001";
         Uri uri = Uri.parse("content://somewhere");
         int type = AvrcpItem.FOLDER_TITLES;
 
@@ -371,7 +579,7 @@
         Assert.assertEquals(UUID, desc.getMediaId());
         Assert.assertEquals(null, desc.getMediaUri());
         Assert.assertEquals(title, desc.getTitle().toString());
-        Assert.assertEquals(null, desc.getSubtitle());
+        Assert.assertEquals(desc.getSubtitle(), null);
         Assert.assertEquals(uri, desc.getIconUri());
         Assert.assertEquals(null, desc.getIconBitmap());
     }
@@ -400,7 +608,7 @@
         Assert.assertEquals(UUID, desc.getMediaId());
         Assert.assertEquals(null, desc.getMediaUri());
         Assert.assertEquals(displayName, desc.getTitle().toString());
-        Assert.assertEquals(null, desc.getSubtitle());
+        Assert.assertEquals(desc.getSubtitle(), null);
         Assert.assertEquals(uri, desc.getIconUri());
         Assert.assertEquals(null, desc.getIconBitmap());
     }
@@ -427,7 +635,7 @@
         Assert.assertEquals(UUID, desc.getMediaId());
         Assert.assertEquals(null, desc.getMediaUri());
         Assert.assertEquals(title, desc.getTitle().toString());
-        Assert.assertEquals(null, desc.getSubtitle());
+        Assert.assertEquals(desc.getSubtitle(), null);
         Assert.assertEquals(uri, desc.getIconUri());
         Assert.assertEquals(null, desc.getIconBitmap());
     }
diff --git a/tests/unit/src/com/android/bluetooth/avrcpcontroller/bip/BipImagePropertiesTest.java b/tests/unit/src/com/android/bluetooth/avrcpcontroller/bip/BipImagePropertiesTest.java
index 5497df3..45d5ed9 100644
--- a/tests/unit/src/com/android/bluetooth/avrcpcontroller/bip/BipImagePropertiesTest.java
+++ b/tests/unit/src/com/android/bluetooth/avrcpcontroller/bip/BipImagePropertiesTest.java
@@ -31,20 +31,72 @@
  */
 @RunWith(AndroidJUnit4.class)
 public class BipImagePropertiesTest {
-    private static String sImageHandle = "123456789";
-    private static final String sXmlDocDecl =
+    private static final String IMAGE_HANDLE = "123456789";
+    private static final String FRIENDLY_NAME = "current-track.jpeg";
+    private static final String VERSION = "1.0";
+    private static final String XML_DOC_DECL =
             "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\n";
-    private static String sXmlString = sXmlDocDecl
-            + "<image-properties version=\"1.0\" handle=\"123456789\">\n"
-            + "    <native encoding=\"JPEG\" pixel=\"1280*1024\" size=\"1048576\" />\n"
-            + "    <variant encoding=\"JPEG\" pixel=\"640*480\" />\n"
-            + "    <variant encoding=\"GIF\" pixel=\"80*60-640*480\" "
-            + "transformation=\"stretch fill crop\" />\n"
-            + "    <variant encoding=\"JPEG\" pixel=\"150**-600*120\" />\n"
-            + "    <attachment content-type=\"text/plain\" name=\"ABCD1234.txt\" size=\"5120\" />\n"
-            + "    <attachment content-type=\"audio/basic\" name=\"ABCD1234.wav\" size=\"102400\" "
-            + "/>\n"
-            + "</image-properties>\n";
+
+    // An image-properties tag with all available attributes
+    private static final String IMAGE_PROPERTIES =
+            "<image-properties version=\"" + VERSION + "\" handle=\"" + IMAGE_HANDLE
+            + "\" friendly-name=\"" + FRIENDLY_NAME + "\">\n";
+
+    // An image-properties tag without an xml version - OUT OF SPEC / INVALID
+    private static final String IMAGE_PROPERTIES_NO_VERSION =
+            "<image-properties handle=\"" + IMAGE_HANDLE + "\" friendly-name=\""
+            + FRIENDLY_NAME + "\">\n";
+
+    // An image-properties tag without an image handle - OUT OF SPEC / INVALID
+    private static final String IMAGE_PROPERTIES_NO_HANDLE =
+            "<image-properties version=\"" + VERSION + "\" friendly-name=\"" + FRIENDLY_NAME
+            + "\">\n";
+
+    // An image-properties tag without an xml version - IN SPEC / VALID
+    private static final String IMAGE_PROPERTIES_NO_FRIENDLY_NAME =
+            "<image-properties version=\"" + VERSION + "\" handle=\"" + IMAGE_HANDLE + "\">\n";
+
+    // A native format representing the unaltered image available. Has a basic pixel and size
+    private static final String NATIVE_FORMAT =
+            "    <native encoding=\"JPEG\" pixel=\"1280*1024\" size=\"1048576\" />\n";
+
+    // A native format representation of the imaging thumbnail format
+    private static final String NATIVE_THUMBNAIL_FORMAT =
+            "    <native encoding=\"JPEG\" pixel=\"200*200\" />\n";
+
+    // A variant format representing a static altered image type. Has a basic pixel and no size
+    private static final String VARIANT_FIXED_FORMAT =
+            "    <variant encoding=\"JPEG\" pixel=\"640*480\" />\n";
+
+    // A variant format representing an range of sizes available. Has transformations and no size
+    private static final String VARIANT_RANGE_FORMAT =
+            "    <variant encoding=\"GIF\" pixel=\"80*60-640*175\" "
+            + "transformation=\"stretch fill crop\" />\n";
+
+    // A variant format representing a range of sizes within a fixed aspect ratio.
+    private static final String VARIANT_FIXED_RANGE_FORMAT =
+            "    <variant encoding=\"JPEG\" pixel=\"150**-600*120\" />\n";
+
+    // A fixed variant format representation of the imaging thumbnail format
+    private static final String VARIANT_FIXED_THUMBNAIL_FORMAT =
+            "    <variant encoding=\"JPEG\" pixel=\"200*200\" />\n";
+
+    // A resizable modifiable aspect ratio variant format containing the imaging thumbnail format
+    private static final String VARIANT_RANGE_THUMBNAIL_FORMAT =
+            "    <variant encoding=\"JPEG\" pixel=\"80*60-640*480\" />\n";
+
+    // A resizable fixed variant format containing the imaging thumbnail format
+    private static final String VARIANT_FIXED_RANGE_THUMBNAIL_FORMAT =
+            "    <variant encoding=\"JPEG\" pixel=\"150**-600*600\" />\n";
+
+    // Though not in the specification, we should be robust to attachments of various formats
+    private static final String ATTACHMENT_1 =
+            "    <attachment content-type=\"text/plain\" name=\"ABCD1234.txt\" size=\"5120\" />\n";
+    private static final String ATTACHMENT_2 =
+            "    <attachment content-type=\"audio/basic\" name=\"ABCD1234.wav\" size=\"102400\" "
+            + "/>\n";
+
+    private static final String IMAGE_PROPERTIES_END = "</image-properties>\n";
 
     private InputStream toUtf8Stream(String s) {
         try {
@@ -54,25 +106,299 @@
         }
     }
 
+    /**
+     * Test parsing image-properties with very simple information available.
+     *
+     * This is the most common type of object we will receive.
+     *
+     * Payload:
+     *     <?xml version='1.0' encoding='utf-8' standalone='yes' ?>
+     *     <image-properties version="1.0" handle="123456789" >
+     *         <native encoding="JPEG" pixel="200*200" />
+     *     </image-properties>";
+     */
     @Test
-    public void testParseProperties() {
-        InputStream stream = toUtf8Stream(sXmlString);
+    public void testParsePropertiesSimple() {
+        String xmlString = XML_DOC_DECL + IMAGE_PROPERTIES_NO_FRIENDLY_NAME
+                + NATIVE_THUMBNAIL_FORMAT + IMAGE_PROPERTIES_END;
+        InputStream stream = toUtf8Stream(xmlString);
         BipImageProperties properties = new BipImageProperties(stream);
-        Assert.assertEquals(sImageHandle, properties.getImageHandle());
-        Assert.assertEquals(sXmlString, properties.toString());
+        Assert.assertEquals(IMAGE_HANDLE, properties.getImageHandle());
+        Assert.assertEquals(VERSION, properties.getVersion());
+        Assert.assertEquals(null, properties.getFriendlyName());
+        Assert.assertTrue(properties.isValid());
+        Assert.assertEquals(xmlString, properties.toString());
     }
 
+    /**
+     * Test parsing image-properties with very rich information available.
+     *
+     * This information includes attachments, which are not allowed in AVRCP-BIP but completely
+     * allowed in standard BIP.
+     *
+     * Payload:
+     *     <?xml version='1.0' encoding='utf-8' standalone='yes' ?>
+     *     <image-properties version="1.0" handle="123456789" friendly-name="current-track.jpeg">
+     *         <native encoding="JPEG" pixel="200*200" />
+     *         <variant encoding="JPEG" pixel="640*480" />
+     *         <variant encoding="GIF" pixel="80*60-640*175" transformation="stretch fill crop" />
+     *         <variant encoding="JPEG" pixel="150**-600*120" />
+     *         <attachment content-type="text/plain" name="ABCD1234.txt" size="5120" />
+     *         <attachment content-type="audio/basic" name="ABCD1234.wav" size="102400" />
+     *     </image-properties>";
+     */
+    @Test
+    public void testParsePropertiesRich() {
+        String xmlString = XML_DOC_DECL + IMAGE_PROPERTIES + NATIVE_THUMBNAIL_FORMAT
+                + VARIANT_FIXED_FORMAT + VARIANT_RANGE_FORMAT + VARIANT_FIXED_RANGE_FORMAT
+                + ATTACHMENT_1 + ATTACHMENT_2 + IMAGE_PROPERTIES_END;
+        InputStream stream = toUtf8Stream(xmlString);
+        BipImageProperties properties = new BipImageProperties(stream);
+        Assert.assertEquals(IMAGE_HANDLE, properties.getImageHandle());
+        Assert.assertEquals(VERSION, properties.getVersion());
+        Assert.assertEquals(FRIENDLY_NAME, properties.getFriendlyName());
+        Assert.assertTrue(properties.isValid());
+        Assert.assertEquals(xmlString, properties.toString());
+    }
+
+    /**
+     * Test parsing image-properties without an image handle.
+     *
+     * This is out of spec, but should not crash. Instead, the individual attributes should be
+     * available and serializing should return null.
+     *
+     * Payload:
+     *     <?xml version='1.0' encoding='utf-8' standalone='yes' ?>
+     *     <image-properties version="1.0" friendly-name="current-track.jpeg">
+     *         <native encoding="JPEG" pixel="200*200" />
+     *     </image-properties>";
+     */
+    @Test
+    public void testParseNoHandle() {
+        String xmlString = XML_DOC_DECL + IMAGE_PROPERTIES_NO_HANDLE + NATIVE_THUMBNAIL_FORMAT
+                + IMAGE_PROPERTIES_END;
+        InputStream stream = toUtf8Stream(xmlString);
+        BipImageProperties properties = new BipImageProperties(stream);
+        Assert.assertEquals(null, properties.getImageHandle());
+        Assert.assertEquals(VERSION, properties.getVersion());
+        Assert.assertEquals(FRIENDLY_NAME, properties.getFriendlyName());
+        Assert.assertFalse(properties.isValid());
+        Assert.assertEquals(xmlString, properties.toString());
+        Assert.assertEquals(null, properties.serialize());
+    }
+
+    /**
+     * Test parsing image-properties without a version.
+     *
+     * This is out of spec, but should not crash. Instead, the individual attributes should be
+     * available and serializing should return null.
+     *
+     * Payload:
+     *     <?xml version='1.0' encoding='utf-8' standalone='yes' ?>
+     *     <image-properties handle="123456789" friendly-name="current-track.jpeg">
+     *         <native encoding="JPEG" pixel="200*200" />
+     *     </image-properties>";
+     */
+    @Test
+    public void testParseNoVersion() {
+        String xmlString = XML_DOC_DECL + IMAGE_PROPERTIES_NO_VERSION + NATIVE_THUMBNAIL_FORMAT
+                + IMAGE_PROPERTIES_END;
+        InputStream stream = toUtf8Stream(xmlString);
+        BipImageProperties properties = new BipImageProperties(stream);
+        Assert.assertEquals(IMAGE_HANDLE, properties.getImageHandle());
+        Assert.assertEquals(null, properties.getVersion());
+        Assert.assertEquals(FRIENDLY_NAME, properties.getFriendlyName());
+        Assert.assertFalse(properties.isValid());
+        Assert.assertEquals(xmlString, properties.toString());
+        Assert.assertEquals(null, properties.serialize());
+    }
+
+    /**
+     * Test parsing image-properties without a friendly name.
+     *
+     * This is in spec, as friendly name isn't required.
+     *
+     * Payload:
+     *     <?xml version='1.0' encoding='utf-8' standalone='yes' ?>
+     *     <image-properties version="1.0" handle="123456789" friendly-name="current-track.jpeg">
+     *         <native encoding="JPEG" pixel="200*200"/>
+     *     </image-properties>";
+     */
+    @Test
+    public void testParseNoFriendlyName() {
+        String xmlString = XML_DOC_DECL + IMAGE_PROPERTIES_NO_FRIENDLY_NAME
+                + NATIVE_THUMBNAIL_FORMAT + IMAGE_PROPERTIES_END;
+        InputStream stream = toUtf8Stream(xmlString);
+        BipImageProperties properties = new BipImageProperties(stream);
+        Assert.assertEquals(IMAGE_HANDLE, properties.getImageHandle());
+        Assert.assertEquals(VERSION, properties.getVersion());
+        Assert.assertEquals(null, properties.getFriendlyName());
+        Assert.assertTrue(properties.isValid());
+        Assert.assertEquals(xmlString, properties.toString());
+    }
+
+    /**
+     * Test parsing image-properties with a fixed variant thumbnail format
+     *
+     * Payload:
+     *     <?xml version='1.0' encoding='utf-8' standalone='yes' ?>
+     *     <image-properties version="1.0" handle="123456789" friendly-name="current-track.jpeg">
+     *         <variant encoding="JPEG" pixel="200*200" />
+     *     </image-properties>";
+     */
+    @Test
+    public void testParseFixedVariantThumbnailFormat() {
+        String xmlString = XML_DOC_DECL + IMAGE_PROPERTIES + VARIANT_FIXED_THUMBNAIL_FORMAT
+                + IMAGE_PROPERTIES_END;
+        InputStream stream = toUtf8Stream(xmlString);
+        BipImageProperties properties = new BipImageProperties(stream);
+        Assert.assertEquals(IMAGE_HANDLE, properties.getImageHandle());
+        Assert.assertEquals(VERSION, properties.getVersion());
+        Assert.assertEquals(FRIENDLY_NAME, properties.getFriendlyName());
+        Assert.assertTrue(properties.isValid());
+        Assert.assertEquals(xmlString, properties.toString());
+    }
+
+    /**
+     * Test parsing image-properties with a range variant thumbnail format
+     *
+     * Payload:
+     *     <?xml version='1.0' encoding='utf-8' standalone='yes' ?>
+     *     <image-properties version="1.0" handle="123456789" friendly-name="current-track.jpeg">
+     *         <variant encoding="JPEG" pixel="80*60-640*480" />
+     *     </image-properties>";
+     */
+    @Test
+    public void testParseRangeVariantThumbnailFormat() {
+        String xmlString = XML_DOC_DECL + IMAGE_PROPERTIES + VARIANT_RANGE_THUMBNAIL_FORMAT
+                + IMAGE_PROPERTIES_END;
+        InputStream stream = toUtf8Stream(xmlString);
+        BipImageProperties properties = new BipImageProperties(stream);
+        Assert.assertEquals(IMAGE_HANDLE, properties.getImageHandle());
+        Assert.assertEquals(VERSION, properties.getVersion());
+        Assert.assertEquals(FRIENDLY_NAME, properties.getFriendlyName());
+        Assert.assertTrue(properties.isValid());
+        Assert.assertEquals(xmlString, properties.toString());
+    }
+
+    /**
+     * Test parsing image-properties with a fixed aspect ratio range variant thumbnail format
+     *
+     * Payload:
+     *     <?xml version='1.0' encoding='utf-8' standalone='yes' ?>
+     *     <image-properties version="1.0" handle="123456789" friendly-name="current-track.jpeg">
+     *         <variant encoding="JPEG" pixel="80*60-640*480" />
+     *     </image-properties>";
+     */
+    @Test
+    public void testParseFixedRangeVariantThumbnailFormat() {
+        String xmlString = XML_DOC_DECL + IMAGE_PROPERTIES + VARIANT_FIXED_RANGE_THUMBNAIL_FORMAT
+                + IMAGE_PROPERTIES_END;
+        InputStream stream = toUtf8Stream(xmlString);
+        BipImageProperties properties = new BipImageProperties(stream);
+        Assert.assertEquals(IMAGE_HANDLE, properties.getImageHandle());
+        Assert.assertEquals(VERSION, properties.getVersion());
+        Assert.assertEquals(FRIENDLY_NAME, properties.getFriendlyName());
+        Assert.assertTrue(properties.isValid());
+        Assert.assertEquals(xmlString, properties.toString());
+    }
+
+    /**
+     * Test parsing image-properties without any thumbnail formats
+     *
+     * Payload:
+     *     <?xml version='1.0' encoding='utf-8' standalone='yes' ?>
+     *     <image-properties version="1.0" handle="123456789" friendly-name="current-track.jpeg">
+     *         <native encoding="JPEG" pixel="1280*1024" size="1048576" />
+     *         <variant encoding="JPEG" pixel="640*480" />
+     *         <variant encoding="GIF" pixel="80*60-640*480" transformation="stretch fill crop" />
+     *         <variant encoding="JPEG" pixel="150**-600*120" />
+     *     </image-properties>";
+     */
+    @Test
+    public void testParseNoThumbnailFormats() {
+        String xmlString = XML_DOC_DECL + IMAGE_PROPERTIES + NATIVE_FORMAT + VARIANT_FIXED_FORMAT
+                + VARIANT_RANGE_FORMAT + VARIANT_FIXED_RANGE_FORMAT + IMAGE_PROPERTIES_END;
+        InputStream stream = toUtf8Stream(xmlString);
+        BipImageProperties properties = new BipImageProperties(stream);
+        Assert.assertEquals(IMAGE_HANDLE, properties.getImageHandle());
+        Assert.assertEquals(VERSION, properties.getVersion());
+        Assert.assertEquals(FRIENDLY_NAME, properties.getFriendlyName());
+        Assert.assertFalse(properties.isValid());
+        Assert.assertEquals(xmlString, properties.toString());
+        Assert.assertEquals(null, properties.serialize());
+    }
+
+    /**
+     * Test parsing image-properties without any formats
+     *
+     * Payload:
+     *     <?xml version='1.0' encoding='utf-8' standalone='yes' ?>
+     *     <image-properties version="1.0" handle="123456789" friendly-name="current-track.jpeg">
+     *     </image-properties>";
+     */
+    @Test
+    public void testParseNoFormats() {
+        String xmlString = XML_DOC_DECL + IMAGE_PROPERTIES + IMAGE_PROPERTIES_END;
+        InputStream stream = toUtf8Stream(xmlString);
+        BipImageProperties properties = new BipImageProperties(stream);
+        Assert.assertEquals(IMAGE_HANDLE, properties.getImageHandle());
+        Assert.assertEquals(VERSION, properties.getVersion());
+        Assert.assertEquals(FRIENDLY_NAME, properties.getFriendlyName());
+        Assert.assertFalse(properties.isValid());
+        Assert.assertEquals(null, properties.serialize());
+    }
+
+    /**
+     * Test parsing an image-properties with no open tag
+     */
+    @Test (expected = ParseException.class)
+    public void testParseMalformedNoOpen() {
+        String xmlString = XML_DOC_DECL + NATIVE_FORMAT + IMAGE_PROPERTIES_END;
+        InputStream stream = toUtf8Stream(xmlString);
+        BipImageProperties properties = new BipImageProperties(stream);
+    }
+
+    /**
+     * Test parsing a malformed image-properties that just cuts out
+     */
+    @Test (expected = ParseException.class)
+    public void testParseSimulateStreamEndedUnexpectedly() {
+        String xmlString = XML_DOC_DECL + IMAGE_PROPERTIES + "<native encoding=\"JPE";
+        InputStream stream = toUtf8Stream(xmlString);
+        BipImageProperties properties = new BipImageProperties(stream);
+    }
+
+    /**
+     * Test creating image-properties with very rich information available:
+     *
+     * Expected Payload created:
+     *     <?xml version='1.0' encoding='utf-8' standalone='yes' ?>
+     *     <image-properties version="1.0" handle="123456789" friendly-name="current-track.jpeg">
+     *         <native encoding="JPEG" pixel="200*200" />
+     *         <variant encoding="JPEG" pixel="640*480" />
+     *         <variant encoding="GIF" pixel="80*60-640*175" transformation="stretch fill crop" />
+     *         <variant encoding="JPEG" pixel="150**-600*120" />
+     *         <attachment content-type="text/plain" name="ABCD1234.txt" size="5120" />
+     *         <attachment content-type="audio/basic" name="ABCD1234.wav" size="102400" />
+     *     </image-properties>";
+     */
     @Test
     public void testCreateProperties() {
+        String xmlString = XML_DOC_DECL + IMAGE_PROPERTIES + NATIVE_THUMBNAIL_FORMAT
+                + VARIANT_FIXED_FORMAT + VARIANT_RANGE_FORMAT + VARIANT_FIXED_RANGE_FORMAT
+                + ATTACHMENT_1 + ATTACHMENT_2 + IMAGE_PROPERTIES_END;
+
         BipTransformation trans = new BipTransformation();
         trans.addTransformation(BipTransformation.STRETCH);
         trans.addTransformation(BipTransformation.CROP);
         trans.addTransformation(BipTransformation.FILL);
 
         BipImageProperties.Builder builder = new BipImageProperties.Builder();
-        builder.setImageHandle(sImageHandle);
+        builder.setImageHandle(IMAGE_HANDLE);
+        builder.setFriendlyName(FRIENDLY_NAME);
         builder.addNativeFormat(BipImageFormat.createNative(new BipEncoding(BipEncoding.JPEG, null),
-                BipPixel.createFixed(1280, 1024), 1048576));
+                BipPixel.createFixed(200, 200), -1));
 
         builder.addVariantFormat(
                 BipImageFormat.createVariant(
@@ -81,7 +407,7 @@
         builder.addVariantFormat(
                 BipImageFormat.createVariant(
                     new BipEncoding(BipEncoding.GIF, null),
-                    BipPixel.createResizableModified(80, 60, 640, 480), -1, trans));
+                    BipPixel.createResizableModified(80, 60, 640, 175), -1, trans));
         builder.addVariantFormat(
                 BipImageFormat.createVariant(
                     new BipEncoding(BipEncoding.JPEG, null),
@@ -93,7 +419,10 @@
                 new BipAttachmentFormat("audio/basic", null, "ABCD1234.wav", 102400, null, null));
 
         BipImageProperties properties = builder.build();
-        Assert.assertEquals(sImageHandle, properties.getImageHandle());
-        Assert.assertEquals(sXmlString, properties.toString());
+        Assert.assertEquals(IMAGE_HANDLE, properties.getImageHandle());
+        Assert.assertEquals(VERSION, properties.getVersion());
+        Assert.assertEquals(FRIENDLY_NAME, properties.getFriendlyName());
+        Assert.assertTrue(properties.isValid());
+        Assert.assertEquals(xmlString, properties.toString());
     }
 }
diff --git a/tests/unit/src/com/android/bluetooth/btservice/AdapterServiceTest.java b/tests/unit/src/com/android/bluetooth/btservice/AdapterServiceTest.java
index b82e554..47f7c5e 100644
--- a/tests/unit/src/com/android/bluetooth/btservice/AdapterServiceTest.java
+++ b/tests/unit/src/com/android/bluetooth/btservice/AdapterServiceTest.java
@@ -16,6 +16,8 @@
 
 package com.android.bluetooth.btservice;
 
+import static android.Manifest.permission.BLUETOOTH_SCAN;
+
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.*;
 
@@ -24,17 +26,24 @@
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothDevice;
 import android.bluetooth.IBluetoothCallback;
+import android.content.AttributionSource;
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
+import android.content.pm.PermissionInfo;
 import android.content.res.Resources;
 import android.media.AudioManager;
+import android.os.AsyncTask;
 import android.os.Binder;
+import android.os.Bundle;
+import android.os.Handler;
 import android.os.Looper;
 import android.os.PowerManager;
 import android.os.Process;
 import android.os.SystemProperties;
+import android.os.UserHandle;
 import android.os.UserManager;
+import android.permission.PermissionCheckerManager;
 import android.test.mock.MockContentResolver;
 import android.util.Log;
 
@@ -89,12 +98,17 @@
     private @Mock android.app.Application mApplication;
 
     private static final int CONTEXT_SWITCH_MS = 100;
+    private static final int PROFILE_SERVICE_TOGGLE_TIME_MS = 200;
     private static final int GATT_START_TIME_MS = 500;
     private static final int ONE_SECOND_MS = 1000;
     private static final int NATIVE_INIT_MS = 8000;
     private static final int NATIVE_DISABLE_MS = 1000;
 
+    private final AttributionSource mAttributionSource = new AttributionSource.Builder(
+            Process.myUid()).build();
+
     private PowerManager mPowerManager;
+    private PermissionCheckerManager mPermissionCheckerManager;
     private PackageManager mMockPackageManager;
     private MockContentResolver mMockContentResolver;
     private HashMap<String, HashMap<String, String>> mAdapterConfig;
@@ -107,8 +121,8 @@
         }
         Assert.assertNotNull(Looper.myLooper());
         AdapterService adapterService = new AdapterService();
-        adapterService.initNative(false /* is_restricted */, false /* is_niap_mode */,
-                0 /* config_compare_result */, false);
+        adapterService.initNative(false /* is_restricted */, false /* is_common_criteria_mode */,
+                0 /* config_compare_result */, new String[0], false);
         adapterService.cleanupNative();
         HashMap<String, HashMap<String, String>> adapterConfig = TestUtils.readAdapterConfig();
         Assert.assertNotNull(adapterConfig);
@@ -123,18 +137,31 @@
         }
         Assert.assertNotNull(Looper.myLooper());
 
+        // Dispatch all async work through instrumentation so we can wait until
+        // it's drained below
+        AsyncTask.setDefaultExecutor((r) -> {
+            InstrumentationRegistry.getInstrumentation().runOnMainSync(r);
+        });
+
         InstrumentationRegistry.getInstrumentation().runOnMainSync(
                 () -> mAdapterService = new AdapterService());
         mServiceBinder = new AdapterService.AdapterServiceBinder(mAdapterService);
         mMockPackageManager = mock(PackageManager.class);
+        when(mMockPackageManager.getPermissionInfo(any(), anyInt()))
+                .thenReturn(new PermissionInfo());
+
         mMockContentResolver = new MockContentResolver(mMockContext);
         MockitoAnnotations.initMocks(this);
         mPowerManager = (PowerManager) InstrumentationRegistry.getTargetContext()
                 .getSystemService(Context.POWER_SERVICE);
+        mPermissionCheckerManager = InstrumentationRegistry.getTargetContext()
+                .getSystemService(PermissionCheckerManager.class);
 
         when(mMockContext.getApplicationInfo()).thenReturn(mMockApplicationInfo);
         when(mMockContext.getContentResolver()).thenReturn(mMockContentResolver);
         when(mMockContext.getApplicationContext()).thenReturn(mMockContext);
+        when(mMockContext.createContextAsUser(UserHandle.SYSTEM, /* flags= */ 0)).thenReturn(
+                mMockContext);
         when(mMockContext.getResources()).thenReturn(mMockResources);
         when(mMockContext.getUserId()).thenReturn(Process.BLUETOOTH_UID);
         when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
@@ -142,8 +169,13 @@
         when(mMockContext.getSystemService(Context.DEVICE_POLICY_SERVICE)).thenReturn(
                 mMockDevicePolicyManager);
         when(mMockContext.getSystemService(Context.POWER_SERVICE)).thenReturn(mPowerManager);
+        when(mMockContext.getSystemServiceName(PermissionCheckerManager.class))
+                .thenReturn(Context.PERMISSION_CHECKER_SERVICE);
+        when(mMockContext.getSystemService(PermissionCheckerManager.class))
+                .thenReturn(mPermissionCheckerManager);
         when(mMockContext.getSystemService(Context.ALARM_SERVICE)).thenReturn(mMockAlarmManager);
         when(mMockContext.getSystemService(Context.AUDIO_SERVICE)).thenReturn(mAudioManager);
+        when(mMockContext.getAttributionSource()).thenReturn(mAttributionSource);
         doAnswer(invocation -> {
             Object[] args = invocation.getArguments();
             return InstrumentationRegistry.getTargetContext().getDatabasePath((String) args[0]);
@@ -166,9 +198,12 @@
 
         // Attach a context to the service for permission checks.
         mAdapterService.attach(mMockContext, null, null, null, mApplication, null);
-
         mAdapterService.onCreate();
-        mServiceBinder.registerCallback(mIBluetoothCallback);
+
+        // Wait for any async events to drain
+        InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+
+        mServiceBinder.registerCallback(mIBluetoothCallback, mAttributionSource);
 
         Config.init(mMockContext);
 
@@ -178,7 +213,7 @@
 
     @After
     public void tearDown() {
-        mServiceBinder.unregisterCallback(mIBluetoothCallback);
+        mServiceBinder.unregisterCallback(mIBluetoothCallback, mAttributionSource);
         mAdapterService.cleanup();
         Config.init(InstrumentationRegistry.getTargetContext());
     }
@@ -211,7 +246,7 @@
         verifyStateChange(BluetoothAdapter.STATE_BLE_TURNING_ON, BluetoothAdapter.STATE_BLE_ON,
                 invocationNumber + 1, NATIVE_INIT_MS);
 
-        mServiceBinder.onLeServiceUp();
+        mServiceBinder.onLeServiceUp(mAttributionSource);
 
         verifyStateChange(BluetoothAdapter.STATE_BLE_ON, BluetoothAdapter.STATE_TURNING_ON,
                 invocationNumber + 1, CONTEXT_SWITCH_MS);
@@ -227,11 +262,12 @@
         }
 
         verifyStateChange(BluetoothAdapter.STATE_TURNING_ON, BluetoothAdapter.STATE_ON,
-                invocationNumber + 1, CONTEXT_SWITCH_MS);
+                invocationNumber + 1, PROFILE_SERVICE_TOGGLE_TIME_MS);
 
         verify(mMockContext, timeout(CONTEXT_SWITCH_MS).times(2 * invocationNumber + 2))
-                .sendBroadcast(any(), eq(android.Manifest.permission.BLUETOOTH));
-        final int scanMode = mServiceBinder.getScanMode();
+                .sendBroadcast(any(), eq(BLUETOOTH_SCAN),
+                        any(Bundle.class));
+        final int scanMode = mServiceBinder.getScanMode(mAttributionSource);
         Assert.assertTrue(scanMode == BluetoothAdapter.SCAN_MODE_CONNECTABLE
                 || scanMode == BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE);
         Assert.assertTrue(mAdapterService.getState() == BluetoothAdapter.STATE_ON);
@@ -256,9 +292,9 @@
         }
 
         verifyStateChange(BluetoothAdapter.STATE_TURNING_OFF, BluetoothAdapter.STATE_BLE_ON,
-                invocationNumber + 1, CONTEXT_SWITCH_MS);
+                invocationNumber + 1, PROFILE_SERVICE_TOGGLE_TIME_MS);
 
-        mServiceBinder.onBrEdrDown();
+        mServiceBinder.onBrEdrDown(mAttributionSource);
 
         verifyStateChange(BluetoothAdapter.STATE_BLE_ON, BluetoothAdapter.STATE_BLE_TURNING_OFF,
                 invocationNumber + 1, CONTEXT_SWITCH_MS);
@@ -372,7 +408,7 @@
         verifyStateChange(BluetoothAdapter.STATE_TURNING_OFF, BluetoothAdapter.STATE_BLE_ON, 1,
                 CONTEXT_SWITCH_MS);
 
-        mServiceBinder.onBrEdrDown();
+        mServiceBinder.onBrEdrDown(mAttributionSource);
 
         verifyStateChange(BluetoothAdapter.STATE_BLE_ON, BluetoothAdapter.STATE_BLE_TURNING_OFF, 1,
                 CONTEXT_SWITCH_MS);
@@ -407,7 +443,7 @@
         verifyStateChange(BluetoothAdapter.STATE_BLE_TURNING_ON, BluetoothAdapter.STATE_BLE_ON, 1,
                 NATIVE_INIT_MS);
 
-        mServiceBinder.onLeServiceUp();
+        mServiceBinder.onLeServiceUp(mAttributionSource);
 
         verifyStateChange(BluetoothAdapter.STATE_BLE_ON, BluetoothAdapter.STATE_TURNING_ON, 1,
                 CONTEXT_SWITCH_MS);
@@ -639,7 +675,7 @@
         byte[] obfuscatedAddress1 = mAdapterService.obfuscateAddress(device);
         Assert.assertTrue(obfuscatedAddress1.length > 0);
         Assert.assertFalse(isByteArrayAllZero(obfuscatedAddress1));
-        mServiceBinder.factoryReset();
+        mServiceBinder.factoryReset(mAttributionSource);
         byte[] obfuscatedAddress2 = mAdapterService.obfuscateAddress(device);
         Assert.assertTrue(obfuscatedAddress2.length > 0);
         Assert.assertFalse(isByteArrayAllZero(obfuscatedAddress2));
@@ -651,7 +687,7 @@
         Assert.assertFalse(isByteArrayAllZero(obfuscatedAddress3));
         Assert.assertArrayEquals(obfuscatedAddress3,
                 obfuscatedAddress2);
-        mServiceBinder.factoryReset();
+        mServiceBinder.factoryReset(mAttributionSource);
         byte[] obfuscatedAddress4 = mAdapterService.obfuscateAddress(device);
         Assert.assertTrue(obfuscatedAddress4.length > 0);
         Assert.assertFalse(isByteArrayAllZero(obfuscatedAddress4));
@@ -675,7 +711,7 @@
         Assert.assertFalse(isByteArrayAllZero(obfuscatedAddress1));
         Assert.assertArrayEquals(obfuscateInJava(metricsSalt1, device),
                 obfuscatedAddress1);
-        mServiceBinder.factoryReset();
+        mServiceBinder.factoryReset(mAttributionSource);
         tearDown();
         setUp();
         // Cannot verify metrics salt since it is not written to disk until native cleanup
diff --git a/tests/unit/src/com/android/bluetooth/btservice/BondStateMachineTest.java b/tests/unit/src/com/android/bluetooth/btservice/BondStateMachineTest.java
index b9c8953..c0ca7b0 100644
--- a/tests/unit/src/com/android/bluetooth/btservice/BondStateMachineTest.java
+++ b/tests/unit/src/com/android/bluetooth/btservice/BondStateMachineTest.java
@@ -15,12 +15,15 @@
  */
 package com.android.bluetooth.btservice;
 
+import static android.Manifest.permission.BLUETOOTH_CONNECT;
 import static org.mockito.Mockito.*;
 
 import android.bluetooth.BluetoothDevice;
 import android.content.Context;
 import android.content.Intent;
+import android.os.Bundle;
 import android.os.HandlerThread;
+import android.os.Message;
 import android.os.ParcelUuid;
 import android.os.UserHandle;
 
@@ -44,6 +47,7 @@
 public class BondStateMachineTest {
     private static final int TEST_BOND_REASON = 0;
     private static final byte[] TEST_BT_ADDR_BYTES = {00, 11, 22, 33, 44, 55};
+    private static final byte[] TEST_BT_ADDR_BYTES_2 = {00, 11, 22, 33, 44, 66};
     private static final ParcelUuid[] TEST_UUIDS =
             {ParcelUuid.fromString("0000111E-0000-1000-8000-00805F9B34FB")};
 
@@ -87,6 +91,55 @@
     }
 
     @Test
+    public void testCreateBondAfterRemoveBond() {
+        // Set up two devices already bonded.
+        mRemoteDevices.reset();
+        RemoteDevices.DeviceProperties deviceProperties1, deviceProperties2;
+        deviceProperties1 = mRemoteDevices.addDeviceProperties(TEST_BT_ADDR_BYTES);
+        deviceProperties2 = mRemoteDevices.addDeviceProperties(TEST_BT_ADDR_BYTES_2);
+        BluetoothDevice device1, device2;
+        device1 = mRemoteDevices.getDevice(TEST_BT_ADDR_BYTES);
+        device2 = mRemoteDevices.getDevice(TEST_BT_ADDR_BYTES_2);
+        deviceProperties1.mBondState = BOND_BONDED;
+        deviceProperties2.mBondState = BOND_BONDED;
+
+        doReturn(true).when(mAdapterService).removeBondNative(any(byte[].class));
+        doReturn(true).when(mAdapterService).createBondNative(any(byte[].class), anyInt());
+
+        // The removeBond() request for a bonded device should invoke the removeBondNative() call.
+        Message removeBondMsg1 = mBondStateMachine.obtainMessage(BondStateMachine.REMOVE_BOND);
+        removeBondMsg1.obj = device1;
+        mBondStateMachine.sendMessage(removeBondMsg1);
+        TestUtils.waitForLooperToFinishScheduledTask(mBondStateMachine.getHandler().getLooper());
+        Message removeBondMsg2 = mBondStateMachine.obtainMessage(BondStateMachine.REMOVE_BOND);
+        removeBondMsg2.obj = device2;
+        mBondStateMachine.sendMessage(removeBondMsg2);
+        TestUtils.waitForLooperToFinishScheduledTask(mBondStateMachine.getHandler().getLooper());
+
+        verify(mAdapterService, times(1)).removeBondNative(eq(TEST_BT_ADDR_BYTES));
+        verify(mAdapterService, times(1)).removeBondNative(eq(TEST_BT_ADDR_BYTES_2));
+
+        mBondStateMachine.bondStateChangeCallback(AbstractionLayer.BT_STATUS_SUCCESS,
+                TEST_BT_ADDR_BYTES, BOND_NONE);
+        TestUtils.waitForLooperToFinishScheduledTask(mBondStateMachine.getHandler().getLooper());
+        mBondStateMachine.bondStateChangeCallback(AbstractionLayer.BT_STATUS_SUCCESS,
+                TEST_BT_ADDR_BYTES_2, BOND_NONE);
+        TestUtils.waitForLooperToFinishScheduledTask(mBondStateMachine.getHandler().getLooper());
+
+        // Try to pair these two devices again, createBondNative() should be invoked.
+        Message createBondMsg1 = mBondStateMachine.obtainMessage(BondStateMachine.CREATE_BOND);
+        createBondMsg1.obj = device1;
+        mBondStateMachine.sendMessage(createBondMsg1);
+        Message createBondMsg2 = mBondStateMachine.obtainMessage(BondStateMachine.CREATE_BOND);
+        createBondMsg2.obj = device2;
+        mBondStateMachine.sendMessage(createBondMsg2);
+        TestUtils.waitForLooperToFinishScheduledTask(mBondStateMachine.getHandler().getLooper());
+
+        verify(mAdapterService, times(1)).createBondNative(eq(TEST_BT_ADDR_BYTES), anyInt());
+        verify(mAdapterService, times(1)).createBondNative(eq(TEST_BT_ADDR_BYTES_2), anyInt());
+    }
+
+    @Test
     public void testSendIntent() {
         int badBondState = 42;
         mVerifyCount = 0;
@@ -232,12 +285,12 @@
         if (shouldBroadcast) {
             verify(mAdapterService, times(++mVerifyCount)).sendBroadcastAsUser(
                     intentArgument.capture(), eq(UserHandle.ALL),
-                    eq(AdapterService.BLUETOOTH_PERM));
+                    eq(BLUETOOTH_CONNECT), any(Bundle.class));
             verifyBondStateChangeIntent(broadcastOldState, broadcastNewState,
                     intentArgument.getValue());
         } else {
             verify(mAdapterService, times(mVerifyCount)).sendBroadcastAsUser(any(Intent.class),
-                    any(UserHandle.class), anyString());
+                    any(UserHandle.class), anyString(), any(Bundle.class));
         }
     }
 
diff --git a/tests/unit/src/com/android/bluetooth/btservice/PhonePolicyTest.java b/tests/unit/src/com/android/bluetooth/btservice/PhonePolicyTest.java
index fa0c7c0..0b3d2bb 100644
--- a/tests/unit/src/com/android/bluetooth/btservice/PhonePolicyTest.java
+++ b/tests/unit/src/com/android/bluetooth/btservice/PhonePolicyTest.java
@@ -16,6 +16,9 @@
 
 package com.android.bluetooth.btservice;
 
+import static com.android.bluetooth.TestUtils.getTestDevice;
+import static com.android.bluetooth.TestUtils.waitForLooperToFinishScheduledTask;
+
 import static org.mockito.Mockito.*;
 
 import android.bluetooth.BluetoothA2dp;
@@ -76,6 +79,7 @@
         TestUtils.setAdapterService(mAdapterService);
         // Configure the maximum connected audio devices
         doReturn(MAX_CONNECTED_AUDIO_DEVICES).when(mAdapterService).getMaxConnectedAudioDevices();
+        doReturn(mDatabaseManager).when(mAdapterService).getDatabase();
         // Setup the mocked factory to return mocked services
         doReturn(mHeadsetService).when(mServiceFactory).getHeadsetService();
         doReturn(mA2dpService).when(mServiceFactory).getA2dpService();
@@ -83,7 +87,7 @@
         mHandlerThread = new HandlerThread("PhonePolicyTestHandlerThread");
         mHandlerThread.start();
         // Mock the looper
-        doReturn(mHandlerThread.getLooper()).when(mAdapterService).getMainLooper();
+        when(mAdapterService.getMainLooper()).thenReturn(mHandlerThread.getLooper());
         // Tell the AdapterService that it is a mock (see isMock documentation)
         doReturn(true).when(mAdapterService).isMock();
         // Must be called to initialize services
@@ -94,7 +98,9 @@
 
     @After
     public void tearDown() throws Exception {
-        mHandlerThread.quit();
+        if (mHandlerThread != null) {
+            mHandlerThread.quitSafely();
+        }
         TestUtils.clearAdapterService(mAdapterService);
     }
 
@@ -105,7 +111,7 @@
      */
     @Test
     public void testProcessInitProfilePriorities() {
-        BluetoothDevice device = TestUtils.getTestDevice(mAdapter, 0);
+        BluetoothDevice device = getTestDevice(mAdapter, 0);
         // Mock the HeadsetService to return unknown connection policy
         when(mHeadsetService.getConnectionPolicy(device))
                 .thenReturn(BluetoothProfile.CONNECTION_POLICY_UNKNOWN);
@@ -147,8 +153,7 @@
         when(mAdapterService.isQuietModeEnabled()).thenReturn(false);
 
         // Return a list of connection order
-        BluetoothDevice bondedDevice = TestUtils.getTestDevice(mAdapter, 0);
-        when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager);
+        BluetoothDevice bondedDevice = getTestDevice(mAdapter, 0);
         when(mDatabaseManager.getMostRecentlyConnectedA2dpDevice()).thenReturn(bondedDevice);
         when(mAdapterService.getBondState(bondedDevice)).thenReturn(BluetoothDevice.BOND_BONDED);
 
@@ -179,12 +184,11 @@
 
         // Return a list of connection order
         List<BluetoothDevice> connectionOrder = new ArrayList<>();
-        connectionOrder.add(TestUtils.getTestDevice(mAdapter, 0));
-        connectionOrder.add(TestUtils.getTestDevice(mAdapter, 1));
-        connectionOrder.add(TestUtils.getTestDevice(mAdapter, 2));
-        connectionOrder.add(TestUtils.getTestDevice(mAdapter, 3));
+        connectionOrder.add(getTestDevice(mAdapter, 0));
+        connectionOrder.add(getTestDevice(mAdapter, 1));
+        connectionOrder.add(getTestDevice(mAdapter, 2));
+        connectionOrder.add(getTestDevice(mAdapter, 3));
 
-        when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager);
         when(mDatabaseManager.getMostRecentlyConnectedA2dpDevice()).thenReturn(
                 connectionOrder.get(0));
 
@@ -203,6 +207,7 @@
         intent.putExtra(BluetoothDevice.EXTRA_DEVICE, connectionOrder.get(0));
         intent.addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
         mPhonePolicy.getBroadcastReceiver().onReceive(null /* context */, intent);
+        waitForLooperToFinishScheduledTask(mHandlerThread.getLooper());
 
         // Only calls setConnection on device connectionOrder.get(0) with STATE_CONNECTED
         verify(mDatabaseManager, timeout(ASYNC_CALL_TIMEOUT_MILLIS)).setConnection(
@@ -218,6 +223,7 @@
         intent.putExtra(BluetoothDevice.EXTRA_DEVICE, connectionOrder.get(1));
         intent.addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
         mPhonePolicy.getBroadcastReceiver().onReceive(null /* context */, intent);
+        waitForLooperToFinishScheduledTask(mHandlerThread.getLooper());
 
         // Only calls setConnection on device connectionOrder.get(1) with STATE_CONNECTED
         verify(mDatabaseManager, timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(1)).setConnection(
@@ -236,6 +242,7 @@
         intent.putExtra(BluetoothProfile.EXTRA_STATE, BluetoothProfile.STATE_DISCONNECTED);
         intent.addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
         mPhonePolicy.getBroadcastReceiver().onReceive(null /* context */, intent);
+        waitForLooperToFinishScheduledTask(mHandlerThread.getLooper());
 
         // Verify that we do not call setConnection, but instead setDisconnection on disconnect
         verify(mDatabaseManager, timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(1)).setConnection(
@@ -248,6 +255,7 @@
                 BluetoothProfile.STATE_DISCONNECTED);
         updateProfileConnectionStateHelper(connectionOrder.get(1), BluetoothProfile.HEADSET,
                 BluetoothProfile.STATE_DISCONNECTED, BluetoothProfile.STATE_CONNECTING);
+        waitForLooperToFinishScheduledTask(mHandlerThread.getLooper());
 
         // Verify we don't call deleteConnection as that only happens when we disconnect a2dp
         verify(mDatabaseManager, timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(1)).setDisconnection(
@@ -266,7 +274,7 @@
     public void testReconnectOnPartialConnect() {
         // Return a list of bonded devices (just one)
         BluetoothDevice[] bondedDevices = new BluetoothDevice[1];
-        bondedDevices[0] = TestUtils.getTestDevice(mAdapter, 0);
+        bondedDevices[0] = getTestDevice(mAdapter, 0);
         when(mAdapterService.getBondedDevices()).thenReturn(bondedDevices);
 
         // Return PRIORITY_AUTO_CONNECT over HFP and A2DP. This would imply that the profiles are
@@ -306,8 +314,7 @@
     @Test
     public void testReconnectOnPartialConnect_PreviousPartialFail() {
         List<BluetoothDevice> connectionOrder = new ArrayList<>();
-        connectionOrder.add(TestUtils.getTestDevice(mAdapter, 0));
-        when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager);
+        connectionOrder.add(getTestDevice(mAdapter, 0));
         when(mDatabaseManager.getMostRecentlyConnectedA2dpDevice()).thenReturn(
                 connectionOrder.get(0));
 
@@ -344,7 +351,7 @@
         updateProfileConnectionStateHelper(connectionOrder.get(0), BluetoothProfile.A2DP,
                 BluetoothProfile.STATE_DISCONNECTED, BluetoothProfile.STATE_CONNECTING);
 
-        TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper());
+        waitForLooperToFinishScheduledTask(mHandlerThread.getLooper());
 
         // Verify no one changes the priority of the failed profile
         verify(mA2dpService, never()).setConnectionPolicy(eq(connectionOrder.get(0)), anyInt());
@@ -362,7 +369,7 @@
         updateProfileConnectionStateHelper(connectionOrder.get(0), BluetoothProfile.HEADSET,
                 BluetoothProfile.STATE_DISCONNECTED, BluetoothProfile.STATE_CONNECTED);
 
-        TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper());
+        waitForLooperToFinishScheduledTask(mHandlerThread.getLooper());
 
         // Send a connection success event for one profile again to trigger re-connect
         hsConnectedDevices.add(connectionOrder.get(0));
@@ -391,7 +398,7 @@
         BluetoothDevice a2dpNotConnectedDevice2 = null;
 
         for (int i = 0; i < kMaxTestDevices; i++) {
-            BluetoothDevice testDevice = TestUtils.getTestDevice(mAdapter, i);
+            BluetoothDevice testDevice = getTestDevice(mAdapter, i);
             testDevices[i] = testDevice;
 
             // Return PRIORITY_AUTO_CONNECT over HFP and A2DP. This would imply that the profiles
@@ -459,7 +466,7 @@
         ArrayList<BluetoothDevice> a2dpConnectedDevices = new ArrayList<>();
 
         for (int i = 0; i < kMaxTestDevices; i++) {
-            BluetoothDevice testDevice = TestUtils.getTestDevice(mAdapter, i);
+            BluetoothDevice testDevice = getTestDevice(mAdapter, i);
             testDevices[i] = testDevice;
 
             // Connect HFP and A2DP for each device as appropriate.
@@ -597,7 +604,7 @@
     public void testNoReconnectOnNoConnect() {
         // Return a list of bonded devices (just one)
         BluetoothDevice[] bondedDevices = new BluetoothDevice[1];
-        bondedDevices[0] = TestUtils.getTestDevice(mAdapter, 0);
+        bondedDevices[0] = getTestDevice(mAdapter, 0);
         when(mAdapterService.getBondedDevices()).thenReturn(bondedDevices);
 
         // Return PRIORITY_AUTO_CONNECT over HFP and A2DP. This would imply that the profiles are
@@ -642,8 +649,8 @@
     public void testNoReconnectOnNoConnect_MultiDevice() {
         // Return a list of bonded devices (just one)
         BluetoothDevice[] bondedDevices = new BluetoothDevice[2];
-        bondedDevices[0] = TestUtils.getTestDevice(mAdapter, 0);
-        bondedDevices[1] = TestUtils.getTestDevice(mAdapter, 1);
+        bondedDevices[0] = getTestDevice(mAdapter, 0);
+        bondedDevices[1] = getTestDevice(mAdapter, 1);
         when(mAdapterService.getBondedDevices()).thenReturn(bondedDevices);
 
         // Return PRIORITY_AUTO_CONNECT over HFP and A2DP. This would imply that the profiles are
@@ -697,8 +704,8 @@
     public void testReconnectOnPartialConnect_MultiDevice() {
         // Return a list of bonded devices (just one)
         BluetoothDevice[] bondedDevices = new BluetoothDevice[2];
-        bondedDevices[0] = TestUtils.getTestDevice(mAdapter, 0);
-        bondedDevices[1] = TestUtils.getTestDevice(mAdapter, 1);
+        bondedDevices[0] = getTestDevice(mAdapter, 0);
+        bondedDevices[1] = getTestDevice(mAdapter, 1);
         when(mAdapterService.getBondedDevices()).thenReturn(bondedDevices);
 
         // Return PRIORITY_AUTO_CONNECT over HFP and A2DP. This would imply that the profiles are
@@ -744,7 +751,7 @@
     @Test
     public void testNoSupportedUuids() {
         // Mock the HeadsetService to return undefined priority
-        BluetoothDevice device = TestUtils.getTestDevice(mAdapter, 0);
+        BluetoothDevice device = getTestDevice(mAdapter, 0);
         when(mHeadsetService.getConnectionPolicy(device))
                 .thenReturn(BluetoothProfile.CONNECTION_POLICY_UNKNOWN);
 
diff --git a/tests/unit/src/com/android/bluetooth/btservice/ProfileServiceTest.java b/tests/unit/src/com/android/bluetooth/btservice/ProfileServiceTest.java
index 4648e1f..fb9eb96 100644
--- a/tests/unit/src/com/android/bluetooth/btservice/ProfileServiceTest.java
+++ b/tests/unit/src/com/android/bluetooth/btservice/ProfileServiceTest.java
@@ -29,6 +29,7 @@
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.bluetooth.TestUtils;
+import com.android.bluetooth.btservice.storage.DatabaseManager;
 
 import org.junit.After;
 import org.junit.Assert;
@@ -39,9 +40,12 @@
 import org.mockito.ArgumentCaptor;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
 
 import java.lang.reflect.InvocationTargetException;
 import java.util.List;
+import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.TimeoutException;
 
 @MediumTest
@@ -51,8 +55,18 @@
     private static final int NUM_REPEATS = 5;
 
     @Rule public final ServiceTestRule mServiceTestRule = new ServiceTestRule();
+    @Mock private AdapterService mMockAdapterService;
+    @Mock private DatabaseManager mDatabaseManager;
+
+    private Class[] mProfiles;
+    ConcurrentHashMap<String, Boolean> mStartedProfileMap = new ConcurrentHashMap();
 
     private void setProfileState(Class profile, int state) throws TimeoutException {
+        if (state == BluetoothAdapter.STATE_ON) {
+            mStartedProfileMap.put(profile.getSimpleName(), true);
+        } else if (state == BluetoothAdapter.STATE_OFF) {
+            mStartedProfileMap.put(profile.getSimpleName(), false);
+        }
         Intent startIntent = new Intent(InstrumentationRegistry.getTargetContext(), profile);
         startIntent.putExtra(AdapterService.EXTRA_ACTION,
                 AdapterService.ACTION_SERVICE_STATE_CHANGED);
@@ -80,10 +94,6 @@
         }
     }
 
-    private @Mock AdapterService mMockAdapterService;
-
-    private Class[] mProfiles;
-
     @Before
     public void setUp()
             throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
@@ -93,13 +103,22 @@
         Assert.assertNotNull(Looper.myLooper());
 
         MockitoAnnotations.initMocks(this);
+        when(mMockAdapterService.isStartedProfile(anyString())).thenAnswer(new Answer<Boolean>() {
+            @Override
+            public Boolean answer(InvocationOnMock invocation) throws Throwable {
+                Object[] args = invocation.getArguments();
+                return mStartedProfileMap.get((String) args[0]);
+            }
+        });
 
         mProfiles = Config.getSupportedProfiles();
 
-        mMockAdapterService.initNative(false /* is_restricted */, false /* is_niap_mode */,
-                0 /* config_compare_result */, false);
+        mMockAdapterService.initNative(false /* is_restricted */,
+                false /* is_common_criteria_mode */, 0 /* config_compare_result */,
+                new String[0], false);
 
         TestUtils.setAdapterService(mMockAdapterService);
+        doReturn(mDatabaseManager).when(mMockAdapterService).getDatabase();
 
         Assert.assertNotNull(AdapterService.getAdapterService());
     }
diff --git a/tests/unit/src/com/android/bluetooth/btservice/RemoteDevicesTest.java b/tests/unit/src/com/android/bluetooth/btservice/RemoteDevicesTest.java
index 81abfc6..12c6e74 100644
--- a/tests/unit/src/com/android/bluetooth/btservice/RemoteDevicesTest.java
+++ b/tests/unit/src/com/android/bluetooth/btservice/RemoteDevicesTest.java
@@ -1,5 +1,6 @@
 package com.android.bluetooth.btservice;
 
+import static android.Manifest.permission.BLUETOOTH_CONNECT;
 import static org.mockito.Mockito.*;
 
 import android.bluetooth.BluetoothAdapter;
@@ -8,6 +9,7 @@
 import android.bluetooth.BluetoothHeadset;
 import android.bluetooth.BluetoothProfile;
 import android.content.Intent;
+import android.os.Bundle;
 import android.os.HandlerThread;
 import android.os.Message;
 import android.os.TestLooperManager;
@@ -70,7 +72,7 @@
 
         // Verify that executing that message results in a broadcast intent
         mTestLooperManager.execute(msg);
-        verify(mAdapterService).sendBroadcast(any(), anyString());
+        verify(mAdapterService).sendBroadcast(any(), anyString(), any());
         verifyNoMoreInteractions(mAdapterService);
     }
 
@@ -83,9 +85,10 @@
 
         // Verify that updating battery level triggers ACTION_BATTERY_LEVEL_CHANGED intent
         mRemoteDevices.updateBatteryLevel(mDevice1, batteryLevel);
-        verify(mAdapterService).sendBroadcast(mIntentArgument.capture(), mStringArgument.capture());
+        verify(mAdapterService).sendBroadcast(mIntentArgument.capture(), mStringArgument.capture(),
+                any(Bundle.class));
         verifyBatteryLevelChangedIntent(mDevice1, batteryLevel, mIntentArgument);
-        Assert.assertEquals(AdapterService.BLUETOOTH_PERM, mStringArgument.getValue());
+        Assert.assertEquals(BLUETOOTH_CONNECT, mStringArgument.getValue());
 
         // Verify that user can get battery level after the update
         Assert.assertNotNull(mRemoteDevices.getDeviceProperties(mDevice1));
@@ -94,14 +97,14 @@
 
         // Verify that update same battery level for the same device does not trigger intent
         mRemoteDevices.updateBatteryLevel(mDevice1, batteryLevel);
-        verify(mAdapterService).sendBroadcast(any(), anyString());
+        verify(mAdapterService).sendBroadcast(any(), anyString(), any());
 
         // Verify that updating battery level to different value triggers the intent again
         batteryLevel = 15;
         mRemoteDevices.updateBatteryLevel(mDevice1, batteryLevel);
         verify(mAdapterService, times(2)).sendBroadcast(mIntentArgument.capture(),
-                mStringArgument.capture());
-        Assert.assertEquals(AdapterService.BLUETOOTH_PERM, mStringArgument.getValue());
+                mStringArgument.capture(), any(Bundle.class));
+        Assert.assertEquals(BLUETOOTH_CONNECT, mStringArgument.getValue());
         verifyBatteryLevelChangedIntent(mDevice1, batteryLevel, mIntentArgument);
 
         // Verify that user can get battery level after the update
@@ -120,7 +123,7 @@
 
         // Verify that updating with invalid battery level does not trigger the intent
         mRemoteDevices.updateBatteryLevel(mDevice1, batteryLevel);
-        verify(mAdapterService, never()).sendBroadcast(any(), anyString());
+        verify(mAdapterService, never()).sendBroadcast(any(), anyString(), any());
 
         // Verify that device property stays null after invalid update
         Assert.assertNull(mRemoteDevices.getDeviceProperties(mDevice1));
@@ -137,7 +140,7 @@
 
         // Verify that updating invalid battery level does not trigger the intent
         mRemoteDevices.updateBatteryLevel(mDevice1, batteryLevel);
-        verify(mAdapterService, never()).sendBroadcast(any(), anyString());
+        verify(mAdapterService, never()).sendBroadcast(any(), anyString(), any());
 
         // Verify that device property stays null after invalid update
         Assert.assertNull(mRemoteDevices.getDeviceProperties(mDevice1));
@@ -166,9 +169,10 @@
 
         // Verify that updating battery level triggers ACTION_BATTERY_LEVEL_CHANGED intent
         mRemoteDevices.updateBatteryLevel(mDevice1, batteryLevel);
-        verify(mAdapterService).sendBroadcast(mIntentArgument.capture(), mStringArgument.capture());
+        verify(mAdapterService).sendBroadcast(mIntentArgument.capture(), mStringArgument.capture(),
+                any(Bundle.class));
         verifyBatteryLevelChangedIntent(mDevice1, batteryLevel, mIntentArgument);
-        Assert.assertEquals(AdapterService.BLUETOOTH_PERM, mStringArgument.getValue());
+        Assert.assertEquals(BLUETOOTH_CONNECT, mStringArgument.getValue());
 
         // Verify that user can get battery level after the update
         Assert.assertNotNull(mRemoteDevices.getDeviceProperties(mDevice1));
@@ -180,10 +184,10 @@
         mRemoteDevices.resetBatteryLevel(mDevice1);
         // Verify BATTERY_LEVEL_CHANGED intent is sent after first reset
         verify(mAdapterService, times(2)).sendBroadcast(mIntentArgument.capture(),
-                mStringArgument.capture());
+                mStringArgument.capture(), any(Bundle.class));
         verifyBatteryLevelChangedIntent(mDevice1, BluetoothDevice.BATTERY_LEVEL_UNKNOWN,
                 mIntentArgument);
-        Assert.assertEquals(AdapterService.BLUETOOTH_PERM, mStringArgument.getValue());
+        Assert.assertEquals(BLUETOOTH_CONNECT, mStringArgument.getValue());
         // Verify value is reset in properties
         Assert.assertNotNull(mRemoteDevices.getDeviceProperties(mDevice1));
         Assert.assertEquals(mRemoteDevices.getDeviceProperties(mDevice1).getBatteryLevel(),
@@ -191,14 +195,15 @@
 
         // Verify no intent is sent after second reset
         mRemoteDevices.resetBatteryLevel(mDevice1);
-        verify(mAdapterService, times(2)).sendBroadcast(any(), anyString());
+        verify(mAdapterService, times(2)).sendBroadcast(any(), anyString(),
+                any());
 
         // Verify that updating battery level triggers ACTION_BATTERY_LEVEL_CHANGED intent again
         mRemoteDevices.updateBatteryLevel(mDevice1, batteryLevel);
         verify(mAdapterService, times(3)).sendBroadcast(mIntentArgument.capture(),
-                mStringArgument.capture());
+                mStringArgument.capture(), any(Bundle.class));
         verifyBatteryLevelChangedIntent(mDevice1, batteryLevel, mIntentArgument);
-        Assert.assertEquals(AdapterService.BLUETOOTH_PERM, mStringArgument.getValue());
+        Assert.assertEquals(BLUETOOTH_CONNECT, mStringArgument.getValue());
 
         verifyNoMoreInteractions(mAdapterService);
     }
@@ -212,9 +217,10 @@
 
         // Verify that updating battery level triggers ACTION_BATTERY_LEVEL_CHANGED intent
         mRemoteDevices.updateBatteryLevel(mDevice1, batteryLevel);
-        verify(mAdapterService).sendBroadcast(mIntentArgument.capture(), mStringArgument.capture());
+        verify(mAdapterService).sendBroadcast(mIntentArgument.capture(), mStringArgument.capture(),
+                any(Bundle.class));
         verifyBatteryLevelChangedIntent(mDevice1, batteryLevel, mIntentArgument);
-        Assert.assertEquals(AdapterService.BLUETOOTH_PERM, mStringArgument.getValue());
+        Assert.assertEquals(BLUETOOTH_CONNECT, mStringArgument.getValue());
 
         // Verify that user can get battery level after the update
         Assert.assertNotNull(mRemoteDevices.getDeviceProperties(mDevice1));
@@ -228,10 +234,10 @@
                         BluetoothProfile.STATE_DISCONNECTING, BluetoothProfile.STATE_DISCONNECTED));
         // Verify BATTERY_LEVEL_CHANGED intent is sent after first reset
         verify(mAdapterService, times(2)).sendBroadcast(mIntentArgument.capture(),
-                mStringArgument.capture());
+                mStringArgument.capture(), any(Bundle.class));
         verifyBatteryLevelChangedIntent(mDevice1, BluetoothDevice.BATTERY_LEVEL_UNKNOWN,
                 mIntentArgument);
-        Assert.assertEquals(AdapterService.BLUETOOTH_PERM, mStringArgument.getValue());
+        Assert.assertEquals(BLUETOOTH_CONNECT, mStringArgument.getValue());
         // Verify value is reset in properties
         Assert.assertNotNull(mRemoteDevices.getDeviceProperties(mDevice1));
         Assert.assertEquals(mRemoteDevices.getDeviceProperties(mDevice1).getBatteryLevel(),
@@ -240,9 +246,9 @@
         // Verify that updating battery level triggers ACTION_BATTERY_LEVEL_CHANGED intent again
         mRemoteDevices.updateBatteryLevel(mDevice1, batteryLevel);
         verify(mAdapterService, times(3)).sendBroadcast(mIntentArgument.capture(),
-                mStringArgument.capture());
+                mStringArgument.capture(), any(Bundle.class));
         verifyBatteryLevelChangedIntent(mDevice1, batteryLevel, mIntentArgument);
-        Assert.assertEquals(AdapterService.BLUETOOTH_PERM, mStringArgument.getValue());
+        Assert.assertEquals(BLUETOOTH_CONNECT, mStringArgument.getValue());
 
         verifyNoMoreInteractions(mAdapterService);
     }
@@ -256,9 +262,10 @@
 
         // Verify that updating battery level triggers ACTION_BATTERY_LEVEL_CHANGED intent
         mRemoteDevices.updateBatteryLevel(mDevice1, batteryLevel);
-        verify(mAdapterService).sendBroadcast(mIntentArgument.capture(), mStringArgument.capture());
+        verify(mAdapterService).sendBroadcast(mIntentArgument.capture(), mStringArgument.capture(),
+                any(Bundle.class));
         verifyBatteryLevelChangedIntent(mDevice1, batteryLevel, mIntentArgument);
-        Assert.assertEquals(AdapterService.BLUETOOTH_PERM, mStringArgument.getValue());
+        Assert.assertEquals(BLUETOOTH_CONNECT, mStringArgument.getValue());
 
         // Verify that user can get battery level after the update
         Assert.assertNotNull(mRemoteDevices.getDeviceProperties(mDevice1));
@@ -269,18 +276,18 @@
         // BluetoothDevice.BATTERY_LEVEL_UNKNOWN
         when(mAdapterService.getState()).thenReturn(BluetoothAdapter.STATE_ON);
         mRemoteDevices.aclStateChangeCallback(0, Utils.getByteAddress(mDevice1),
-                AbstractionLayer.BT_ACL_STATE_DISCONNECTED);
+                AbstractionLayer.BT_ACL_STATE_DISCONNECTED, 19); // HCI code 19 remote terminated
         // Verify ACTION_ACL_DISCONNECTED and BATTERY_LEVEL_CHANGED intent are sent
         verify(mAdapterService, times(3)).sendBroadcast(mIntentArgument.capture(),
-                mStringArgument.capture());
+                mStringArgument.capture(), any(Bundle.class));
         verify(mAdapterService, times(2)).obfuscateAddress(mDevice1);
         verifyBatteryLevelChangedIntent(mDevice1, BluetoothDevice.BATTERY_LEVEL_UNKNOWN,
                 mIntentArgument.getAllValues().get(mIntentArgument.getAllValues().size() - 2));
-        Assert.assertEquals(AdapterService.BLUETOOTH_PERM,
+        Assert.assertEquals(BLUETOOTH_CONNECT,
                 mStringArgument.getAllValues().get(mStringArgument.getAllValues().size() - 2));
         Assert.assertEquals(BluetoothDevice.ACTION_ACL_DISCONNECTED,
                 mIntentArgument.getValue().getAction());
-        Assert.assertEquals(AdapterService.BLUETOOTH_PERM, mStringArgument.getValue());
+        Assert.assertEquals(BLUETOOTH_CONNECT, mStringArgument.getValue());
         // Verify value is reset in properties
         Assert.assertNotNull(mRemoteDevices.getDeviceProperties(mDevice1));
         Assert.assertEquals(BluetoothDevice.BATTERY_LEVEL_UNKNOWN,
@@ -289,9 +296,9 @@
         // Verify that updating battery level triggers ACTION_BATTERY_LEVEL_CHANGED intent again
         mRemoteDevices.updateBatteryLevel(mDevice1, batteryLevel);
         verify(mAdapterService, times(4)).sendBroadcast(mIntentArgument.capture(),
-                mStringArgument.capture());
+                mStringArgument.capture(), any(Bundle.class));
         verifyBatteryLevelChangedIntent(mDevice1, batteryLevel, mIntentArgument);
-        Assert.assertEquals(AdapterService.BLUETOOTH_PERM, mStringArgument.getValue());
+        Assert.assertEquals(BLUETOOTH_CONNECT, mStringArgument.getValue());
     }
 
     @Test
@@ -304,9 +311,10 @@
         // Verify that ACTION_HF_INDICATORS_VALUE_CHANGED intent updates battery level
         mRemoteDevices.onHfIndicatorValueChanged(getHfIndicatorIntent(mDevice1, batteryLevel,
                 HeadsetHalConstants.HF_INDICATOR_BATTERY_LEVEL_STATUS));
-        verify(mAdapterService).sendBroadcast(mIntentArgument.capture(), mStringArgument.capture());
+        verify(mAdapterService).sendBroadcast(mIntentArgument.capture(), mStringArgument.capture(),
+                any(Bundle.class));
         verifyBatteryLevelChangedIntent(mDevice1, batteryLevel, mIntentArgument);
-        Assert.assertEquals(AdapterService.BLUETOOTH_PERM, mStringArgument.getValue());
+        Assert.assertEquals(BLUETOOTH_CONNECT, mStringArgument.getValue());
     }
 
     @Test
@@ -333,9 +341,10 @@
                 BluetoothHeadset.VENDOR_SPECIFIC_HEADSET_EVENT_XEVENT,
                 BluetoothAssignedNumbers.PLANTRONICS, BluetoothHeadset.AT_CMD_TYPE_SET,
                 getXEventArray(3, 8), mDevice1));
-        verify(mAdapterService).sendBroadcast(mIntentArgument.capture(), mStringArgument.capture());
+        verify(mAdapterService).sendBroadcast(mIntentArgument.capture(), mStringArgument.capture(),
+                any(Bundle.class));
         verifyBatteryLevelChangedIntent(mDevice1, 42, mIntentArgument);
-        Assert.assertEquals(AdapterService.BLUETOOTH_PERM, mStringArgument.getValue());
+        Assert.assertEquals(BLUETOOTH_CONNECT, mStringArgument.getValue());
     }
 
     @Test
@@ -355,9 +364,10 @@
                         3,
                         10
                 }, mDevice1));
-        verify(mAdapterService).sendBroadcast(mIntentArgument.capture(), mStringArgument.capture());
+        verify(mAdapterService).sendBroadcast(mIntentArgument.capture(), mStringArgument.capture(),
+                any(Bundle.class));
         verifyBatteryLevelChangedIntent(mDevice1, 60, mIntentArgument);
-        Assert.assertEquals(AdapterService.BLUETOOTH_PERM, mStringArgument.getValue());
+        Assert.assertEquals(BLUETOOTH_CONNECT, mStringArgument.getValue());
     }
 
     @Test
diff --git a/tests/unit/src/com/android/bluetooth/btservice/SilenceDeviceManagerTest.java b/tests/unit/src/com/android/bluetooth/btservice/SilenceDeviceManagerTest.java
index 0f7864a..b900305 100644
--- a/tests/unit/src/com/android/bluetooth/btservice/SilenceDeviceManagerTest.java
+++ b/tests/unit/src/com/android/bluetooth/btservice/SilenceDeviceManagerTest.java
@@ -16,6 +16,7 @@
 
 package com.android.bluetooth.btservice;
 
+import static android.Manifest.permission.BLUETOOTH_CONNECT;
 import static org.mockito.Mockito.*;
 
 import android.bluetooth.BluetoothA2dp;
@@ -25,6 +26,7 @@
 import android.bluetooth.BluetoothProfile;
 import android.content.Context;
 import android.content.Intent;
+import android.os.Bundle;
 import android.os.HandlerThread;
 import android.os.Looper;
 import android.os.UserHandle;
@@ -118,7 +120,7 @@
             TestUtils.waitForLooperToFinishScheduledTask(mLooper);
             verify(mAdapterService, times(++mVerifyCount)).sendBroadcastAsUser(
                     intentArgument.capture(), eq(UserHandle.ALL),
-                    eq(AdapterService.BLUETOOTH_PERM));
+                    eq(BLUETOOTH_CONNECT), any(Bundle.class));
         }
 
         // Set silence state and check whether state changed successfully
@@ -130,7 +132,7 @@
         if (wasSilenced != enableSilence) {
             verify(mAdapterService, times(++mVerifyCount)).sendBroadcastAsUser(
                     intentArgument.capture(), eq(UserHandle.ALL),
-                    eq(AdapterService.BLUETOOTH_PERM));
+                    eq(BLUETOOTH_CONNECT), any(Bundle.class));
             verifySilenceStateIntent(intentArgument.getValue());
         }
 
@@ -144,7 +146,7 @@
             // after device is disconnected.
             verify(mAdapterService, times(++mVerifyCount)).sendBroadcastAsUser(
                     intentArgument.capture(), eq(UserHandle.ALL),
-                    eq(AdapterService.BLUETOOTH_PERM));
+                    eq(BLUETOOTH_CONNECT), any(Bundle.class));
         }
     }
 
@@ -158,7 +160,7 @@
         // Should be no intent been broadcasted
         verify(mAdapterService, times(mVerifyCount)).sendBroadcastAsUser(
                 intentArgument.capture(), eq(UserHandle.ALL),
-                eq(AdapterService.BLUETOOTH_PERM));
+                eq(BLUETOOTH_CONNECT), any(Bundle.class));
     }
 
     void verifySilenceStateIntent(Intent intent) {
diff --git a/tests/unit/src/com/android/bluetooth/btservice/activityAttribution/ActivityAttributionServiceTest.java b/tests/unit/src/com/android/bluetooth/btservice/activityAttribution/ActivityAttributionServiceTest.java
new file mode 100644
index 0000000..d27704c
--- /dev/null
+++ b/tests/unit/src/com/android/bluetooth/btservice/activityAttribution/ActivityAttributionServiceTest.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.bluetooth.btservice.activityattribution;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.os.Binder;
+import android.os.Process;
+
+import org.junit.After;
+import org.junit.Assume;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public final class ActivityAttributionServiceTest {
+    private static final String TAG = "ActivityAttributionServiceTest";
+    private ActivityAttributionService mActivityAttributionService;
+
+    @Before
+    public void setUp() {
+        Assume.assumeTrue("Ignore test when the user is not primary.", isPrimaryUser());
+        mActivityAttributionService = new ActivityAttributionService();
+        assertThat(mActivityAttributionService).isNotNull();
+    }
+
+    @After
+    public void tearDown() {
+        if (!isPrimaryUser()) {
+            return;
+        }
+        mActivityAttributionService.cleanup();
+        mActivityAttributionService = null;
+    }
+
+    private boolean isPrimaryUser() {
+        return Binder.getCallingUid() == Process.BLUETOOTH_UID;
+    }
+
+    @Test
+    public void testSetUpAndTearDown() {}
+}
diff --git a/tests/unit/src/com/android/bluetooth/btservice/bluetoothKeystore/BluetoothKeystoreServiceTest.java b/tests/unit/src/com/android/bluetooth/btservice/bluetoothKeystore/BluetoothKeystoreServiceTest.java
index 1b8044a..f65ab82 100644
--- a/tests/unit/src/com/android/bluetooth/btservice/bluetoothKeystore/BluetoothKeystoreServiceTest.java
+++ b/tests/unit/src/com/android/bluetooth/btservice/bluetoothKeystore/BluetoothKeystoreServiceTest.java
@@ -25,7 +25,7 @@
 import java.nio.file.Paths;
 import java.security.NoSuchAlgorithmException;
 import java.util.HashMap;
-import java.util.LinkedList;
+import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
 
@@ -113,7 +113,7 @@
             "LE_KEY_LID ="
             );
 
-    private List<String> mConfigData = new LinkedList<>();
+    private List<String> mConfigData = new ArrayList<>();
 
     private Map<String, String> mNameDecryptKeyResult = new HashMap<>();
 
@@ -149,7 +149,7 @@
         mBluetoothKeystoreService = null;
     }
 
-    private static boolean isPrimaryUser() {
+    private boolean isPrimaryUser() {
         return Binder.getCallingUid() == Process.BLUETOOTH_UID;
     }
 
@@ -214,7 +214,7 @@
     private boolean compareFileHash(String hashFilePathString) {
         try {
             return mBluetoothKeystoreService.compareFileHash(hashFilePathString);
-        } catch (IOException | NoSuchAlgorithmException e) {
+        } catch (InterruptedException | IOException | NoSuchAlgorithmException e) {
             return false;
         }
     }
@@ -270,16 +270,16 @@
     }
 
     @Test
-    public void testParserFileAfterDisableNiapMode() {
+    public void testParserFileAfterDisableCommonCriteriaMode() {
         // preconfiguration.
         // need to creat encrypted file.
         testParserFile();
         // created encrypted file
         Assert.assertTrue(setEncryptKeyOrRemoveKey(CONFIG_FILE_PREFIX, CONFIG_FILE_HASH));
         // clean up memory and stop thread.
-        mBluetoothKeystoreService.cleanupForNiapModeEnable();
+        mBluetoothKeystoreService.cleanupForCommonCriteriaModeEnable();
 
-        // new mBluetoothKeystoreService and the Niap mode is false.
+        // new mBluetoothKeystoreService and the Common Criteria mode is false.
         mBluetoothKeystoreService = new BluetoothKeystoreService(false);
         Assert.assertNotNull(mBluetoothKeystoreService);
 
@@ -299,11 +299,11 @@
     }
 
     @Test
-    public void testParserFileAfterDisableNiapModeWhenEnableNiapMode() {
-        testParserFileAfterDisableNiapMode();
-        mBluetoothKeystoreService.cleanupForNiapModeDisable();
+    public void testParserFileAfterDisableCommonCriteriaModeWhenEnableCommonCriteriaMode() {
+        testParserFileAfterDisableCommonCriteriaMode();
+        mBluetoothKeystoreService.cleanupForCommonCriteriaModeDisable();
 
-        // new mBluetoothKeystoreService and the Niap mode is true.
+        // new mBluetoothKeystoreService and the Common Criteria mode is true.
         mBluetoothKeystoreService = new BluetoothKeystoreService(true);
         mBluetoothKeystoreService.loadConfigData();
 
diff --git a/tests/unit/src/com/android/bluetooth/btservice/storage/DatabaseManagerTest.java b/tests/unit/src/com/android/bluetooth/btservice/storage/DatabaseManagerTest.java
index 0c71659..007e8ea 100644
--- a/tests/unit/src/com/android/bluetooth/btservice/storage/DatabaseManagerTest.java
+++ b/tests/unit/src/com/android/bluetooth/btservice/storage/DatabaseManagerTest.java
@@ -17,9 +17,9 @@
 package com.android.bluetooth.btservice.storage;
 
 import static org.junit.Assert.assertThat;
-import static org.mockito.Mockito.any;
-import static org.mockito.Mockito.anyInt;
-import static org.mockito.Mockito.anyString;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.times;
@@ -364,6 +364,23 @@
                 value, true);
         testSetGetCustomMetaCase(false, BluetoothDevice.METADATA_ENHANCED_SETTINGS_UI_URI,
                 value, true);
+        testSetGetCustomMetaCase(false, BluetoothDevice.METADATA_DEVICE_TYPE,
+                value, true);
+        testSetGetCustomMetaCase(false, BluetoothDevice.METADATA_MAIN_BATTERY,
+                value, true);
+        testSetGetCustomMetaCase(false, BluetoothDevice.METADATA_MAIN_CHARGING,
+                value, true);
+        testSetGetCustomMetaCase(false, BluetoothDevice.METADATA_MAIN_LOW_BATTERY_THRESHOLD,
+                value, true);
+        testSetGetCustomMetaCase(false,
+                BluetoothDevice.METADATA_UNTETHERED_LEFT_LOW_BATTERY_THRESHOLD,
+                value, true);
+        testSetGetCustomMetaCase(false,
+                BluetoothDevice.METADATA_UNTETHERED_RIGHT_LOW_BATTERY_THRESHOLD,
+                value, true);
+        testSetGetCustomMetaCase(false,
+                BluetoothDevice.METADATA_UNTETHERED_CASE_LOW_BATTERY_THRESHOLD,
+                value, true);
         testSetGetCustomMetaCase(false, badKey, value, false);
 
         // Device is in database
@@ -401,6 +418,23 @@
                 value, true);
         testSetGetCustomMetaCase(true, BluetoothDevice.METADATA_ENHANCED_SETTINGS_UI_URI,
                 value, true);
+        testSetGetCustomMetaCase(true, BluetoothDevice.METADATA_DEVICE_TYPE,
+                value, true);
+        testSetGetCustomMetaCase(true, BluetoothDevice.METADATA_MAIN_BATTERY,
+                value, true);
+        testSetGetCustomMetaCase(true, BluetoothDevice.METADATA_MAIN_CHARGING,
+                value, true);
+        testSetGetCustomMetaCase(true, BluetoothDevice.METADATA_MAIN_LOW_BATTERY_THRESHOLD,
+                value, true);
+        testSetGetCustomMetaCase(true,
+                BluetoothDevice.METADATA_UNTETHERED_LEFT_LOW_BATTERY_THRESHOLD,
+                value, true);
+        testSetGetCustomMetaCase(true,
+                BluetoothDevice.METADATA_UNTETHERED_RIGHT_LOW_BATTERY_THRESHOLD,
+                value, true);
+        testSetGetCustomMetaCase(true,
+                BluetoothDevice.METADATA_UNTETHERED_CASE_LOW_BATTERY_THRESHOLD,
+                value, true);
     }
 
     @Test
@@ -432,7 +466,8 @@
         Assert.assertTrue(mDatabaseManager
                 .mMetadataCache.get(mTestDevice2.getAddress()).is_active_a2dp_device);
         Assert.assertEquals(mTestDevice2, mDatabaseManager.getMostRecentlyConnectedA2dpDevice());
-        mostRecentlyConnectedDevicesOrdered = mDatabaseManager.getMostRecentlyConnectedDevices();
+        mostRecentlyConnectedDevicesOrdered =
+                mDatabaseManager.getMostRecentlyConnectedDevices();
         Assert.assertEquals(2, mostRecentlyConnectedDevicesOrdered.size());
         Assert.assertEquals(mTestDevice2, mostRecentlyConnectedDevicesOrdered.get(0));
         Assert.assertEquals(mTestDevice, mostRecentlyConnectedDevicesOrdered.get(1));
@@ -446,7 +481,8 @@
         Assert.assertFalse(mDatabaseManager
                 .mMetadataCache.get(mTestDevice2.getAddress()).is_active_a2dp_device);
         Assert.assertEquals(mTestDevice, mDatabaseManager.getMostRecentlyConnectedA2dpDevice());
-        mostRecentlyConnectedDevicesOrdered = mDatabaseManager.getMostRecentlyConnectedDevices();
+        mostRecentlyConnectedDevicesOrdered =
+                mDatabaseManager.getMostRecentlyConnectedDevices();
         Assert.assertEquals(2, mostRecentlyConnectedDevicesOrdered.size());
         Assert.assertEquals(mTestDevice, mostRecentlyConnectedDevicesOrdered.get(0));
         Assert.assertEquals(mTestDevice2, mostRecentlyConnectedDevicesOrdered.get(1));
@@ -460,7 +496,8 @@
         Assert.assertFalse(mDatabaseManager
                 .mMetadataCache.get(mTestDevice2.getAddress()).is_active_a2dp_device);
         Assert.assertNull(mDatabaseManager.getMostRecentlyConnectedA2dpDevice());
-        mostRecentlyConnectedDevicesOrdered = mDatabaseManager.getMostRecentlyConnectedDevices();
+        mostRecentlyConnectedDevicesOrdered =
+                mDatabaseManager.getMostRecentlyConnectedDevices();
         Assert.assertEquals(2, mostRecentlyConnectedDevicesOrdered.size());
         Assert.assertEquals(mTestDevice, mostRecentlyConnectedDevicesOrdered.get(0));
         Assert.assertEquals(mTestDevice2, mostRecentlyConnectedDevicesOrdered.get(1));
@@ -476,7 +513,8 @@
         Assert.assertFalse(mDatabaseManager
                 .mMetadataCache.get(mTestDevice3.getAddress()).is_active_a2dp_device);
         Assert.assertNull(mDatabaseManager.getMostRecentlyConnectedA2dpDevice());
-        mostRecentlyConnectedDevicesOrdered = mDatabaseManager.getMostRecentlyConnectedDevices();
+        mostRecentlyConnectedDevicesOrdered =
+                mDatabaseManager.getMostRecentlyConnectedDevices();
         Assert.assertEquals(3, mostRecentlyConnectedDevicesOrdered.size());
         Assert.assertEquals(mTestDevice3, mostRecentlyConnectedDevicesOrdered.get(0));
         Assert.assertEquals(mTestDevice, mostRecentlyConnectedDevicesOrdered.get(1));
@@ -493,7 +531,8 @@
         Assert.assertFalse(mDatabaseManager
                 .mMetadataCache.get(mTestDevice3.getAddress()).is_active_a2dp_device);
         Assert.assertEquals(mTestDevice, mDatabaseManager.getMostRecentlyConnectedA2dpDevice());
-        mostRecentlyConnectedDevicesOrdered = mDatabaseManager.getMostRecentlyConnectedDevices();
+        mostRecentlyConnectedDevicesOrdered =
+                mDatabaseManager.getMostRecentlyConnectedDevices();
         Assert.assertEquals(3, mostRecentlyConnectedDevicesOrdered.size());
         Assert.assertEquals(mTestDevice, mostRecentlyConnectedDevicesOrdered.get(0));
         Assert.assertEquals(mTestDevice3, mostRecentlyConnectedDevicesOrdered.get(1));
@@ -510,7 +549,8 @@
         Assert.assertFalse(mDatabaseManager
                 .mMetadataCache.get(mTestDevice3.getAddress()).is_active_a2dp_device);
         Assert.assertEquals(mTestDevice, mDatabaseManager.getMostRecentlyConnectedA2dpDevice());
-        mostRecentlyConnectedDevicesOrdered = mDatabaseManager.getMostRecentlyConnectedDevices();
+        mostRecentlyConnectedDevicesOrdered =
+                mDatabaseManager.getMostRecentlyConnectedDevices();
         Assert.assertEquals(3, mostRecentlyConnectedDevicesOrdered.size());
         Assert.assertEquals(mTestDevice3, mostRecentlyConnectedDevicesOrdered.get(0));
         Assert.assertEquals(mTestDevice, mostRecentlyConnectedDevicesOrdered.get(1));
@@ -527,7 +567,8 @@
         Assert.assertFalse(mDatabaseManager
                 .mMetadataCache.get(mTestDevice3.getAddress()).is_active_a2dp_device);
         Assert.assertEquals(mTestDevice, mDatabaseManager.getMostRecentlyConnectedA2dpDevice());
-        mostRecentlyConnectedDevicesOrdered = mDatabaseManager.getMostRecentlyConnectedDevices();
+        mostRecentlyConnectedDevicesOrdered =
+                mDatabaseManager.getMostRecentlyConnectedDevices();
         Assert.assertEquals(3, mostRecentlyConnectedDevicesOrdered.size());
         Assert.assertEquals(mTestDevice3, mostRecentlyConnectedDevicesOrdered.get(0));
         Assert.assertEquals(mTestDevice, mostRecentlyConnectedDevicesOrdered.get(1));
@@ -544,7 +585,8 @@
         Assert.assertFalse(mDatabaseManager
                 .mMetadataCache.get(mTestDevice3.getAddress()).is_active_a2dp_device);
         Assert.assertNull(mDatabaseManager.getMostRecentlyConnectedA2dpDevice());
-        mostRecentlyConnectedDevicesOrdered = mDatabaseManager.getMostRecentlyConnectedDevices();
+        mostRecentlyConnectedDevicesOrdered =
+                mDatabaseManager.getMostRecentlyConnectedDevices();
         Assert.assertEquals(3, mostRecentlyConnectedDevicesOrdered.size());
         Assert.assertEquals(mTestDevice3, mostRecentlyConnectedDevicesOrdered.get(0));
         Assert.assertEquals(mTestDevice, mostRecentlyConnectedDevicesOrdered.get(1));
@@ -561,7 +603,8 @@
         Assert.assertFalse(mDatabaseManager
                 .mMetadataCache.get(mTestDevice3.getAddress()).is_active_a2dp_device);
         Assert.assertNull(mDatabaseManager.getMostRecentlyConnectedA2dpDevice());
-        mostRecentlyConnectedDevicesOrdered = mDatabaseManager.getMostRecentlyConnectedDevices();
+        mostRecentlyConnectedDevicesOrdered =
+                mDatabaseManager.getMostRecentlyConnectedDevices();
         Assert.assertEquals(3, mostRecentlyConnectedDevicesOrdered.size());
         Assert.assertEquals(mTestDevice3, mostRecentlyConnectedDevicesOrdered.get(0));
         Assert.assertEquals(mTestDevice, mostRecentlyConnectedDevicesOrdered.get(1));
@@ -922,6 +965,72 @@
         }
     }
 
+    @Test
+    public void testDatabaseMigration_104_105() throws IOException {
+        // Create a database with version 104
+        SupportSQLiteDatabase db = testHelper.createDatabase(DB_NAME, 104);
+
+        // insert a device to the database
+        ContentValues device = new ContentValues();
+        device.put("address", TEST_BT_ADDR);
+
+        // Migrate database from 104 to 105
+        db.close();
+        db = testHelper.runMigrationsAndValidate(DB_NAME, 105, true,
+                MetadataDatabase.MIGRATION_104_105);
+        Cursor cursor = db.query("SELECT * FROM metadata");
+
+        assertHasColumn(cursor, "device_type", true);
+        assertHasColumn(cursor, "main_battery", true);
+        assertHasColumn(cursor, "main_charging", true);
+        assertHasColumn(cursor, "main_low_battery_threshold", true);
+        assertHasColumn(cursor, "untethered_right_low_battery_threshold", true);
+        assertHasColumn(cursor, "untethered_left_low_battery_threshold", true);
+        assertHasColumn(cursor, "untethered_case_low_battery_threshold", true);
+
+        while (cursor.moveToNext()) {
+            // Check the old column have the original value
+            assertColumnBlobData(cursor, "address", TEST_BT_ADDR.getBytes());
+
+            // Check the new columns were added with their default values
+            assertColumnBlobData(cursor, "device_type", null);
+            assertColumnBlobData(cursor, "main_battery", null);
+            assertColumnBlobData(cursor, "main_charging", null);
+            assertColumnBlobData(cursor, "main_low_battery_threshold", null);
+            assertColumnBlobData(cursor, "untethered_right_low_battery_threshold", null);
+            assertColumnBlobData(cursor, "untethered_left_low_battery_threshold", null);
+            assertColumnBlobData(cursor, "untethered_case_low_battery_threshold", null);
+        }
+    }
+
+    @Test
+    public void testDatabaseMigration_105_106() throws IOException {
+        String testString = "TEST STRING";
+
+        // Create a database with version 105
+        SupportSQLiteDatabase db = testHelper.createDatabase(DB_NAME, 105);
+
+        // insert a device to the database
+        ContentValues device = new ContentValues();
+        device.put("address", TEST_BT_ADDR);
+        device.put("migrated", false);
+        assertThat(db.insert("metadata", SQLiteDatabase.CONFLICT_IGNORE, device),
+                CoreMatchers.not(-1));
+
+        // Migrate database from 105 to 106
+        db.close();
+        db = testHelper.runMigrationsAndValidate(DB_NAME, 106, true,
+                MetadataDatabase.MIGRATION_105_106);
+        Cursor cursor = db.query("SELECT * FROM metadata");
+
+        assertHasColumn(cursor, "le_audio_connection_policy", true);
+
+        while (cursor.moveToNext()) {
+            // Check the new columns was added with default value
+            assertColumnIntData(cursor, "le_audio_connection_policy", 100);
+        }
+    }
+
     /**
      * Helper function to check whether the database has the expected column
      */
diff --git a/tests/unit/src/com/android/bluetooth/btservice/storage/schemas/com.android.bluetooth.btservice.storage.MetadataDatabase/105.json b/tests/unit/src/com/android/bluetooth/btservice/storage/schemas/com.android.bluetooth.btservice.storage.MetadataDatabase/105.json
new file mode 100644
index 0000000..fde2d43
--- /dev/null
+++ b/tests/unit/src/com/android/bluetooth/btservice/storage/schemas/com.android.bluetooth.btservice.storage.MetadataDatabase/105.json
@@ -0,0 +1,280 @@
+{
+  "formatVersion": 1,
+  "database": {
+    "version": 105,
+    "identityHash": "ec8e40b25f67dfcd55824abc5e801124",
+    "entities": [
+      {
+        "tableName": "metadata",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`address` TEXT NOT NULL, `migrated` INTEGER NOT NULL, `a2dpSupportsOptionalCodecs` INTEGER NOT NULL, `a2dpOptionalCodecsEnabled` INTEGER NOT NULL, `last_active_time` INTEGER NOT NULL, `is_active_a2dp_device` INTEGER NOT NULL, `a2dp_connection_policy` INTEGER, `a2dp_sink_connection_policy` INTEGER, `hfp_connection_policy` INTEGER, `hfp_client_connection_policy` INTEGER, `hid_host_connection_policy` INTEGER, `pan_connection_policy` INTEGER, `pbap_connection_policy` INTEGER, `pbap_client_connection_policy` INTEGER, `map_connection_policy` INTEGER, `sap_connection_policy` INTEGER, `hearing_aid_connection_policy` INTEGER, `map_client_connection_policy` INTEGER, `manufacturer_name` BLOB, `model_name` BLOB, `software_version` BLOB, `hardware_version` BLOB, `companion_app` BLOB, `main_icon` BLOB, `is_untethered_headset` BLOB, `untethered_left_icon` BLOB, `untethered_right_icon` BLOB, `untethered_case_icon` BLOB, `untethered_left_battery` BLOB, `untethered_right_battery` BLOB, `untethered_case_battery` BLOB, `untethered_left_charging` BLOB, `untethered_right_charging` BLOB, `untethered_case_charging` BLOB, `enhanced_settings_ui_uri` BLOB, `device_type` BLOB, `main_battery` BLOB, `main_charging` BLOB, `main_low_battery_threshold` BLOB, `untethered_left_low_battery_threshold` BLOB, `untethered_right_low_battery_threshold` BLOB, `untethered_case_low_battery_threshold` BLOB, PRIMARY KEY(`address`))",
+        "fields": [
+          {
+            "fieldPath": "address",
+            "columnName": "address",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "migrated",
+            "columnName": "migrated",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "a2dpSupportsOptionalCodecs",
+            "columnName": "a2dpSupportsOptionalCodecs",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "a2dpOptionalCodecsEnabled",
+            "columnName": "a2dpOptionalCodecsEnabled",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "last_active_time",
+            "columnName": "last_active_time",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "is_active_a2dp_device",
+            "columnName": "is_active_a2dp_device",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "profileConnectionPolicies.a2dp_connection_policy",
+            "columnName": "a2dp_connection_policy",
+            "affinity": "INTEGER",
+            "notNull": false
+          },
+          {
+            "fieldPath": "profileConnectionPolicies.a2dp_sink_connection_policy",
+            "columnName": "a2dp_sink_connection_policy",
+            "affinity": "INTEGER",
+            "notNull": false
+          },
+          {
+            "fieldPath": "profileConnectionPolicies.hfp_connection_policy",
+            "columnName": "hfp_connection_policy",
+            "affinity": "INTEGER",
+            "notNull": false
+          },
+          {
+            "fieldPath": "profileConnectionPolicies.hfp_client_connection_policy",
+            "columnName": "hfp_client_connection_policy",
+            "affinity": "INTEGER",
+            "notNull": false
+          },
+          {
+            "fieldPath": "profileConnectionPolicies.hid_host_connection_policy",
+            "columnName": "hid_host_connection_policy",
+            "affinity": "INTEGER",
+            "notNull": false
+          },
+          {
+            "fieldPath": "profileConnectionPolicies.pan_connection_policy",
+            "columnName": "pan_connection_policy",
+            "affinity": "INTEGER",
+            "notNull": false
+          },
+          {
+            "fieldPath": "profileConnectionPolicies.pbap_connection_policy",
+            "columnName": "pbap_connection_policy",
+            "affinity": "INTEGER",
+            "notNull": false
+          },
+          {
+            "fieldPath": "profileConnectionPolicies.pbap_client_connection_policy",
+            "columnName": "pbap_client_connection_policy",
+            "affinity": "INTEGER",
+            "notNull": false
+          },
+          {
+            "fieldPath": "profileConnectionPolicies.map_connection_policy",
+            "columnName": "map_connection_policy",
+            "affinity": "INTEGER",
+            "notNull": false
+          },
+          {
+            "fieldPath": "profileConnectionPolicies.sap_connection_policy",
+            "columnName": "sap_connection_policy",
+            "affinity": "INTEGER",
+            "notNull": false
+          },
+          {
+            "fieldPath": "profileConnectionPolicies.hearing_aid_connection_policy",
+            "columnName": "hearing_aid_connection_policy",
+            "affinity": "INTEGER",
+            "notNull": false
+          },
+          {
+            "fieldPath": "profileConnectionPolicies.map_client_connection_policy",
+            "columnName": "map_client_connection_policy",
+            "affinity": "INTEGER",
+            "notNull": false
+          },
+          {
+            "fieldPath": "publicMetadata.manufacturer_name",
+            "columnName": "manufacturer_name",
+            "affinity": "BLOB",
+            "notNull": false
+          },
+          {
+            "fieldPath": "publicMetadata.model_name",
+            "columnName": "model_name",
+            "affinity": "BLOB",
+            "notNull": false
+          },
+          {
+            "fieldPath": "publicMetadata.software_version",
+            "columnName": "software_version",
+            "affinity": "BLOB",
+            "notNull": false
+          },
+          {
+            "fieldPath": "publicMetadata.hardware_version",
+            "columnName": "hardware_version",
+            "affinity": "BLOB",
+            "notNull": false
+          },
+          {
+            "fieldPath": "publicMetadata.companion_app",
+            "columnName": "companion_app",
+            "affinity": "BLOB",
+            "notNull": false
+          },
+          {
+            "fieldPath": "publicMetadata.main_icon",
+            "columnName": "main_icon",
+            "affinity": "BLOB",
+            "notNull": false
+          },
+          {
+            "fieldPath": "publicMetadata.is_untethered_headset",
+            "columnName": "is_untethered_headset",
+            "affinity": "BLOB",
+            "notNull": false
+          },
+          {
+            "fieldPath": "publicMetadata.untethered_left_icon",
+            "columnName": "untethered_left_icon",
+            "affinity": "BLOB",
+            "notNull": false
+          },
+          {
+            "fieldPath": "publicMetadata.untethered_right_icon",
+            "columnName": "untethered_right_icon",
+            "affinity": "BLOB",
+            "notNull": false
+          },
+          {
+            "fieldPath": "publicMetadata.untethered_case_icon",
+            "columnName": "untethered_case_icon",
+            "affinity": "BLOB",
+            "notNull": false
+          },
+          {
+            "fieldPath": "publicMetadata.untethered_left_battery",
+            "columnName": "untethered_left_battery",
+            "affinity": "BLOB",
+            "notNull": false
+          },
+          {
+            "fieldPath": "publicMetadata.untethered_right_battery",
+            "columnName": "untethered_right_battery",
+            "affinity": "BLOB",
+            "notNull": false
+          },
+          {
+            "fieldPath": "publicMetadata.untethered_case_battery",
+            "columnName": "untethered_case_battery",
+            "affinity": "BLOB",
+            "notNull": false
+          },
+          {
+            "fieldPath": "publicMetadata.untethered_left_charging",
+            "columnName": "untethered_left_charging",
+            "affinity": "BLOB",
+            "notNull": false
+          },
+          {
+            "fieldPath": "publicMetadata.untethered_right_charging",
+            "columnName": "untethered_right_charging",
+            "affinity": "BLOB",
+            "notNull": false
+          },
+          {
+            "fieldPath": "publicMetadata.untethered_case_charging",
+            "columnName": "untethered_case_charging",
+            "affinity": "BLOB",
+            "notNull": false
+          },
+          {
+            "fieldPath": "publicMetadata.enhanced_settings_ui_uri",
+            "columnName": "enhanced_settings_ui_uri",
+            "affinity": "BLOB",
+            "notNull": false
+          },
+          {
+            "fieldPath": "publicMetadata.device_type",
+            "columnName": "device_type",
+            "affinity": "BLOB",
+            "notNull": false
+          },
+          {
+            "fieldPath": "publicMetadata.main_battery",
+            "columnName": "main_battery",
+            "affinity": "BLOB",
+            "notNull": false
+          },
+          {
+            "fieldPath": "publicMetadata.main_charging",
+            "columnName": "main_charging",
+            "affinity": "BLOB",
+            "notNull": false
+          },
+          {
+            "fieldPath": "publicMetadata.main_low_battery_threshold",
+            "columnName": "main_low_battery_threshold",
+            "affinity": "BLOB",
+            "notNull": false
+          },
+          {
+            "fieldPath": "publicMetadata.untethered_left_low_battery_threshold",
+            "columnName": "untethered_left_low_battery_threshold",
+            "affinity": "BLOB",
+            "notNull": false
+          },
+          {
+            "fieldPath": "publicMetadata.untethered_right_low_battery_threshold",
+            "columnName": "untethered_right_low_battery_threshold",
+            "affinity": "BLOB",
+            "notNull": false
+          },
+          {
+            "fieldPath": "publicMetadata.untethered_case_low_battery_threshold",
+            "columnName": "untethered_case_low_battery_threshold",
+            "affinity": "BLOB",
+            "notNull": false
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "address"
+          ],
+          "autoGenerate": false
+        },
+        "indices": [],
+        "foreignKeys": []
+      }
+    ],
+    "views": [],
+    "setupQueries": [
+      "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
+      "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'ec8e40b25f67dfcd55824abc5e801124')"
+    ]
+  }
+}
\ No newline at end of file
diff --git a/tests/unit/src/com/android/bluetooth/btservice/storage/schemas/com.android.bluetooth.btservice.storage.MetadataDatabase/106.json b/tests/unit/src/com/android/bluetooth/btservice/storage/schemas/com.android.bluetooth.btservice.storage.MetadataDatabase/106.json
new file mode 100644
index 0000000..b289acc
--- /dev/null
+++ b/tests/unit/src/com/android/bluetooth/btservice/storage/schemas/com.android.bluetooth.btservice.storage.MetadataDatabase/106.json
@@ -0,0 +1,286 @@
+{
+  "formatVersion": 1,
+  "database": {
+    "version": 106,
+    "identityHash": "e2c67aa60536bcccd99f827ebf6ca671",
+    "entities": [
+      {
+        "tableName": "metadata",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`address` TEXT NOT NULL, `migrated` INTEGER NOT NULL, `a2dpSupportsOptionalCodecs` INTEGER NOT NULL, `a2dpOptionalCodecsEnabled` INTEGER NOT NULL, `last_active_time` INTEGER NOT NULL, `is_active_a2dp_device` INTEGER NOT NULL, `a2dp_connection_policy` INTEGER, `a2dp_sink_connection_policy` INTEGER, `hfp_connection_policy` INTEGER, `hfp_client_connection_policy` INTEGER, `hid_host_connection_policy` INTEGER, `pan_connection_policy` INTEGER, `pbap_connection_policy` INTEGER, `pbap_client_connection_policy` INTEGER, `map_connection_policy` INTEGER, `sap_connection_policy` INTEGER, `hearing_aid_connection_policy` INTEGER, `map_client_connection_policy` INTEGER, `le_audio_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, `device_type` BLOB, `main_battery` BLOB, `main_charging` BLOB, `main_low_battery_threshold` BLOB, `untethered_left_low_battery_threshold` BLOB, `untethered_right_low_battery_threshold` BLOB, `untethered_case_low_battery_threshold` BLOB, PRIMARY KEY(`address`))",
+        "fields": [
+          {
+            "fieldPath": "address",
+            "columnName": "address",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "migrated",
+            "columnName": "migrated",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "a2dpSupportsOptionalCodecs",
+            "columnName": "a2dpSupportsOptionalCodecs",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "a2dpOptionalCodecsEnabled",
+            "columnName": "a2dpOptionalCodecsEnabled",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "last_active_time",
+            "columnName": "last_active_time",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "is_active_a2dp_device",
+            "columnName": "is_active_a2dp_device",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "profileConnectionPolicies.a2dp_connection_policy",
+            "columnName": "a2dp_connection_policy",
+            "affinity": "INTEGER",
+            "notNull": false
+          },
+          {
+            "fieldPath": "profileConnectionPolicies.a2dp_sink_connection_policy",
+            "columnName": "a2dp_sink_connection_policy",
+            "affinity": "INTEGER",
+            "notNull": false
+          },
+          {
+            "fieldPath": "profileConnectionPolicies.hfp_connection_policy",
+            "columnName": "hfp_connection_policy",
+            "affinity": "INTEGER",
+            "notNull": false
+          },
+          {
+            "fieldPath": "profileConnectionPolicies.hfp_client_connection_policy",
+            "columnName": "hfp_client_connection_policy",
+            "affinity": "INTEGER",
+            "notNull": false
+          },
+          {
+            "fieldPath": "profileConnectionPolicies.hid_host_connection_policy",
+            "columnName": "hid_host_connection_policy",
+            "affinity": "INTEGER",
+            "notNull": false
+          },
+          {
+            "fieldPath": "profileConnectionPolicies.pan_connection_policy",
+            "columnName": "pan_connection_policy",
+            "affinity": "INTEGER",
+            "notNull": false
+          },
+          {
+            "fieldPath": "profileConnectionPolicies.pbap_connection_policy",
+            "columnName": "pbap_connection_policy",
+            "affinity": "INTEGER",
+            "notNull": false
+          },
+          {
+            "fieldPath": "profileConnectionPolicies.pbap_client_connection_policy",
+            "columnName": "pbap_client_connection_policy",
+            "affinity": "INTEGER",
+            "notNull": false
+          },
+          {
+            "fieldPath": "profileConnectionPolicies.map_connection_policy",
+            "columnName": "map_connection_policy",
+            "affinity": "INTEGER",
+            "notNull": false
+          },
+          {
+            "fieldPath": "profileConnectionPolicies.sap_connection_policy",
+            "columnName": "sap_connection_policy",
+            "affinity": "INTEGER",
+            "notNull": false
+          },
+          {
+            "fieldPath": "profileConnectionPolicies.hearing_aid_connection_policy",
+            "columnName": "hearing_aid_connection_policy",
+            "affinity": "INTEGER",
+            "notNull": false
+          },
+          {
+            "fieldPath": "profileConnectionPolicies.map_client_connection_policy",
+            "columnName": "map_client_connection_policy",
+            "affinity": "INTEGER",
+            "notNull": false
+          },
+          {
+            "fieldPath": "profileConnectionPolicies.le_audio_connection_policy",
+            "columnName": "le_audio_connection_policy",
+            "affinity": "INTEGER",
+            "notNull": false
+          },
+          {
+            "fieldPath": "publicMetadata.manufacturer_name",
+            "columnName": "manufacturer_name",
+            "affinity": "BLOB",
+            "notNull": false
+          },
+          {
+            "fieldPath": "publicMetadata.model_name",
+            "columnName": "model_name",
+            "affinity": "BLOB",
+            "notNull": false
+          },
+          {
+            "fieldPath": "publicMetadata.software_version",
+            "columnName": "software_version",
+            "affinity": "BLOB",
+            "notNull": false
+          },
+          {
+            "fieldPath": "publicMetadata.hardware_version",
+            "columnName": "hardware_version",
+            "affinity": "BLOB",
+            "notNull": false
+          },
+          {
+            "fieldPath": "publicMetadata.companion_app",
+            "columnName": "companion_app",
+            "affinity": "BLOB",
+            "notNull": false
+          },
+          {
+            "fieldPath": "publicMetadata.main_icon",
+            "columnName": "main_icon",
+            "affinity": "BLOB",
+            "notNull": false
+          },
+          {
+            "fieldPath": "publicMetadata.is_untethered_headset",
+            "columnName": "is_untethered_headset",
+            "affinity": "BLOB",
+            "notNull": false
+          },
+          {
+            "fieldPath": "publicMetadata.untethered_left_icon",
+            "columnName": "untethered_left_icon",
+            "affinity": "BLOB",
+            "notNull": false
+          },
+          {
+            "fieldPath": "publicMetadata.untethered_right_icon",
+            "columnName": "untethered_right_icon",
+            "affinity": "BLOB",
+            "notNull": false
+          },
+          {
+            "fieldPath": "publicMetadata.untethered_case_icon",
+            "columnName": "untethered_case_icon",
+            "affinity": "BLOB",
+            "notNull": false
+          },
+          {
+            "fieldPath": "publicMetadata.untethered_left_battery",
+            "columnName": "untethered_left_battery",
+            "affinity": "BLOB",
+            "notNull": false
+          },
+          {
+            "fieldPath": "publicMetadata.untethered_right_battery",
+            "columnName": "untethered_right_battery",
+            "affinity": "BLOB",
+            "notNull": false
+          },
+          {
+            "fieldPath": "publicMetadata.untethered_case_battery",
+            "columnName": "untethered_case_battery",
+            "affinity": "BLOB",
+            "notNull": false
+          },
+          {
+            "fieldPath": "publicMetadata.untethered_left_charging",
+            "columnName": "untethered_left_charging",
+            "affinity": "BLOB",
+            "notNull": false
+          },
+          {
+            "fieldPath": "publicMetadata.untethered_right_charging",
+            "columnName": "untethered_right_charging",
+            "affinity": "BLOB",
+            "notNull": false
+          },
+          {
+            "fieldPath": "publicMetadata.untethered_case_charging",
+            "columnName": "untethered_case_charging",
+            "affinity": "BLOB",
+            "notNull": false
+          },
+          {
+            "fieldPath": "publicMetadata.enhanced_settings_ui_uri",
+            "columnName": "enhanced_settings_ui_uri",
+            "affinity": "BLOB",
+            "notNull": false
+          },
+          {
+            "fieldPath": "publicMetadata.device_type",
+            "columnName": "device_type",
+            "affinity": "BLOB",
+            "notNull": false
+          },
+          {
+            "fieldPath": "publicMetadata.main_battery",
+            "columnName": "main_battery",
+            "affinity": "BLOB",
+            "notNull": false
+          },
+          {
+            "fieldPath": "publicMetadata.main_charging",
+            "columnName": "main_charging",
+            "affinity": "BLOB",
+            "notNull": false
+          },
+          {
+            "fieldPath": "publicMetadata.main_low_battery_threshold",
+            "columnName": "main_low_battery_threshold",
+            "affinity": "BLOB",
+            "notNull": false
+          },
+          {
+            "fieldPath": "publicMetadata.untethered_left_low_battery_threshold",
+            "columnName": "untethered_left_low_battery_threshold",
+            "affinity": "BLOB",
+            "notNull": false
+          },
+          {
+            "fieldPath": "publicMetadata.untethered_right_low_battery_threshold",
+            "columnName": "untethered_right_low_battery_threshold",
+            "affinity": "BLOB",
+            "notNull": false
+          },
+          {
+            "fieldPath": "publicMetadata.untethered_case_low_battery_threshold",
+            "columnName": "untethered_case_low_battery_threshold",
+            "affinity": "BLOB",
+            "notNull": false
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "address"
+          ],
+          "autoGenerate": false
+        },
+        "indices": [],
+        "foreignKeys": []
+      }
+    ],
+    "views": [],
+    "setupQueries": [
+      "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
+      "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'e2c67aa60536bcccd99f827ebf6ca671')"
+    ]
+  }
+}
\ No newline at end of file
diff --git a/tests/unit/src/com/android/bluetooth/gatt/GattServiceTest.java b/tests/unit/src/com/android/bluetooth/gatt/GattServiceTest.java
index 6882b50..58605c7 100644
--- a/tests/unit/src/com/android/bluetooth/gatt/GattServiceTest.java
+++ b/tests/unit/src/com/android/bluetooth/gatt/GattServiceTest.java
@@ -44,6 +44,7 @@
                 mTargetContext.getResources().getBoolean(R.bool.profile_supported_gatt));
         MockitoAnnotations.initMocks(this);
         TestUtils.setAdapterService(mAdapterService);
+        doReturn(true).when(mAdapterService).isStartedProfile(anyString());
         TestUtils.startService(mServiceRule, GattService.class);
         mService = GattService.getGattService();
         Assert.assertNotNull(mService);
@@ -54,6 +55,7 @@
         if (!mTargetContext.getResources().getBoolean(R.bool.profile_supported_gatt)) {
             return;
         }
+        doReturn(false).when(mAdapterService).isStartedProfile(anyString());
         TestUtils.stopService(mServiceRule, GattService.class);
         mService = GattService.getGattService();
         Assert.assertNull(mService);
@@ -69,6 +71,7 @@
     public void testServiceUpAndDown() throws Exception {
         for (int i = 0; i < TIMES_UP_AND_DOWN; i++) {
             GattService gattService = GattService.getGattService();
+            doReturn(false).when(mAdapterService).isStartedProfile(anyString());
             TestUtils.stopService(mServiceRule, GattService.class);
             mService = GattService.getGattService();
             Assert.assertNull(mService);
@@ -76,6 +79,7 @@
             TestUtils.clearAdapterService(mAdapterService);
             reset(mAdapterService);
             TestUtils.setAdapterService(mAdapterService);
+            doReturn(true).when(mAdapterService).isStartedProfile(anyString());
             TestUtils.startService(mServiceRule, GattService.class);
             mService = GattService.getGattService();
             Assert.assertNotNull(mService);
@@ -89,5 +93,4 @@
         });
         Assert.assertEquals(99700000000L, timestampNanos);
     }
-
 }
diff --git a/tests/unit/src/com/android/bluetooth/hearingaid/HearingAidServiceTest.java b/tests/unit/src/com/android/bluetooth/hearingaid/HearingAidServiceTest.java
index a1c938c..dab3099 100644
--- a/tests/unit/src/com/android/bluetooth/hearingaid/HearingAidServiceTest.java
+++ b/tests/unit/src/com/android/bluetooth/hearingaid/HearingAidServiceTest.java
@@ -91,6 +91,8 @@
         }
 
         TestUtils.setAdapterService(mAdapterService);
+        doReturn(mDatabaseManager).when(mAdapterService).getDatabase();
+        doReturn(true, false).when(mAdapterService).isStartedProfile(anyString());
 
         mAdapter = BluetoothAdapter.getDefaultAdapter();
 
@@ -216,21 +218,18 @@
      */
     @Test
     public void testGetSetPriority() {
-        when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager);
         when(mDatabaseManager.getProfileConnectionPolicy(mLeftDevice, BluetoothProfile.HEARING_AID))
                 .thenReturn(BluetoothProfile.CONNECTION_POLICY_UNKNOWN);
         Assert.assertEquals("Initial device priority",
                 BluetoothProfile.CONNECTION_POLICY_UNKNOWN,
                 mService.getConnectionPolicy(mLeftDevice));
 
-        when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager);
         when(mDatabaseManager.getProfileConnectionPolicy(mLeftDevice, BluetoothProfile.HEARING_AID))
                 .thenReturn(BluetoothProfile.CONNECTION_POLICY_FORBIDDEN);
         Assert.assertEquals("Setting device priority to PRIORITY_OFF",
                 BluetoothProfile.CONNECTION_POLICY_FORBIDDEN,
                 mService.getConnectionPolicy(mLeftDevice));
 
-        when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager);
         when(mDatabaseManager.getProfileConnectionPolicy(mLeftDevice, BluetoothProfile.HEARING_AID))
                 .thenReturn(BluetoothProfile.CONNECTION_POLICY_ALLOWED);
         Assert.assertEquals("Setting device priority to PRIORITY_ON",
@@ -285,7 +284,6 @@
     @Test
     public void testOutgoingConnectMissingHearingAidUuid() {
         // Update the device priority so okToConnect() returns true
-        when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager);
         when(mDatabaseManager.getProfileConnectionPolicy(mLeftDevice, BluetoothProfile.HEARING_AID))
                 .thenReturn(BluetoothProfile.CONNECTION_POLICY_ALLOWED);
         when(mDatabaseManager
@@ -314,7 +312,6 @@
         doReturn(true).when(mNativeInterface).disconnectHearingAid(any(BluetoothDevice.class));
 
         // Set the device priority to PRIORITY_OFF so connect() should fail
-        when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager);
         when(mDatabaseManager
                 .getProfileConnectionPolicy(mLeftDevice, BluetoothProfile.HEARING_AID))
                 .thenReturn(BluetoothProfile.CONNECTION_POLICY_FORBIDDEN);
@@ -329,7 +326,6 @@
     @Test
     public void testOutgoingConnectTimeout() {
         // Update the device priority so okToConnect() returns true
-        when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager);
         when(mDatabaseManager
                 .getProfileConnectionPolicy(mLeftDevice, BluetoothProfile.HEARING_AID))
                 .thenReturn(BluetoothProfile.CONNECTION_POLICY_ALLOWED);
@@ -367,7 +363,6 @@
         // Update hiSyncId map
         getHiSyncIdFromNative();
         // Update the device priority so okToConnect() returns true
-        when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager);
         when(mDatabaseManager
                 .getProfileConnectionPolicy(mLeftDevice, BluetoothProfile.HEARING_AID))
                 .thenReturn(BluetoothProfile.CONNECTION_POLICY_ALLOWED);
@@ -402,7 +397,6 @@
         // Update hiSyncId map
         getHiSyncIdFromNative();
         // Update the device priority so okToConnect() returns true
-        when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager);
         when(mDatabaseManager
                 .getProfileConnectionPolicy(mLeftDevice, BluetoothProfile.HEARING_AID))
                 .thenReturn(BluetoothProfile.CONNECTION_POLICY_ALLOWED);
@@ -469,7 +463,6 @@
         // Update hiSyncId map
         getHiSyncIdFromNative();
         // Update the device priority so okToConnect() returns true
-        when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager);
         when(mDatabaseManager
                 .getProfileConnectionPolicy(mLeftDevice, BluetoothProfile.HEARING_AID))
                 .thenReturn(BluetoothProfile.CONNECTION_POLICY_ALLOWED);
@@ -589,7 +582,6 @@
     @Test
     public void testCreateStateMachineStackEvents() {
         // Update the device priority so okToConnect() returns true
-        when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager);
         when(mDatabaseManager
                 .getProfileConnectionPolicy(mLeftDevice, BluetoothProfile.HEARING_AID))
                 .thenReturn(BluetoothProfile.CONNECTION_POLICY_ALLOWED);
@@ -657,7 +649,6 @@
     @Test
     public void testDeleteStateMachineUnbondEvents() {
         // Update the device priority so okToConnect() returns true
-        when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager);
         when(mDatabaseManager
                 .getProfileConnectionPolicy(mLeftDevice, BluetoothProfile.HEARING_AID))
                 .thenReturn(BluetoothProfile.CONNECTION_POLICY_ALLOWED);
@@ -721,7 +712,6 @@
     @Test
     public void testDeleteStateMachineDisconnectEvents() {
         // Update the device priority so okToConnect() returns true
-        when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager);
         when(mDatabaseManager
                 .getProfileConnectionPolicy(mLeftDevice, BluetoothProfile.HEARING_AID))
                 .thenReturn(BluetoothProfile.CONNECTION_POLICY_ALLOWED);
@@ -773,7 +763,6 @@
         // Update hiSyncId map
         getHiSyncIdFromNative();
         // Update the device priority so okToConnect() returns true
-        when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager);
         when(mDatabaseManager
                 .getProfileConnectionPolicy(mLeftDevice, BluetoothProfile.HEARING_AID))
                 .thenReturn(BluetoothProfile.CONNECTION_POLICY_ALLOWED);
@@ -810,7 +799,6 @@
         // Update hiSyncId map
         getHiSyncIdFromNative();
         // Update the device priority so okToConnect() returns true
-        when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager);
         when(mDatabaseManager
                 .getProfileConnectionPolicy(mLeftDevice, BluetoothProfile.HEARING_AID))
                 .thenReturn(BluetoothProfile.CONNECTION_POLICY_ALLOWED);
@@ -846,7 +834,6 @@
     @Test
     public void firstTimeConnection_shouldConnectToBothDevices() {
         // Update the device priority so okToConnect() returns true
-        when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager);
         when(mDatabaseManager
                 .getProfileConnectionPolicy(mLeftDevice, BluetoothProfile.HEARING_AID))
                 .thenReturn(BluetoothProfile.CONNECTION_POLICY_ALLOWED);
@@ -931,7 +918,6 @@
     @Test
     public void getHiSyncId_afterFirstDeviceConnected() {
         // Update the device priority so okToConnect() returns true
-        when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager);
         when(mDatabaseManager
                 .getProfileConnectionPolicy(mLeftDevice, BluetoothProfile.HEARING_AID))
                 .thenReturn(BluetoothProfile.CONNECTION_POLICY_ALLOWED);
@@ -1026,7 +1012,6 @@
         List<BluetoothDevice> prevConnectedDevices = mService.getConnectedDevices();
 
         // Update the device priority so okToConnect() returns true
-        when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager);
         when(mDatabaseManager.getProfileConnectionPolicy(device, BluetoothProfile.HEARING_AID))
                 .thenReturn(BluetoothProfile.CONNECTION_POLICY_ALLOWED);
         doReturn(true).when(mNativeInterface).connectHearingAid(device);
@@ -1095,7 +1080,6 @@
     private void testOkToConnectCase(BluetoothDevice device, int bondState, int priority,
             boolean expected) {
         doReturn(bondState).when(mAdapterService).getBondState(device);
-        when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager);
         when(mDatabaseManager.getProfileConnectionPolicy(device, BluetoothProfile.HEARING_AID))
                 .thenReturn(priority);
         Assert.assertEquals(expected, mService.okToConnect(device));
diff --git a/tests/unit/src/com/android/bluetooth/hearingaid/HearingAidStateMachineTest.java b/tests/unit/src/com/android/bluetooth/hearingaid/HearingAidStateMachineTest.java
index 2512c5e..b8efd47 100644
--- a/tests/unit/src/com/android/bluetooth/hearingaid/HearingAidStateMachineTest.java
+++ b/tests/unit/src/com/android/bluetooth/hearingaid/HearingAidStateMachineTest.java
@@ -23,6 +23,7 @@
 import android.bluetooth.BluetoothProfile;
 import android.content.Context;
 import android.content.Intent;
+import android.os.Bundle;
 import android.os.HandlerThread;
 
 import androidx.test.InstrumentationRegistry;
@@ -128,7 +129,7 @@
 
         // Verify that no connection state broadcast is executed
         verify(mHearingAidService, after(TIMEOUT_MS).never()).sendBroadcast(any(Intent.class),
-                anyString());
+                anyString(), any(Bundle.class));
         // Check that we are in Disconnected state
         Assert.assertThat(mHearingAidStateMachine.getCurrentState(),
                 IsInstanceOf.instanceOf(HearingAidStateMachine.Disconnected.class));
@@ -151,7 +152,7 @@
         // Verify that one connection state broadcast is executed
         ArgumentCaptor<Intent> intentArgument1 = ArgumentCaptor.forClass(Intent.class);
         verify(mHearingAidService, timeout(TIMEOUT_MS).times(1)).sendBroadcast(
-                intentArgument1.capture(), anyString());
+                intentArgument1.capture(), anyString(), any(Bundle.class));
         Assert.assertEquals(BluetoothProfile.STATE_CONNECTING,
                 intentArgument1.getValue().getIntExtra(BluetoothProfile.EXTRA_STATE, -1));
 
@@ -170,7 +171,7 @@
         // - two calls to broadcastConnectionState(): Disconnected -> Conecting -> Connected
         ArgumentCaptor<Intent> intentArgument2 = ArgumentCaptor.forClass(Intent.class);
         verify(mHearingAidService, timeout(TIMEOUT_MS).times(2)).sendBroadcast(
-                intentArgument2.capture(), anyString());
+                intentArgument2.capture(), anyString(), any(Bundle.class));
         // Check that we are in Connected state
         Assert.assertThat(mHearingAidStateMachine.getCurrentState(),
                 IsInstanceOf.instanceOf(HearingAidStateMachine.Connected.class));
@@ -195,7 +196,7 @@
         ArgumentCaptor<Intent> intentArgument1 = ArgumentCaptor.forClass(Intent.class);
         verify(mHearingAidService, timeout(TIMEOUT_MS).times(1)).sendBroadcast(
                 intentArgument1.capture(),
-                anyString());
+                anyString(), any(Bundle.class));
         Assert.assertEquals(BluetoothProfile.STATE_CONNECTING,
                 intentArgument1.getValue().getIntExtra(BluetoothProfile.EXTRA_STATE, -1));
 
@@ -206,14 +207,15 @@
         // Verify that one connection state broadcast is executed
         ArgumentCaptor<Intent> intentArgument2 = ArgumentCaptor.forClass(Intent.class);
         verify(mHearingAidService, timeout(HearingAidStateMachine.sConnectTimeoutMs * 2).times(
-                2)).sendBroadcast(intentArgument2.capture(), anyString());
+                2)).sendBroadcast(intentArgument2.capture(), anyString(),
+                any(Bundle.class));
         Assert.assertEquals(BluetoothProfile.STATE_DISCONNECTED,
                 intentArgument2.getValue().getIntExtra(BluetoothProfile.EXTRA_STATE, -1));
 
         // Check that we are in Disconnected state
         Assert.assertThat(mHearingAidStateMachine.getCurrentState(),
                 IsInstanceOf.instanceOf(HearingAidStateMachine.Disconnected.class));
-        verify(mHearingAidNativeInterface).addToWhiteList(eq(mTestDevice));
+        verify(mHearingAidNativeInterface).addToAcceptlist(eq(mTestDevice));
     }
 
     /**
@@ -239,7 +241,7 @@
         ArgumentCaptor<Intent> intentArgument1 = ArgumentCaptor.forClass(Intent.class);
         verify(mHearingAidService, timeout(TIMEOUT_MS).times(1)).sendBroadcast(
                 intentArgument1.capture(),
-                anyString());
+                anyString(), any(Bundle.class));
         Assert.assertEquals(BluetoothProfile.STATE_CONNECTING,
                 intentArgument1.getValue().getIntExtra(BluetoothProfile.EXTRA_STATE, -1));
 
@@ -250,13 +252,14 @@
         // Verify that one connection state broadcast is executed
         ArgumentCaptor<Intent> intentArgument2 = ArgumentCaptor.forClass(Intent.class);
         verify(mHearingAidService, timeout(HearingAidStateMachine.sConnectTimeoutMs * 2).times(
-                2)).sendBroadcast(intentArgument2.capture(), anyString());
+                2)).sendBroadcast(intentArgument2.capture(), anyString(),
+                any(Bundle.class));
         Assert.assertEquals(BluetoothProfile.STATE_DISCONNECTED,
                 intentArgument2.getValue().getIntExtra(BluetoothProfile.EXTRA_STATE, -1));
 
         // Check that we are in Disconnected state
         Assert.assertThat(mHearingAidStateMachine.getCurrentState(),
                 IsInstanceOf.instanceOf(HearingAidStateMachine.Disconnected.class));
-        verify(mHearingAidNativeInterface).addToWhiteList(eq(mTestDevice));
+        verify(mHearingAidNativeInterface).addToAcceptlist(eq(mTestDevice));
     }
 }
diff --git a/tests/unit/src/com/android/bluetooth/hfp/HeadsetPhoneStateTest.java b/tests/unit/src/com/android/bluetooth/hfp/HeadsetPhoneStateTest.java
index cfea7fa..51e6a26 100644
--- a/tests/unit/src/com/android/bluetooth/hfp/HeadsetPhoneStateTest.java
+++ b/tests/unit/src/com/android/bluetooth/hfp/HeadsetPhoneStateTest.java
@@ -69,6 +69,7 @@
         }
         PropertyInvalidatedCache.disableForTestMode();
         MockitoAnnotations.initMocks(this);
+        SubscriptionManager.disableCaching();
         TelephonyManager.disableServiceHandleCaching();
         // Mock SubscriptionManager.getDefaultSubscriptionId() to return a valid value
         when(mISub.getDefaultSubId()).thenReturn(SubscriptionManager.DEFAULT_SUBSCRIPTION_ID);
diff --git a/tests/unit/src/com/android/bluetooth/hfp/HeadsetServiceAndStateMachineTest.java b/tests/unit/src/com/android/bluetooth/hfp/HeadsetServiceAndStateMachineTest.java
index 149c64f..a265b67 100644
--- a/tests/unit/src/com/android/bluetooth/hfp/HeadsetServiceAndStateMachineTest.java
+++ b/tests/unit/src/com/android/bluetooth/hfp/HeadsetServiceAndStateMachineTest.java
@@ -167,6 +167,8 @@
         doReturn(MAX_HEADSET_CONNECTIONS).when(mAdapterService).getMaxConnectedAudioDevices();
         doReturn(new ParcelUuid[]{BluetoothUuid.HFP}).when(mAdapterService)
                 .getRemoteUuids(any(BluetoothDevice.class));
+        doReturn(mDatabaseManager).when(mAdapterService).getDatabase();
+        doReturn(true, false).when(mAdapterService).isStartedProfile(anyString());
         // We cannot mock HeadsetObjectsFactory.getInstance() with Mockito.
         // Hence we need to use reflection to call a private method to
         // initialize properly the HeadsetObjectsFactory.sInstance field.
@@ -184,7 +186,6 @@
         doAnswer(invocation -> mBondedDevices.toArray(new BluetoothDevice[]{})).when(
                 mAdapterService).getBondedDevices();
         // Mock system interface
-        doNothing().when(mSystemInterface).init();
         doNothing().when(mSystemInterface).stop();
         when(mSystemInterface.getHeadsetPhoneState()).thenReturn(mPhoneState);
         when(mSystemInterface.getAudioManager()).thenReturn(mAudioManager);
@@ -278,7 +279,6 @@
     @Test
     public void testConnectFromApi() {
         BluetoothDevice device = TestUtils.getTestDevice(mAdapter, 0);
-        when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager);
         when(mDatabaseManager.getProfileConnectionPolicy(device, BluetoothProfile.HEADSET))
                 .thenReturn(BluetoothProfile.CONNECTION_POLICY_UNKNOWN);
         mBondedDevices.add(device);
@@ -322,7 +322,6 @@
     @Test
     public void testUnbondDevice_disconnectBeforeUnbond() {
         BluetoothDevice device = TestUtils.getTestDevice(mAdapter, 0);
-        when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager);
         when(mDatabaseManager.getProfileConnectionPolicy(device, BluetoothProfile.HEADSET))
                 .thenReturn(BluetoothProfile.CONNECTION_POLICY_UNKNOWN);
         mBondedDevices.add(device);
@@ -366,7 +365,6 @@
     @Test
     public void testUnbondDevice_disconnectAfterUnbond() {
         BluetoothDevice device = TestUtils.getTestDevice(mAdapter, 0);
-        when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager);
         when(mDatabaseManager.getProfileConnectionPolicy(device, BluetoothProfile.HEADSET))
                 .thenReturn(BluetoothProfile.CONNECTION_POLICY_UNKNOWN);
         mBondedDevices.add(device);
@@ -432,23 +430,27 @@
             Assert.assertThat(mHeadsetServiceBinder.getConnectedDevices(),
                     Matchers.containsInAnyOrder(mBondedDevices.toArray()));
             Assert.assertThat(mHeadsetServiceBinder.getDevicesMatchingConnectionStates(
-                    new int[]{BluetoothProfile.STATE_CONNECTED}),
+                    new int[]{BluetoothProfile.STATE_CONNECTED}, mAdapter.getAttributionSource()),
                     Matchers.containsInAnyOrder(mBondedDevices.toArray()));
         }
         List<BluetoothDevice> connectedDevices = mHeadsetServiceBinder.getConnectedDevices();
         Assert.assertThat(connectedDevices, Matchers.containsInAnyOrder(mBondedDevices.toArray()));
         Assert.assertFalse(mHeadsetService.isVirtualCallStarted());
         BluetoothDevice activeDevice = connectedDevices.get(MAX_HEADSET_CONNECTIONS / 2);
-        Assert.assertTrue(mHeadsetServiceBinder.setActiveDevice(activeDevice));
+        Assert.assertTrue(mHeadsetServiceBinder.setActiveDevice(activeDevice,
+                mAdapter.getAttributionSource()));
         verify(mNativeInterface).setActiveDevice(activeDevice);
         waitAndVerifyActiveDeviceChangedIntent(ASYNC_CALL_TIMEOUT_MILLIS, activeDevice);
-        Assert.assertEquals(activeDevice, mHeadsetServiceBinder.getActiveDevice());
+        Assert.assertEquals(activeDevice,
+                mHeadsetServiceBinder.getActiveDevice(mAdapter.getAttributionSource()));
         // Start virtual call
-        Assert.assertTrue(mHeadsetServiceBinder.startScoUsingVirtualVoiceCall());
+        Assert.assertTrue(mHeadsetServiceBinder
+                .startScoUsingVirtualVoiceCall(mAdapter.getAttributionSource()));
         Assert.assertTrue(mHeadsetService.isVirtualCallStarted());
         verifyVirtualCallStartSequenceInvocations(connectedDevices);
         // End virtual call
-        Assert.assertTrue(mHeadsetServiceBinder.stopScoUsingVirtualVoiceCall());
+        Assert.assertTrue(mHeadsetServiceBinder
+                .stopScoUsingVirtualVoiceCall(mAdapter.getAttributionSource()));
         Assert.assertFalse(mHeadsetService.isVirtualCallStarted());
         verifyVirtualCallStopSequenceInvocations(connectedDevices);
     }
@@ -468,24 +470,27 @@
             Assert.assertThat(mHeadsetServiceBinder.getConnectedDevices(),
                     Matchers.containsInAnyOrder(mBondedDevices.toArray()));
             Assert.assertThat(mHeadsetServiceBinder.getDevicesMatchingConnectionStates(
-                    new int[]{BluetoothProfile.STATE_CONNECTED}),
+                    new int[]{BluetoothProfile.STATE_CONNECTED}, mAdapter.getAttributionSource()),
                     Matchers.containsInAnyOrder(mBondedDevices.toArray()));
         }
         List<BluetoothDevice> connectedDevices = mHeadsetServiceBinder.getConnectedDevices();
         Assert.assertThat(connectedDevices, Matchers.containsInAnyOrder(mBondedDevices.toArray()));
         Assert.assertFalse(mHeadsetService.isVirtualCallStarted());
         BluetoothDevice activeDevice = connectedDevices.get(MAX_HEADSET_CONNECTIONS / 2);
-        Assert.assertTrue(mHeadsetServiceBinder.setActiveDevice(activeDevice));
+        Assert.assertTrue(mHeadsetServiceBinder.setActiveDevice(activeDevice,
+                mAdapter.getAttributionSource()));
         verify(mNativeInterface).setActiveDevice(activeDevice);
         waitAndVerifyActiveDeviceChangedIntent(ASYNC_CALL_TIMEOUT_MILLIS, activeDevice);
-        Assert.assertEquals(activeDevice, mHeadsetServiceBinder.getActiveDevice());
+        Assert.assertEquals(activeDevice,
+                mHeadsetServiceBinder.getActiveDevice(mAdapter.getAttributionSource()));
         // Start virtual call
-        Assert.assertTrue(mHeadsetServiceBinder.startScoUsingVirtualVoiceCall());
+        Assert.assertTrue(mHeadsetServiceBinder
+                .startScoUsingVirtualVoiceCall(mAdapter.getAttributionSource()));
         Assert.assertTrue(mHeadsetService.isVirtualCallStarted());
         verifyVirtualCallStartSequenceInvocations(connectedDevices);
         // Virtual call should be preempted by telecom call
         mHeadsetServiceBinder.phoneStateChanged(0, 0, HeadsetHalConstants.CALL_STATE_INCOMING,
-                TEST_PHONE_NUMBER, 128, "");
+                TEST_PHONE_NUMBER, 128, "", mAdapter.getAttributionSource());
         Assert.assertFalse(mHeadsetService.isVirtualCallStarted());
         verifyVirtualCallStopSequenceInvocations(connectedDevices);
         verifyCallStateToNativeInvocation(
@@ -508,20 +513,23 @@
             Assert.assertThat(mHeadsetServiceBinder.getConnectedDevices(),
                     Matchers.containsInAnyOrder(mBondedDevices.toArray()));
             Assert.assertThat(mHeadsetServiceBinder.getDevicesMatchingConnectionStates(
-                    new int[]{BluetoothProfile.STATE_CONNECTED}),
+                    new int[]{BluetoothProfile.STATE_CONNECTED}, mAdapter.getAttributionSource()),
                     Matchers.containsInAnyOrder(mBondedDevices.toArray()));
         }
         List<BluetoothDevice> connectedDevices = mHeadsetServiceBinder.getConnectedDevices();
         Assert.assertThat(connectedDevices, Matchers.containsInAnyOrder(mBondedDevices.toArray()));
         Assert.assertFalse(mHeadsetService.isVirtualCallStarted());
         BluetoothDevice activeDevice = connectedDevices.get(MAX_HEADSET_CONNECTIONS / 2);
-        Assert.assertTrue(mHeadsetServiceBinder.setActiveDevice(activeDevice));
+        Assert.assertTrue(mHeadsetServiceBinder.setActiveDevice(activeDevice,
+                mAdapter.getAttributionSource()));
         verify(mNativeInterface).setActiveDevice(activeDevice);
         waitAndVerifyActiveDeviceChangedIntent(ASYNC_CALL_TIMEOUT_MILLIS, activeDevice);
-        Assert.assertEquals(activeDevice, mHeadsetServiceBinder.getActiveDevice());
+        Assert.assertEquals(activeDevice,
+                mHeadsetServiceBinder.getActiveDevice(mAdapter.getAttributionSource()));
         // Reject virtual call setup if call state is not idle
         when(mSystemInterface.isCallIdle()).thenReturn(false);
-        Assert.assertFalse(mHeadsetServiceBinder.startScoUsingVirtualVoiceCall());
+        Assert.assertFalse(mHeadsetServiceBinder
+                .startScoUsingVirtualVoiceCall(mAdapter.getAttributionSource()));
         Assert.assertFalse(mHeadsetService.isVirtualCallStarted());
     }
 
@@ -536,17 +544,19 @@
             Assert.assertThat(mHeadsetServiceBinder.getConnectedDevices(),
                     Matchers.containsInAnyOrder(mBondedDevices.toArray()));
             Assert.assertThat(mHeadsetServiceBinder.getDevicesMatchingConnectionStates(
-                    new int[]{BluetoothProfile.STATE_CONNECTED}),
+                    new int[]{BluetoothProfile.STATE_CONNECTED}, mAdapter.getAttributionSource()),
                     Matchers.containsInAnyOrder(mBondedDevices.toArray()));
         }
         List<BluetoothDevice> connectedDevices = mHeadsetServiceBinder.getConnectedDevices();
         Assert.assertThat(connectedDevices, Matchers.containsInAnyOrder(mBondedDevices.toArray()));
         Assert.assertFalse(mHeadsetService.isVirtualCallStarted());
         BluetoothDevice activeDevice = connectedDevices.get(0);
-        Assert.assertTrue(mHeadsetServiceBinder.setActiveDevice(activeDevice));
+        Assert.assertTrue(mHeadsetServiceBinder.setActiveDevice(activeDevice,
+                mAdapter.getAttributionSource()));
         verify(mNativeInterface).setActiveDevice(activeDevice);
         waitAndVerifyActiveDeviceChangedIntent(ASYNC_CALL_TIMEOUT_MILLIS, activeDevice);
-        Assert.assertEquals(activeDevice, mHeadsetServiceBinder.getActiveDevice());
+        Assert.assertEquals(activeDevice,
+                mHeadsetServiceBinder.getActiveDevice(mAdapter.getAttributionSource()));
         // Try dialing out from the a non active Headset
         BluetoothDevice dialingOutDevice = connectedDevices.get(1);
         HeadsetStackEvent dialingOutEvent =
@@ -581,13 +591,14 @@
         verify(mNativeInterface).atResponseCode(activeDevice, HeadsetHalConstants.AT_RESPONSE_ERROR,
                 0);
         TestUtils.waitForNoIntent(ASYNC_CALL_TIMEOUT_MILLIS, mActiveDeviceChangedQueue);
-        Assert.assertEquals(dialingOutDevice, mHeadsetServiceBinder.getActiveDevice());
+        Assert.assertEquals(dialingOutDevice,
+                mHeadsetServiceBinder.getActiveDevice(mAdapter.getAttributionSource()));
         // Make sure only one intent is fired
         Intents.intended(allOf(IntentMatchers.hasAction(Intent.ACTION_CALL_PRIVILEGED),
                 IntentMatchers.hasData(dialOutUri)), Intents.times(1));
         // Verify that phone state update confirms the dial out event
         mHeadsetServiceBinder.phoneStateChanged(0, 0, HeadsetHalConstants.CALL_STATE_DIALING,
-                TEST_PHONE_NUMBER, 128, "");
+                TEST_PHONE_NUMBER, 128, "", mAdapter.getAttributionSource());
         HeadsetCallState dialingCallState =
                 new HeadsetCallState(0, 0, HeadsetHalConstants.CALL_STATE_DIALING,
                         TEST_PHONE_NUMBER, 128, "");
@@ -596,7 +607,7 @@
                 HeadsetHalConstants.AT_RESPONSE_OK, 0);
         // Verify that IDLE phone state clears the dialing out flag
         mHeadsetServiceBinder.phoneStateChanged(1, 0, HeadsetHalConstants.CALL_STATE_IDLE,
-                TEST_PHONE_NUMBER, 128, "");
+                TEST_PHONE_NUMBER, 128, "", mAdapter.getAttributionSource());
         HeadsetCallState activeCallState =
                 new HeadsetCallState(0, 0, HeadsetHalConstants.CALL_STATE_DIALING,
                         TEST_PHONE_NUMBER, 128, "");
@@ -615,19 +626,22 @@
             Assert.assertThat(mHeadsetServiceBinder.getConnectedDevices(),
                     Matchers.containsInAnyOrder(mBondedDevices.toArray()));
             Assert.assertThat(mHeadsetServiceBinder.getDevicesMatchingConnectionStates(
-                    new int[]{BluetoothProfile.STATE_CONNECTED}),
+                    new int[]{BluetoothProfile.STATE_CONNECTED}, mAdapter.getAttributionSource()),
                     Matchers.containsInAnyOrder(mBondedDevices.toArray()));
         }
         List<BluetoothDevice> connectedDevices = mHeadsetServiceBinder.getConnectedDevices();
         Assert.assertThat(connectedDevices, Matchers.containsInAnyOrder(mBondedDevices.toArray()));
         Assert.assertFalse(mHeadsetService.isVirtualCallStarted());
         BluetoothDevice activeDevice = connectedDevices.get(0);
-        Assert.assertTrue(mHeadsetServiceBinder.setActiveDevice(activeDevice));
+        Assert.assertTrue(mHeadsetServiceBinder.setActiveDevice(activeDevice,
+                mAdapter.getAttributionSource()));
         verify(mNativeInterface).setActiveDevice(activeDevice);
         waitAndVerifyActiveDeviceChangedIntent(ASYNC_CALL_TIMEOUT_MILLIS, activeDevice);
-        Assert.assertEquals(activeDevice, mHeadsetServiceBinder.getActiveDevice());
+        Assert.assertEquals(activeDevice,
+                mHeadsetServiceBinder.getActiveDevice(mAdapter.getAttributionSource()));
         // Start virtual call
-        Assert.assertTrue(mHeadsetServiceBinder.startScoUsingVirtualVoiceCall());
+        Assert.assertTrue(mHeadsetServiceBinder
+                .startScoUsingVirtualVoiceCall(mAdapter.getAttributionSource()));
         Assert.assertTrue(mHeadsetService.isVirtualCallStarted());
         verifyVirtualCallStartSequenceInvocations(connectedDevices);
         // Try dialing out from the a non active Headset
@@ -1097,14 +1111,14 @@
             Assert.assertThat(mHeadsetServiceBinder.getConnectedDevices(),
                     Matchers.containsInAnyOrder(mBondedDevices.toArray()));
             Assert.assertThat(mHeadsetServiceBinder.getDevicesMatchingConnectionStates(
-                    new int[]{BluetoothProfile.STATE_CONNECTED}),
+                    new int[]{BluetoothProfile.STATE_CONNECTED}, mAdapter.getAttributionSource()),
                     Matchers.containsInAnyOrder(mBondedDevices.toArray()));
         }
         List<BluetoothDevice> connectedDevices = mHeadsetServiceBinder.getConnectedDevices();
         Assert.assertThat(connectedDevices, Matchers.containsInAnyOrder(mBondedDevices.toArray()));
         // Incoming call update by telecom
         mHeadsetServiceBinder.phoneStateChanged(0, 0, HeadsetHalConstants.CALL_STATE_INCOMING,
-                TEST_PHONE_NUMBER, 128, TEST_CALLER_ID);
+                TEST_PHONE_NUMBER, 128, TEST_CALLER_ID, mAdapter.getAttributionSource());
         HeadsetCallState incomingCallState = new HeadsetCallState(0, 0,
                 HeadsetHalConstants.CALL_STATE_INCOMING, TEST_PHONE_NUMBER, 128, TEST_CALLER_ID);
         verifyCallStateToNativeInvocation(incomingCallState, connectedDevices);
@@ -1152,7 +1166,6 @@
     }
 
     private void connectTestDevice(BluetoothDevice device) {
-        when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager);
         when(mDatabaseManager.getProfileConnectionPolicy(device, BluetoothProfile.HEADSET))
                 .thenReturn(BluetoothProfile.CONNECTION_POLICY_UNKNOWN);
         // Make device bonded
diff --git a/tests/unit/src/com/android/bluetooth/hfp/HeadsetServiceTest.java b/tests/unit/src/com/android/bluetooth/hfp/HeadsetServiceTest.java
index 465b78e..347897e 100644
--- a/tests/unit/src/com/android/bluetooth/hfp/HeadsetServiceTest.java
+++ b/tests/unit/src/com/android/bluetooth/hfp/HeadsetServiceTest.java
@@ -110,16 +110,17 @@
                 .getRemoteUuids(any(BluetoothDevice.class));
         doReturn(BluetoothDevice.BOND_BONDED).when(mAdapterService)
                 .getBondState(any(BluetoothDevice.class));
+        doReturn(mDatabaseManager).when(mAdapterService).getDatabase();
+        doReturn(true, false).when(mAdapterService).isStartedProfile(anyString());
         doAnswer(invocation -> {
             Set<BluetoothDevice> keys = mStateMachines.keySet();
             return keys.toArray(new BluetoothDevice[keys.size()]);
         }).when(mAdapterService).getBondedDevices();
         // Mock system interface
-        doNothing().when(mSystemInterface).init();
         doNothing().when(mSystemInterface).stop();
         when(mSystemInterface.getHeadsetPhoneState()).thenReturn(mPhoneState);
         when(mSystemInterface.getAudioManager()).thenReturn(mAudioManager);
-        when(mSystemInterface.isCallIdle()).thenReturn(true);
+        when(mSystemInterface.isCallIdle()).thenReturn(true, false, true, false);
         // Mock methods in HeadsetNativeInterface
         mNativeInterface = spy(HeadsetNativeInterface.getInstance());
         doNothing().when(mNativeInterface).init(anyInt(), anyBoolean());
@@ -148,9 +149,7 @@
         verify(mObjectsFactory).getNativeInterface();
         mHeadsetServiceBinder = (IBluetoothHeadset.Stub) mHeadsetService.initBinder();
         Assert.assertNotNull(mHeadsetServiceBinder);
-        mHeadsetServiceBinder.setForceScoAudio(true);
-        // Mock database for getConnectionPolicy()
-        when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager);
+        mHeadsetServiceBinder.setForceScoAudio(true, mAdapter.getAttributionSource());
     }
 
     @After
@@ -704,7 +703,10 @@
                         TEST_PHONE_NUMBER, 128, "");
         mHeadsetServiceBinder.phoneStateChanged(headsetCallState.mNumActive,
                 headsetCallState.mNumHeld, headsetCallState.mCallState, headsetCallState.mNumber,
-                headsetCallState.mType, headsetCallState.mName);
+                headsetCallState.mType, headsetCallState.mName, mAdapter.getAttributionSource());
+        TestUtils.waitForLooperToFinishScheduledTask(
+                mHeadsetService.getStateMachinesThreadLooper());
+        verify(mAudioManager, never()).setParameters("A2dpSuspended=true");
         HeadsetTestUtils.verifyPhoneStateChangeSetters(mPhoneState, headsetCallState,
                 ASYNC_CALL_TIMEOUT_MILLIS);
     }
@@ -718,7 +720,7 @@
     @Test
     public void testPhoneStateChange_oneDeviceSaveState() throws RemoteException {
         HeadsetCallState headsetCallState =
-                new HeadsetCallState(1, 0, HeadsetHalConstants.CALL_STATE_ALERTING,
+                new HeadsetCallState(0, 0, HeadsetHalConstants.CALL_STATE_IDLE,
                         TEST_PHONE_NUMBER, 128, "");
         when(mDatabaseManager.getProfileConnectionPolicy(any(BluetoothDevice.class),
                 eq(BluetoothProfile.HEADSET)))
@@ -758,13 +760,34 @@
         // Change phone state
         mHeadsetServiceBinder.phoneStateChanged(headsetCallState.mNumActive,
                 headsetCallState.mNumHeld, headsetCallState.mCallState, headsetCallState.mNumber,
-                headsetCallState.mType, headsetCallState.mName);
+                headsetCallState.mType, headsetCallState.mName, mAdapter.getAttributionSource());
+        TestUtils.waitForLooperToFinishScheduledTask(
+                mHeadsetService.getStateMachinesThreadLooper());
+
+        // Should not ask Audio HAL to suspend A2DP without active device
+        verify(mAudioManager, never()).setParameters("A2dpSuspended=true");
         // Make sure we notify device about this change
         verify(mStateMachines.get(mCurrentDevice)).sendMessage(
                 HeadsetStateMachine.CALL_STATE_CHANGED, headsetCallState);
         // Make sure state is updated once in phone state holder
         HeadsetTestUtils.verifyPhoneStateChangeSetters(mPhoneState, headsetCallState,
                 ASYNC_CALL_TIMEOUT_MILLIS);
+
+        // Set the device first as the active device
+        Assert.assertTrue(mHeadsetService.setActiveDevice(mCurrentDevice));
+        // Change phone state
+        headsetCallState.mCallState = HeadsetHalConstants.CALL_STATE_ALERTING;
+        mHeadsetServiceBinder.phoneStateChanged(headsetCallState.mNumActive,
+                headsetCallState.mNumHeld, headsetCallState.mCallState, headsetCallState.mNumber,
+                headsetCallState.mType, headsetCallState.mName, mAdapter.getAttributionSource());
+        TestUtils.waitForLooperToFinishScheduledTask(
+                mHeadsetService.getStateMachinesThreadLooper());
+        // Ask Audio HAL to suspend A2DP
+        verify(mAudioManager).setParameters("A2dpSuspended=true");
+        // Make sure state is updated
+        verify(mStateMachines.get(mCurrentDevice)).sendMessage(
+                HeadsetStateMachine.CALL_STATE_CHANGED, headsetCallState);
+        verify(mPhoneState).setCallState(eq(headsetCallState.mCallState));
     }
 
     /**
@@ -817,11 +840,15 @@
                     Matchers.containsInAnyOrder(connectedDevices.toArray()));
             mHeadsetService.onConnectionStateChangedFromStateMachine(mCurrentDevice,
                     BluetoothProfile.STATE_CONNECTING, BluetoothProfile.STATE_CONNECTED);
+            Assert.assertTrue(mHeadsetService.setActiveDevice(mCurrentDevice));
         }
         // Change phone state
         mHeadsetServiceBinder.phoneStateChanged(headsetCallState.mNumActive,
                 headsetCallState.mNumHeld, headsetCallState.mCallState, headsetCallState.mNumber,
-                headsetCallState.mType, headsetCallState.mName);
+                headsetCallState.mType, headsetCallState.mName, mAdapter.getAttributionSource());
+        // Ask Audio HAL to suspend A2DP
+        verify(mAudioManager, timeout(ASYNC_CALL_TIMEOUT_MILLIS))
+                .setParameters("A2dpSuspended=true");
         // Make sure we notify devices about this change
         for (BluetoothDevice device : connectedDevices) {
             verify(mStateMachines.get(device)).sendMessage(HeadsetStateMachine.CALL_STATE_CHANGED,
diff --git a/tests/unit/src/com/android/bluetooth/hfp/HeadsetStateMachineTest.java b/tests/unit/src/com/android/bluetooth/hfp/HeadsetStateMachineTest.java
index b3d44ec..34d228d 100644
--- a/tests/unit/src/com/android/bluetooth/hfp/HeadsetStateMachineTest.java
+++ b/tests/unit/src/com/android/bluetooth/hfp/HeadsetStateMachineTest.java
@@ -16,6 +16,7 @@
 
 package com.android.bluetooth.hfp;
 
+import static android.Manifest.permission.BLUETOOTH_CONNECT;
 import static org.mockito.Mockito.*;
 
 import android.bluetooth.BluetoothAdapter;
@@ -171,7 +172,8 @@
     public void testStateTransition_DisconnectedToConnecting_Connect() {
         mHeadsetStateMachine.sendMessage(HeadsetStateMachine.CONNECT, mTestDevice);
         verify(mHeadsetService, timeout(ASYNC_CALL_TIMEOUT_MILLIS)).sendBroadcastAsUser(
-                mIntentArgument.capture(), eq(UserHandle.ALL), eq(HeadsetService.BLUETOOTH_PERM));
+                mIntentArgument.capture(), eq(UserHandle.ALL), eq(BLUETOOTH_CONNECT),
+                any(Bundle.class));
         HeadsetTestUtils.verifyConnectionStateBroadcast(mTestDevice,
                 BluetoothProfile.STATE_CONNECTING, BluetoothProfile.STATE_DISCONNECTED,
                 mIntentArgument.getValue());
@@ -188,7 +190,8 @@
                 new HeadsetStackEvent(HeadsetStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED,
                         HeadsetHalConstants.CONNECTION_STATE_CONNECTED, mTestDevice));
         verify(mHeadsetService, timeout(ASYNC_CALL_TIMEOUT_MILLIS)).sendBroadcastAsUser(
-                mIntentArgument.capture(), eq(UserHandle.ALL), eq(HeadsetService.BLUETOOTH_PERM));
+                mIntentArgument.capture(), eq(UserHandle.ALL), eq(BLUETOOTH_CONNECT),
+                any(Bundle.class));
         HeadsetTestUtils.verifyConnectionStateBroadcast(mTestDevice,
                 BluetoothProfile.STATE_CONNECTING, BluetoothProfile.STATE_DISCONNECTED,
                 mIntentArgument.getValue());
@@ -205,7 +208,8 @@
                 new HeadsetStackEvent(HeadsetStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED,
                         HeadsetHalConstants.CONNECTION_STATE_CONNECTING, mTestDevice));
         verify(mHeadsetService, timeout(ASYNC_CALL_TIMEOUT_MILLIS)).sendBroadcastAsUser(
-                mIntentArgument.capture(), eq(UserHandle.ALL), eq(HeadsetService.BLUETOOTH_PERM));
+                mIntentArgument.capture(), eq(UserHandle.ALL), eq(BLUETOOTH_CONNECT),
+                any(Bundle.class));
         HeadsetTestUtils.verifyConnectionStateBroadcast(mTestDevice,
                 BluetoothProfile.STATE_CONNECTING, BluetoothProfile.STATE_DISCONNECTED,
                 mIntentArgument.getValue());
@@ -227,7 +231,7 @@
         // Should do nothing new
         verify(mHeadsetService,
                 after(ASYNC_CALL_TIMEOUT_MILLIS).times(numBroadcastsSent)).sendBroadcastAsUser(
-                any(Intent.class), any(UserHandle.class), anyString());
+                any(Intent.class), any(UserHandle.class), anyString(), any(Bundle.class));
         Assert.assertThat(mHeadsetStateMachine.getCurrentState(),
                 IsInstanceOf.instanceOf(HeadsetStateMachine.Connecting.class));
 
@@ -239,7 +243,8 @@
         numBroadcastsSent++;
         verify(mHeadsetService,
                 timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(numBroadcastsSent)).sendBroadcastAsUser(
-                mIntentArgument.capture(), eq(UserHandle.ALL), eq(HeadsetService.BLUETOOTH_PERM));
+                mIntentArgument.capture(), eq(UserHandle.ALL), eq(BLUETOOTH_CONNECT),
+                any(Bundle.class));
         HeadsetTestUtils.verifyConnectionStateBroadcast(mTestDevice,
                 BluetoothProfile.STATE_DISCONNECTED, BluetoothProfile.STATE_CONNECTING,
                 mIntentArgument.getValue());
@@ -257,7 +262,8 @@
         numBroadcastsSent++;
         verify(mHeadsetService, timeout(CONNECT_TIMEOUT_TEST_WAIT_MILLIS).times(
                 numBroadcastsSent)).sendBroadcastAsUser(mIntentArgument.capture(),
-                eq(UserHandle.ALL), eq(HeadsetService.BLUETOOTH_PERM));
+                eq(UserHandle.ALL), eq(BLUETOOTH_CONNECT),
+                any(Bundle.class));
         HeadsetTestUtils.verifyConnectionStateBroadcast(mTestDevice,
                 BluetoothProfile.STATE_DISCONNECTED, BluetoothProfile.STATE_CONNECTING,
                 mIntentArgument.getValue());
@@ -278,7 +284,7 @@
         // Should do nothing
         verify(mHeadsetService,
                 after(ASYNC_CALL_TIMEOUT_MILLIS).times(numBroadcastsSent)).sendBroadcastAsUser(
-                any(Intent.class), any(UserHandle.class), anyString());
+                any(Intent.class), any(UserHandle.class), anyString(), any(Bundle.class));
         Assert.assertThat(mHeadsetStateMachine.getCurrentState(),
                 IsInstanceOf.instanceOf(HeadsetStateMachine.Connecting.class));
 
@@ -289,7 +295,7 @@
         // Should do nothing
         verify(mHeadsetService,
                 after(ASYNC_CALL_TIMEOUT_MILLIS).times(numBroadcastsSent)).sendBroadcastAsUser(
-                any(Intent.class), any(UserHandle.class), anyString());
+                any(Intent.class), any(UserHandle.class), anyString(), any(Bundle.class));
         Assert.assertThat(mHeadsetStateMachine.getCurrentState(),
                 IsInstanceOf.instanceOf(HeadsetStateMachine.Connecting.class));
 
@@ -300,7 +306,8 @@
                         HeadsetHalConstants.CONNECTION_STATE_SLC_CONNECTED, mTestDevice));
         verify(mHeadsetService,
                 timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(numBroadcastsSent)).sendBroadcastAsUser(
-                mIntentArgument.capture(), eq(UserHandle.ALL), eq(HeadsetService.BLUETOOTH_PERM));
+                mIntentArgument.capture(), eq(UserHandle.ALL), eq(BLUETOOTH_CONNECT),
+                any(Bundle.class));
         HeadsetTestUtils.verifyConnectionStateBroadcast(mTestDevice,
                 BluetoothProfile.STATE_CONNECTED, BluetoothProfile.STATE_CONNECTING,
                 mIntentArgument.getValue());
@@ -322,7 +329,8 @@
                         HeadsetHalConstants.CONNECTION_STATE_DISCONNECTED, mTestDevice));
         verify(mHeadsetService,
                 timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(numBroadcastsSent)).sendBroadcastAsUser(
-                mIntentArgument.capture(), eq(UserHandle.ALL), eq(HeadsetService.BLUETOOTH_PERM));
+                mIntentArgument.capture(), eq(UserHandle.ALL), eq(BLUETOOTH_CONNECT),
+                any(Bundle.class));
         HeadsetTestUtils.verifyConnectionStateBroadcast(mTestDevice,
                 BluetoothProfile.STATE_DISCONNECTED, BluetoothProfile.STATE_DISCONNECTING,
                 mIntentArgument.getValue());
@@ -341,7 +349,8 @@
         numBroadcastsSent++;
         verify(mHeadsetService, timeout(CONNECT_TIMEOUT_TEST_WAIT_MILLIS).times(
                 numBroadcastsSent)).sendBroadcastAsUser(mIntentArgument.capture(),
-                eq(UserHandle.ALL), eq(HeadsetService.BLUETOOTH_PERM));
+                eq(UserHandle.ALL), eq(BLUETOOTH_CONNECT),
+                any(Bundle.class));
         HeadsetTestUtils.verifyConnectionStateBroadcast(mTestDevice,
                 BluetoothProfile.STATE_DISCONNECTED, BluetoothProfile.STATE_DISCONNECTING,
                 mIntentArgument.getValue());
@@ -363,7 +372,8 @@
                         HeadsetHalConstants.CONNECTION_STATE_SLC_CONNECTED, mTestDevice));
         verify(mHeadsetService,
                 timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(numBroadcastsSent)).sendBroadcastAsUser(
-                mIntentArgument.capture(), eq(UserHandle.ALL), eq(HeadsetService.BLUETOOTH_PERM));
+                mIntentArgument.capture(), eq(UserHandle.ALL), eq(BLUETOOTH_CONNECT),
+                any(Bundle.class));
         HeadsetTestUtils.verifyConnectionStateBroadcast(mTestDevice,
                 BluetoothProfile.STATE_CONNECTED, BluetoothProfile.STATE_DISCONNECTING,
                 mIntentArgument.getValue());
@@ -382,7 +392,8 @@
         mHeadsetStateMachine.sendMessage(HeadsetStateMachine.DISCONNECT, mTestDevice);
         verify(mHeadsetService,
                 timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(numBroadcastsSent)).sendBroadcastAsUser(
-                mIntentArgument.capture(), eq(UserHandle.ALL), eq(HeadsetService.BLUETOOTH_PERM));
+                mIntentArgument.capture(), eq(UserHandle.ALL), eq(BLUETOOTH_CONNECT),
+                any(Bundle.class));
         HeadsetTestUtils.verifyConnectionStateBroadcast(mTestDevice,
                 BluetoothProfile.STATE_DISCONNECTING, BluetoothProfile.STATE_CONNECTED,
                 mIntentArgument.getValue());
@@ -404,7 +415,8 @@
                         HeadsetHalConstants.CONNECTION_STATE_DISCONNECTING, mTestDevice));
         verify(mHeadsetService,
                 timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(numBroadcastsSent)).sendBroadcastAsUser(
-                mIntentArgument.capture(), eq(UserHandle.ALL), eq(HeadsetService.BLUETOOTH_PERM));
+                mIntentArgument.capture(), eq(UserHandle.ALL), eq(BLUETOOTH_CONNECT),
+                any(Bundle.class));
         HeadsetTestUtils.verifyConnectionStateBroadcast(mTestDevice,
                 BluetoothProfile.STATE_DISCONNECTING, BluetoothProfile.STATE_CONNECTED,
                 mIntentArgument.getValue());
@@ -426,7 +438,8 @@
                         HeadsetHalConstants.CONNECTION_STATE_DISCONNECTED, mTestDevice));
         verify(mHeadsetService,
                 timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(numBroadcastsSent)).sendBroadcastAsUser(
-                mIntentArgument.capture(), eq(UserHandle.ALL), eq(HeadsetService.BLUETOOTH_PERM));
+                mIntentArgument.capture(), eq(UserHandle.ALL), eq(BLUETOOTH_CONNECT),
+                any(Bundle.class));
         HeadsetTestUtils.verifyConnectionStateBroadcast(mTestDevice,
                 BluetoothProfile.STATE_DISCONNECTED, BluetoothProfile.STATE_CONNECTED,
                 mIntentArgument.getValue());
@@ -445,7 +458,8 @@
         mHeadsetStateMachine.sendMessage(HeadsetStateMachine.CONNECT_AUDIO, mTestDevice);
         verify(mHeadsetService,
                 timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(numBroadcastsSent)).sendBroadcastAsUser(
-                mIntentArgument.capture(), eq(UserHandle.ALL), eq(HeadsetService.BLUETOOTH_PERM));
+                mIntentArgument.capture(), eq(UserHandle.ALL), eq(BLUETOOTH_CONNECT),
+                any(Bundle.class));
         HeadsetTestUtils.verifyAudioStateBroadcast(mTestDevice,
                 BluetoothHeadset.STATE_AUDIO_CONNECTING, BluetoothHeadset.STATE_AUDIO_DISCONNECTED,
                 mIntentArgument.getValue());
@@ -467,7 +481,8 @@
                         HeadsetHalConstants.AUDIO_STATE_CONNECTING, mTestDevice));
         verify(mHeadsetService,
                 timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(numBroadcastsSent)).sendBroadcastAsUser(
-                mIntentArgument.capture(), eq(UserHandle.ALL), eq(HeadsetService.BLUETOOTH_PERM));
+                mIntentArgument.capture(), eq(UserHandle.ALL), eq(BLUETOOTH_CONNECT),
+                any(Bundle.class));
         HeadsetTestUtils.verifyAudioStateBroadcast(mTestDevice,
                 BluetoothHeadset.STATE_AUDIO_CONNECTING, BluetoothHeadset.STATE_AUDIO_DISCONNECTED,
                 mIntentArgument.getValue());
@@ -488,7 +503,8 @@
                         HeadsetHalConstants.AUDIO_STATE_CONNECTED, mTestDevice));
         verify(mHeadsetService,
                 timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(numBroadcastsSent)).sendBroadcastAsUser(
-                mIntentArgument.capture(), eq(UserHandle.ALL), eq(HeadsetService.BLUETOOTH_PERM));
+                mIntentArgument.capture(), eq(UserHandle.ALL), eq(BLUETOOTH_CONNECT),
+                any(Bundle.class));
         HeadsetTestUtils.verifyAudioStateBroadcast(mTestDevice,
                 BluetoothHeadset.STATE_AUDIO_CONNECTED, BluetoothHeadset.STATE_AUDIO_DISCONNECTED,
                 mIntentArgument.getValue());
@@ -506,7 +522,8 @@
         numBroadcastsSent++;
         verify(mHeadsetService, timeout(CONNECT_TIMEOUT_TEST_WAIT_MILLIS).times(
                 numBroadcastsSent)).sendBroadcastAsUser(mIntentArgument.capture(),
-                eq(UserHandle.ALL), eq(HeadsetService.BLUETOOTH_PERM));
+                eq(UserHandle.ALL), eq(BLUETOOTH_CONNECT),
+                any(Bundle.class));
         HeadsetTestUtils.verifyAudioStateBroadcast(mTestDevice,
                 BluetoothHeadset.STATE_AUDIO_DISCONNECTED, BluetoothHeadset.STATE_AUDIO_CONNECTING,
                 mIntentArgument.getValue());
@@ -528,7 +545,8 @@
                         HeadsetHalConstants.AUDIO_STATE_DISCONNECTED, mTestDevice));
         verify(mHeadsetService,
                 timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(numBroadcastsSent)).sendBroadcastAsUser(
-                mIntentArgument.capture(), eq(UserHandle.ALL), eq(HeadsetService.BLUETOOTH_PERM));
+                mIntentArgument.capture(), eq(UserHandle.ALL), eq(BLUETOOTH_CONNECT),
+                any(Bundle.class));
         HeadsetTestUtils.verifyAudioStateBroadcast(mTestDevice,
                 BluetoothHeadset.STATE_AUDIO_DISCONNECTED, BluetoothHeadset.STATE_AUDIO_CONNECTING,
                 mIntentArgument.getValue());
@@ -550,7 +568,8 @@
                         HeadsetHalConstants.CONNECTION_STATE_DISCONNECTED, mTestDevice));
         verify(mHeadsetService,
                 timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(numBroadcastsSent)).sendBroadcastAsUser(
-                mIntentArgument.capture(), eq(UserHandle.ALL), eq(HeadsetService.BLUETOOTH_PERM));
+                mIntentArgument.capture(), eq(UserHandle.ALL), eq(BLUETOOTH_CONNECT),
+                any(Bundle.class));
         HeadsetTestUtils.verifyAudioStateBroadcast(mTestDevice,
                 BluetoothHeadset.STATE_AUDIO_DISCONNECTED, BluetoothHeadset.STATE_AUDIO_CONNECTING,
                 mIntentArgument.getAllValues().get(mIntentArgument.getAllValues().size() - 2));
@@ -575,7 +594,8 @@
                         HeadsetHalConstants.CONNECTION_STATE_DISCONNECTING, mTestDevice));
         verify(mHeadsetService,
                 timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(numBroadcastsSent)).sendBroadcastAsUser(
-                mIntentArgument.capture(), eq(UserHandle.ALL), eq(HeadsetService.BLUETOOTH_PERM));
+                mIntentArgument.capture(), eq(UserHandle.ALL), eq(BLUETOOTH_CONNECT),
+                any(Bundle.class));
         HeadsetTestUtils.verifyAudioStateBroadcast(mTestDevice,
                 BluetoothHeadset.STATE_AUDIO_DISCONNECTED, BluetoothHeadset.STATE_AUDIO_CONNECTING,
                 mIntentArgument.getAllValues().get(mIntentArgument.getAllValues().size() - 2));
@@ -600,7 +620,8 @@
                         HeadsetHalConstants.AUDIO_STATE_CONNECTED, mTestDevice));
         verify(mHeadsetService,
                 timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(numBroadcastsSent)).sendBroadcastAsUser(
-                mIntentArgument.capture(), eq(UserHandle.ALL), eq(HeadsetService.BLUETOOTH_PERM));
+                mIntentArgument.capture(), eq(UserHandle.ALL), eq(BLUETOOTH_CONNECT),
+                any(Bundle.class));
         HeadsetTestUtils.verifyAudioStateBroadcast(mTestDevice,
                 BluetoothHeadset.STATE_AUDIO_CONNECTED, BluetoothHeadset.STATE_AUDIO_CONNECTING,
                 mIntentArgument.getValue());
@@ -621,7 +642,8 @@
                         HeadsetHalConstants.AUDIO_STATE_DISCONNECTING, mTestDevice));
         verify(mHeadsetService,
                 after(ASYNC_CALL_TIMEOUT_MILLIS).times(numBroadcastsSent)).sendBroadcastAsUser(
-                any(Intent.class), eq(UserHandle.ALL), eq(HeadsetService.BLUETOOTH_PERM));
+                any(Intent.class), eq(UserHandle.ALL), eq(BLUETOOTH_CONNECT),
+                any(Bundle.class));
         Assert.assertThat(mHeadsetStateMachine.getCurrentState(),
                 IsInstanceOf.instanceOf(HeadsetStateMachine.AudioDisconnecting.class));
     }
@@ -638,7 +660,8 @@
         // Should not sent any broadcast due to lack of AUDIO_DISCONNECTING intent value
         verify(mHeadsetService,
                 after(ASYNC_CALL_TIMEOUT_MILLIS).times(numBroadcastsSent)).sendBroadcastAsUser(
-                any(Intent.class), eq(UserHandle.ALL), eq(HeadsetService.BLUETOOTH_PERM));
+                any(Intent.class), eq(UserHandle.ALL), eq(BLUETOOTH_CONNECT),
+                any(Bundle.class));
         Assert.assertThat(mHeadsetStateMachine.getCurrentState(),
                 IsInstanceOf.instanceOf(HeadsetStateMachine.AudioDisconnecting.class));
     }
@@ -657,7 +680,8 @@
                         HeadsetHalConstants.AUDIO_STATE_DISCONNECTED, mTestDevice));
         verify(mHeadsetService,
                 timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(numBroadcastsSent)).sendBroadcastAsUser(
-                mIntentArgument.capture(), eq(UserHandle.ALL), eq(HeadsetService.BLUETOOTH_PERM));
+                mIntentArgument.capture(), eq(UserHandle.ALL), eq(BLUETOOTH_CONNECT),
+                any(Bundle.class));
         HeadsetTestUtils.verifyAudioStateBroadcast(mTestDevice,
                 BluetoothHeadset.STATE_AUDIO_DISCONNECTED, BluetoothHeadset.STATE_AUDIO_CONNECTED,
                 mIntentArgument.getValue());
@@ -679,7 +703,8 @@
                         HeadsetHalConstants.CONNECTION_STATE_DISCONNECTED, mTestDevice));
         verify(mHeadsetService,
                 timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(numBroadcastsSent)).sendBroadcastAsUser(
-                mIntentArgument.capture(), eq(UserHandle.ALL), eq(HeadsetService.BLUETOOTH_PERM));
+                mIntentArgument.capture(), eq(UserHandle.ALL), eq(BLUETOOTH_CONNECT),
+                any(Bundle.class));
         HeadsetTestUtils.verifyAudioStateBroadcast(mTestDevice,
                 BluetoothHeadset.STATE_AUDIO_DISCONNECTED, BluetoothHeadset.STATE_AUDIO_CONNECTED,
                 mIntentArgument.getAllValues().get(mIntentArgument.getAllValues().size() - 2));
@@ -704,7 +729,8 @@
                         HeadsetHalConstants.CONNECTION_STATE_DISCONNECTING, mTestDevice));
         verify(mHeadsetService,
                 timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(numBroadcastsSent)).sendBroadcastAsUser(
-                mIntentArgument.capture(), eq(UserHandle.ALL), eq(HeadsetService.BLUETOOTH_PERM));
+                mIntentArgument.capture(), eq(UserHandle.ALL), eq(BLUETOOTH_CONNECT),
+                any(Bundle.class));
         HeadsetTestUtils.verifyAudioStateBroadcast(mTestDevice,
                 BluetoothHeadset.STATE_AUDIO_DISCONNECTED, BluetoothHeadset.STATE_AUDIO_CONNECTED,
                 mIntentArgument.getAllValues().get(mIntentArgument.getAllValues().size() - 2));
@@ -726,7 +752,8 @@
         numBroadcastsSent++;
         verify(mHeadsetService, timeout(CONNECT_TIMEOUT_TEST_WAIT_MILLIS).times(
                 numBroadcastsSent)).sendBroadcastAsUser(mIntentArgument.capture(),
-                eq(UserHandle.ALL), eq(HeadsetService.BLUETOOTH_PERM));
+                eq(UserHandle.ALL), eq(BLUETOOTH_CONNECT),
+                any(Bundle.class));
         HeadsetTestUtils.verifyAudioStateBroadcast(mTestDevice,
                 BluetoothHeadset.STATE_AUDIO_DISCONNECTED, BluetoothHeadset.STATE_AUDIO_CONNECTED,
                 mIntentArgument.getValue());
@@ -748,7 +775,8 @@
                         HeadsetHalConstants.AUDIO_STATE_DISCONNECTED, mTestDevice));
         verify(mHeadsetService,
                 timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(numBroadcastsSent)).sendBroadcastAsUser(
-                mIntentArgument.capture(), eq(UserHandle.ALL), eq(HeadsetService.BLUETOOTH_PERM));
+                mIntentArgument.capture(), eq(UserHandle.ALL), eq(BLUETOOTH_CONNECT),
+                any(Bundle.class));
         HeadsetTestUtils.verifyAudioStateBroadcast(mTestDevice,
                 BluetoothHeadset.STATE_AUDIO_DISCONNECTED, BluetoothHeadset.STATE_AUDIO_CONNECTED,
                 mIntentArgument.getValue());
@@ -770,7 +798,8 @@
                         HeadsetHalConstants.AUDIO_STATE_CONNECTED, mTestDevice));
         verify(mHeadsetService,
                 after(ASYNC_CALL_TIMEOUT_MILLIS).times(numBroadcastsSent)).sendBroadcastAsUser(
-                mIntentArgument.capture(), eq(UserHandle.ALL), eq(HeadsetService.BLUETOOTH_PERM));
+                mIntentArgument.capture(), eq(UserHandle.ALL), eq(BLUETOOTH_CONNECT),
+                any(Bundle.class));
         HeadsetTestUtils.verifyAudioStateBroadcast(mTestDevice,
                 BluetoothHeadset.STATE_AUDIO_CONNECTED, BluetoothHeadset.STATE_AUDIO_CONNECTED,
                 mIntentArgument.getValue());
@@ -792,7 +821,8 @@
                         HeadsetHalConstants.CONNECTION_STATE_DISCONNECTING, mTestDevice));
         verify(mHeadsetService,
                 timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(numBroadcastsSent)).sendBroadcastAsUser(
-                mIntentArgument.capture(), eq(UserHandle.ALL), eq(HeadsetService.BLUETOOTH_PERM));
+                mIntentArgument.capture(), eq(UserHandle.ALL), eq(BLUETOOTH_CONNECT),
+                any(Bundle.class));
         HeadsetTestUtils.verifyAudioStateBroadcast(mTestDevice,
                 BluetoothHeadset.STATE_AUDIO_DISCONNECTED, BluetoothHeadset.STATE_AUDIO_CONNECTED,
                 mIntentArgument.getAllValues().get(mIntentArgument.getAllValues().size() - 2));
@@ -817,7 +847,8 @@
                         HeadsetHalConstants.CONNECTION_STATE_DISCONNECTED, mTestDevice));
         verify(mHeadsetService,
                 timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(numBroadcastsSent)).sendBroadcastAsUser(
-                mIntentArgument.capture(), eq(UserHandle.ALL), eq(HeadsetService.BLUETOOTH_PERM));
+                mIntentArgument.capture(), eq(UserHandle.ALL), eq(BLUETOOTH_CONNECT),
+                any(Bundle.class));
         HeadsetTestUtils.verifyAudioStateBroadcast(mTestDevice,
                 BluetoothHeadset.STATE_AUDIO_DISCONNECTED, BluetoothHeadset.STATE_AUDIO_CONNECTED,
                 mIntentArgument.getAllValues().get(mIntentArgument.getAllValues().size() - 2));
@@ -957,8 +988,10 @@
                 new HeadsetStackEvent(HeadsetStackEvent.EVENT_TYPE_BIND, atString, mTestDevice));
         ArgumentCaptor<Intent> intentArgument = ArgumentCaptor.forClass(Intent.class);
         verify(mHeadsetService, timeout(ASYNC_CALL_TIMEOUT_MILLIS)).sendBroadcast(
-                intentArgument.capture(), eq(HeadsetService.BLUETOOTH_PERM));
-        verify(mHeadsetService, times(1)).sendBroadcast(any(), any());
+                intentArgument.capture(), eq(BLUETOOTH_CONNECT),
+                any(Bundle.class));
+        verify(mHeadsetService, times(1)).sendBroadcast(any(), any(),
+                any());
         Assert.assertEquals(mTestDevice, intentArgument.getValue().getExtra(
                 BluetoothDevice.EXTRA_DEVICE, null));
         Assert.assertEquals(HeadsetHalConstants.HF_INDICATOR_ENHANCED_DRIVER_SAFETY,
@@ -980,8 +1013,10 @@
                 new HeadsetStackEvent(HeadsetStackEvent.EVENT_TYPE_BIND, atString, mTestDevice));
         ArgumentCaptor<Intent> intentArgument = ArgumentCaptor.forClass(Intent.class);
         verify(mHeadsetService, timeout(ASYNC_CALL_TIMEOUT_MILLIS)).sendBroadcast(
-                intentArgument.capture(), eq(HeadsetService.BLUETOOTH_PERM));
-        verify(mHeadsetService, times(1)).sendBroadcast(any(), any());
+                intentArgument.capture(), eq(BLUETOOTH_CONNECT),
+                any(Bundle.class));
+        verify(mHeadsetService, times(1)).sendBroadcast(any(), any(),
+                any());
         Assert.assertEquals(mTestDevice, intentArgument.getValue().getExtra(
                 BluetoothDevice.EXTRA_DEVICE, null));
         Assert.assertEquals(HeadsetHalConstants.HF_INDICATOR_BATTERY_LEVEL_STATUS,
@@ -1003,8 +1038,10 @@
                 new HeadsetStackEvent(HeadsetStackEvent.EVENT_TYPE_BIND, atString, mTestDevice));
         ArgumentCaptor<Intent> intentArgument = ArgumentCaptor.forClass(Intent.class);
         verify(mHeadsetService, timeout(ASYNC_CALL_TIMEOUT_MILLIS)).sendBroadcast(
-                intentArgument.capture(), eq(HeadsetService.BLUETOOTH_PERM));
-        verify(mHeadsetService, times(1)).sendBroadcast(any(), any());
+                intentArgument.capture(), eq(BLUETOOTH_CONNECT),
+                any(Bundle.class));
+        verify(mHeadsetService, times(1)).sendBroadcast(any(), any(),
+                any());
         Assert.assertEquals(mTestDevice, intentArgument.getValue().getExtra(
                 BluetoothDevice.EXTRA_DEVICE, null));
         Assert.assertEquals(HeadsetHalConstants.HF_INDICATOR_ENHANCED_DRIVER_SAFETY,
@@ -1034,7 +1071,8 @@
         // Put test state machine in connecting state
         mHeadsetStateMachine.sendMessage(HeadsetStateMachine.CONNECT, mTestDevice);
         verify(mHeadsetService, timeout(ASYNC_CALL_TIMEOUT_MILLIS)).sendBroadcastAsUser(
-                mIntentArgument.capture(), eq(UserHandle.ALL), eq(HeadsetService.BLUETOOTH_PERM));
+                mIntentArgument.capture(), eq(UserHandle.ALL), eq(BLUETOOTH_CONNECT),
+                any(Bundle.class));
         HeadsetTestUtils.verifyConnectionStateBroadcast(mTestDevice,
                 BluetoothProfile.STATE_CONNECTING, BluetoothProfile.STATE_DISCONNECTED,
                 mIntentArgument.getValue());
@@ -1053,7 +1091,8 @@
                 new HeadsetStackEvent(HeadsetStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED,
                         HeadsetHalConstants.CONNECTION_STATE_CONNECTED, mTestDevice));
         verify(mHeadsetService, timeout(ASYNC_CALL_TIMEOUT_MILLIS)).sendBroadcastAsUser(
-                mIntentArgument.capture(), eq(UserHandle.ALL), eq(HeadsetService.BLUETOOTH_PERM));
+                mIntentArgument.capture(), eq(UserHandle.ALL), eq(BLUETOOTH_CONNECT),
+                any(Bundle.class));
         HeadsetTestUtils.verifyConnectionStateBroadcast(mTestDevice,
                 BluetoothProfile.STATE_CONNECTING, BluetoothProfile.STATE_DISCONNECTED,
                 mIntentArgument.getValue());
@@ -1063,7 +1102,8 @@
                 new HeadsetStackEvent(HeadsetStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED,
                         HeadsetHalConstants.CONNECTION_STATE_SLC_CONNECTED, mTestDevice));
         verify(mHeadsetService, timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(2)).sendBroadcastAsUser(
-                mIntentArgument.capture(), eq(UserHandle.ALL), eq(HeadsetService.BLUETOOTH_PERM));
+                mIntentArgument.capture(), eq(UserHandle.ALL), eq(BLUETOOTH_CONNECT),
+                any(Bundle.class));
         HeadsetTestUtils.verifyConnectionStateBroadcast(mTestDevice,
                 BluetoothProfile.STATE_CONNECTED, BluetoothProfile.STATE_CONNECTING,
                 mIntentArgument.getValue());
@@ -1079,7 +1119,8 @@
         mHeadsetStateMachine.sendMessage(HeadsetStateMachine.CONNECT_AUDIO, mTestDevice);
         verify(mHeadsetService,
                 timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(numBroadcastsSent)).sendBroadcastAsUser(
-                mIntentArgument.capture(), eq(UserHandle.ALL), eq(HeadsetService.BLUETOOTH_PERM));
+                mIntentArgument.capture(), eq(UserHandle.ALL), eq(BLUETOOTH_CONNECT),
+                any(Bundle.class));
         HeadsetTestUtils.verifyAudioStateBroadcast(mTestDevice,
                 BluetoothHeadset.STATE_AUDIO_CONNECTING, BluetoothHeadset.STATE_AUDIO_DISCONNECTED,
                 mIntentArgument.getValue());
@@ -1097,7 +1138,8 @@
                         HeadsetHalConstants.AUDIO_STATE_CONNECTED, mTestDevice));
         verify(mHeadsetService,
                 timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(numBroadcastsSent)).sendBroadcastAsUser(
-                mIntentArgument.capture(), eq(UserHandle.ALL), eq(HeadsetService.BLUETOOTH_PERM));
+                mIntentArgument.capture(), eq(UserHandle.ALL), eq(BLUETOOTH_CONNECT),
+                any(Bundle.class));
         HeadsetTestUtils.verifyAudioStateBroadcast(mTestDevice,
                 BluetoothHeadset.STATE_AUDIO_CONNECTED, BluetoothHeadset.STATE_AUDIO_CONNECTING,
                 mIntentArgument.getValue());
@@ -1112,7 +1154,8 @@
         // No new broadcast due to lack of AUDIO_DISCONNECTING intent variable
         verify(mHeadsetService,
                 after(ASYNC_CALL_TIMEOUT_MILLIS).times(numBroadcastsSent)).sendBroadcastAsUser(
-                any(Intent.class), eq(UserHandle.ALL), eq(HeadsetService.BLUETOOTH_PERM));
+                any(Intent.class), eq(UserHandle.ALL), eq(BLUETOOTH_CONNECT),
+                any(Bundle.class));
         Assert.assertThat(mHeadsetStateMachine.getCurrentState(),
                 IsInstanceOf.instanceOf(HeadsetStateMachine.AudioDisconnecting.class));
         return numBroadcastsSent;
@@ -1125,7 +1168,8 @@
         mHeadsetStateMachine.sendMessage(HeadsetStateMachine.DISCONNECT, mTestDevice);
         verify(mHeadsetService,
                 timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(numBroadcastsSent)).sendBroadcastAsUser(
-                mIntentArgument.capture(), eq(UserHandle.ALL), eq(HeadsetService.BLUETOOTH_PERM));
+                mIntentArgument.capture(), eq(UserHandle.ALL), eq(BLUETOOTH_CONNECT),
+                any(Bundle.class));
         HeadsetTestUtils.verifyConnectionStateBroadcast(mTestDevice,
                 BluetoothProfile.STATE_DISCONNECTING, BluetoothProfile.STATE_CONNECTED,
                 mIntentArgument.getValue());
diff --git a/tests/unit/src/com/android/bluetooth/hfpclient/HeadsetClientServiceTest.java b/tests/unit/src/com/android/bluetooth/hfpclient/HeadsetClientServiceTest.java
index 17afcc3..6af6aae 100644
--- a/tests/unit/src/com/android/bluetooth/hfpclient/HeadsetClientServiceTest.java
+++ b/tests/unit/src/com/android/bluetooth/hfpclient/HeadsetClientServiceTest.java
@@ -16,8 +16,18 @@
 
 package com.android.bluetooth.hfpclient;
 
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.anyString;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.verify;
+
 import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
 import android.content.Context;
+import android.content.Intent;
+import android.os.BatteryManager;
 
 import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.MediumTest;
@@ -27,6 +37,7 @@
 import com.android.bluetooth.R;
 import com.android.bluetooth.TestUtils;
 import com.android.bluetooth.btservice.AdapterService;
+import com.android.bluetooth.btservice.storage.DatabaseManager;
 
 import org.junit.After;
 import org.junit.Assert;
@@ -45,9 +56,14 @@
     private BluetoothAdapter mAdapter = null;
     private Context mTargetContext;
 
+    private static final int STANDARD_WAIT_MILLIS = 1000;
+
     @Rule public final ServiceTestRule mServiceRule = new ServiceTestRule();
 
     @Mock private AdapterService mAdapterService;
+    @Mock private HeadsetClientStateMachine mStateMachine;
+
+    @Mock private DatabaseManager mDatabaseManager;
 
     @Before
     public void setUp() throws Exception {
@@ -56,6 +72,8 @@
                 mTargetContext.getResources().getBoolean(R.bool.profile_supported_hfpclient));
         MockitoAnnotations.initMocks(this);
         TestUtils.setAdapterService(mAdapterService);
+        doReturn(mDatabaseManager).when(mAdapterService).getDatabase();
+        doReturn(true, false).when(mAdapterService).isStartedProfile(anyString());
         TestUtils.startService(mServiceRule, HeadsetClientService.class);
         // At this point the service should have started so check NOT null
         mService = HeadsetClientService.getHeadsetClientService();
@@ -80,4 +98,41 @@
     public void testInitialize() {
         Assert.assertNotNull(HeadsetClientService.getHeadsetClientService());
     }
+
+    @Test
+    public void testSendBIEVtoStateMachineWhenBatteryChanged() {
+        // Put mock state machine
+        BluetoothDevice device =
+                BluetoothAdapter.getDefaultAdapter().getRemoteDevice("00:01:02:03:04:05");
+        mService.getStateMachineMap().put(device, mStateMachine);
+
+        // Send battery changed intent
+        Intent intent = new Intent(Intent.ACTION_BATTERY_CHANGED);
+        intent.putExtra(BatteryManager.EXTRA_LEVEL, 50);
+        mService.sendBroadcast(intent);
+
+        // Expect send BIEV to state machine
+        verify(mStateMachine, timeout(STANDARD_WAIT_MILLIS).times(1))
+                .sendMessage(
+                    eq(HeadsetClientStateMachine.SEND_BIEV),
+                    eq(2),
+                    anyInt());
+    }
+
+    @Test
+    public void testUpdateBatteryLevel() {
+        // Put mock state machine
+        BluetoothDevice device =
+                BluetoothAdapter.getDefaultAdapter().getRemoteDevice("00:01:02:03:04:05");
+        mService.getStateMachineMap().put(device, mStateMachine);
+
+        mService.updateBatteryLevel();
+
+        // Expect send BIEV to state machine
+        verify(mStateMachine, timeout(STANDARD_WAIT_MILLIS).times(1))
+                .sendMessage(
+                    eq(HeadsetClientStateMachine.SEND_BIEV),
+                    eq(2),
+                    anyInt());
+    }
 }
diff --git a/tests/unit/src/com/android/bluetooth/hfpclient/HeadsetClientStateMachineTest.java b/tests/unit/src/com/android/bluetooth/hfpclient/HeadsetClientStateMachineTest.java
index 64d048a..cb49e02 100644
--- a/tests/unit/src/com/android/bluetooth/hfpclient/HeadsetClientStateMachineTest.java
+++ b/tests/unit/src/com/android/bluetooth/hfpclient/HeadsetClientStateMachineTest.java
@@ -16,6 +16,7 @@
 import android.content.Intent;
 import android.content.res.Resources;
 import android.media.AudioManager;
+import android.os.Bundle;
 import android.os.HandlerThread;
 import android.os.Message;
 
@@ -144,7 +145,8 @@
                         IntentMatchers.hasExtra(BluetoothProfile.EXTRA_STATE,
                                 BluetoothProfile.STATE_DISCONNECTED),
                         IntentMatchers.hasExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE,
-                                BluetoothProfile.STATE_DISCONNECTED))), anyString());
+                                BluetoothProfile.STATE_DISCONNECTED))), anyString(),
+                any(Bundle.class));
         // Check we are in disconnected state still.
         Assert.assertThat(mHeadsetClientStateMachine.getCurrentState(),
                 IsInstanceOf.instanceOf(HeadsetClientStateMachine.Disconnected.class));
@@ -170,7 +172,7 @@
         ArgumentCaptor<Intent> intentArgument1 = ArgumentCaptor.forClass(Intent.class);
         verify(mHeadsetClientService, timeout(STANDARD_WAIT_MILLIS)).sendBroadcast(intentArgument1
                 .capture(),
-                anyString());
+                anyString(), any(Bundle.class));
         Assert.assertEquals(BluetoothProfile.STATE_CONNECTING,
                 intentArgument1.getValue().getIntExtra(BluetoothProfile.EXTRA_STATE, -1));
 
@@ -188,7 +190,7 @@
         // Verify that one connection state broadcast is executed
         ArgumentCaptor<Intent> intentArgument2 = ArgumentCaptor.forClass(Intent.class);
         verify(mHeadsetClientService, timeout(STANDARD_WAIT_MILLIS).times(2)).sendBroadcast(
-                intentArgument2.capture(), anyString());
+                intentArgument2.capture(), anyString(), any(Bundle.class));
         Assert.assertEquals(BluetoothProfile.STATE_CONNECTED,
                 intentArgument2.getValue().getIntExtra(BluetoothProfile.EXTRA_STATE, -1));
         // Check we are in connecting state now.
@@ -216,7 +218,7 @@
         ArgumentCaptor<Intent> intentArgument1 = ArgumentCaptor.forClass(Intent.class);
         verify(mHeadsetClientService, timeout(STANDARD_WAIT_MILLIS)).sendBroadcast(intentArgument1
                 .capture(),
-                anyString());
+                anyString(), any(Bundle.class));
         Assert.assertEquals(BluetoothProfile.STATE_CONNECTING,
                 intentArgument1.getValue().getIntExtra(BluetoothProfile.EXTRA_STATE, -1));
 
@@ -228,7 +230,8 @@
         ArgumentCaptor<Intent> intentArgument2 = ArgumentCaptor.forClass(Intent.class);
         verify(mHeadsetClientService,
                 timeout(HeadsetClientStateMachine.CONNECTING_TIMEOUT_MS * 2).times(
-                        2)).sendBroadcast(intentArgument2.capture(), anyString());
+                        2)).sendBroadcast(intentArgument2.capture(), anyString(),
+                any(Bundle.class));
         Assert.assertEquals(BluetoothProfile.STATE_DISCONNECTED,
                 intentArgument2.getValue().getIntExtra(BluetoothProfile.EXTRA_STATE, -1));
 
@@ -260,7 +263,7 @@
         ArgumentCaptor<Intent> intentArgument = ArgumentCaptor.forClass(Intent.class);
         verify(mHeadsetClientService, timeout(STANDARD_WAIT_MILLIS)).sendBroadcast(intentArgument
                 .capture(),
-                anyString());
+                anyString(), any(Bundle.class));
         Assert.assertEquals(BluetoothProfile.STATE_CONNECTING,
                 intentArgument.getValue().getIntExtra(BluetoothProfile.EXTRA_STATE, -1));
 
@@ -273,7 +276,7 @@
 
         verify(mHeadsetClientService, timeout(STANDARD_WAIT_MILLIS).times(2)).sendBroadcast(
                 intentArgument.capture(),
-                anyString());
+                anyString(), any(Bundle.class));
 
         Assert.assertEquals(BluetoothProfile.STATE_CONNECTED,
                 intentArgument.getValue().getIntExtra(BluetoothProfile.EXTRA_STATE, -1));
@@ -289,7 +292,7 @@
         mHeadsetClientStateMachine.sendMessage(StackEvent.STACK_EVENT, eventInBandRing);
         verify(mHeadsetClientService, timeout(STANDARD_WAIT_MILLIS).times(3)).sendBroadcast(
                 intentArgument.capture(),
-                anyString());
+                anyString(), any(Bundle.class));
         Assert.assertEquals(1,
                 intentArgument.getValue().getIntExtra(BluetoothHeadsetClient.EXTRA_IN_BAND_RING,
                         -1));
@@ -302,7 +305,7 @@
         TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper());
         verify(mHeadsetClientService, timeout(STANDARD_WAIT_MILLIS).times(3)).sendBroadcast(
                 intentArgument.capture(),
-                anyString());
+                anyString(),any(Bundle.class));
 
         // Provide information about the new call
         StackEvent eventIncomingCall = new StackEvent(StackEvent.EVENT_TYPE_CURRENT_CALLS);
@@ -316,7 +319,7 @@
         mHeadsetClientStateMachine.sendMessage(StackEvent.STACK_EVENT, eventIncomingCall);
         verify(mHeadsetClientService, timeout(STANDARD_WAIT_MILLIS).times(3)).sendBroadcast(
                 intentArgument.capture(),
-                anyString());
+                anyString(), any(Bundle.class));
 
 
         // Signal that the complete list of calls was received.
@@ -327,7 +330,7 @@
         verify(mHeadsetClientService, timeout(QUERY_CURRENT_CALLS_TEST_WAIT_MILLIS).times(4))
                 .sendBroadcast(
                 intentArgument.capture(),
-                anyString());
+                anyString(), any(Bundle.class));
         // Verify that the new call is being registered with the inBandRing flag set.
         Assert.assertEquals(true,
                 ((BluetoothHeadsetClientCall) intentArgument.getValue().getParcelableExtra(
@@ -338,7 +341,7 @@
         mHeadsetClientStateMachine.sendMessage(StackEvent.STACK_EVENT, eventInBandRing);
         verify(mHeadsetClientService, timeout(STANDARD_WAIT_MILLIS).times(5)).sendBroadcast(
                 intentArgument.capture(),
-                anyString());
+                anyString(), any(Bundle.class));
         Assert.assertEquals(0,
                 intentArgument.getValue().getIntExtra(BluetoothHeadsetClient.EXTRA_IN_BAND_RING,
                         -1));
@@ -355,7 +358,7 @@
         mHeadsetClientStateMachine.sendMessage(StackEvent.STACK_EVENT, connStCh);
         ArgumentCaptor<Intent> intentArgument = ArgumentCaptor.forClass(Intent.class);
         verify(mHeadsetClientService, timeout(STANDARD_WAIT_MILLIS).times(startBroadcastIndex))
-                .sendBroadcast(intentArgument.capture(), anyString());
+                .sendBroadcast(intentArgument.capture(), anyString(), any(Bundle.class));
         Assert.assertEquals(BluetoothProfile.STATE_CONNECTING,
                 intentArgument.getValue().getIntExtra(BluetoothProfile.EXTRA_STATE, -1));
         startBroadcastIndex++;
@@ -368,11 +371,12 @@
         StackEvent slcEvent = new StackEvent(StackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED);
         slcEvent.valueInt = HeadsetClientHalConstants.CONNECTION_STATE_SLC_CONNECTED;
         slcEvent.valueInt2 = HeadsetClientHalConstants.PEER_FEAT_ECS;
+        slcEvent.valueInt2 |= HeadsetClientHalConstants.PEER_FEAT_HF_IND;
         slcEvent.device = mTestDevice;
         mHeadsetClientStateMachine.sendMessage(StackEvent.STACK_EVENT, slcEvent);
         ArgumentCaptor<Intent> intentArgument = ArgumentCaptor.forClass(Intent.class);
         verify(mHeadsetClientService, timeout(STANDARD_WAIT_MILLIS).times(startBroadcastIndex))
-                .sendBroadcast(intentArgument.capture(), anyString());
+                .sendBroadcast(intentArgument.capture(), anyString(), any(Bundle.class));
         Assert.assertEquals(BluetoothProfile.STATE_CONNECTED,
                 intentArgument.getValue().getIntExtra(BluetoothProfile.EXTRA_STATE, -1));
         startBroadcastIndex++;
@@ -486,7 +490,7 @@
         // Validate broadcast intent
         ArgumentCaptor<Intent> intentArgument = ArgumentCaptor.forClass(Intent.class);
         verify(mHeadsetClientService, timeout(STANDARD_WAIT_MILLIS).times(expectedBroadcastIndex))
-                .sendBroadcast(intentArgument.capture(), anyString());
+                .sendBroadcast(intentArgument.capture(), anyString(), any(Bundle.class));
         Assert.assertEquals(BluetoothHeadsetClient.ACTION_VENDOR_SPECIFIC_HEADSETCLIENT_EVENT,
                 intentArgument.getValue().getAction());
         Assert.assertEquals(vendorId,
@@ -542,7 +546,7 @@
 
         // Validate no broadcast intent
         verify(mHeadsetClientService, atMost(expectedBroadcastIndex - 1))
-                .sendBroadcast(any(), anyString());
+                .sendBroadcast(any(), anyString(), any(Bundle.class));
     }
 
     /**
@@ -614,7 +618,7 @@
         // Validate broadcast intent
         ArgumentCaptor<Intent> intentArgument = ArgumentCaptor.forClass(Intent.class);
         verify(mHeadsetClientService, timeout(STANDARD_WAIT_MILLIS).times(expectedBroadcastIndex))
-                .sendBroadcast(intentArgument.capture(), anyString());
+                .sendBroadcast(intentArgument.capture(), anyString(), any(Bundle.class));
         Assert.assertEquals(BluetoothHeadsetClient.ACTION_AG_EVENT,
                 intentArgument.getValue().getAction());
         int state = intentArgument.getValue().getIntExtra(
@@ -622,4 +626,54 @@
         Assert.assertEquals(expectedState, state);
         return expectedBroadcastIndex + 1;
     }
+
+    /**
+     * Test send BIEV command
+     */
+    @MediumTest
+    @Test
+    public void testSendBIEVCommand() {
+        // Setup connection state machine to be in connected state
+        when(mHeadsetClientService.getConnectionPolicy(any(BluetoothDevice.class))).thenReturn(
+                BluetoothProfile.CONNECTION_POLICY_ALLOWED);
+        int expectedBroadcastIndex = 1;
+        expectedBroadcastIndex = setUpHfpClientConnection(expectedBroadcastIndex);
+        expectedBroadcastIndex = setUpServiceLevelConnection(expectedBroadcastIndex);
+
+        int indicator_id = 2;
+        int indicator_value = 50;
+
+        Message msg = mHeadsetClientStateMachine.obtainMessage(HeadsetClientStateMachine.SEND_BIEV);
+        msg.arg1 = indicator_id;
+        msg.arg2 = indicator_value;
+
+        mHeadsetClientStateMachine.sendMessage(msg);
+
+        verify(mNativeInterface, timeout(STANDARD_WAIT_MILLIS).times(1))
+                .sendATCmd(
+                        Utils.getBytesFromAddress(mTestDevice.getAddress()),
+                        HeadsetClientHalConstants.HANDSFREECLIENT_AT_CMD_BIEV,
+                        indicator_id,
+                        indicator_value,
+                        null);
+    }
+
+    /**
+     * Test state machine shall try to send AT+BIEV command to AG
+     * to update an init battery level.
+     */
+    @MediumTest
+    @Test
+    public void testSendBatteryUpdateIndicatorWhenConnect() {
+        // Setup connection state machine to be in connected state
+        when(mHeadsetClientService.getConnectionPolicy(any(BluetoothDevice.class))).thenReturn(
+                BluetoothProfile.CONNECTION_POLICY_ALLOWED);
+        int expectedBroadcastIndex = 1;
+
+        expectedBroadcastIndex = setUpHfpClientConnection(expectedBroadcastIndex);
+        expectedBroadcastIndex = setUpServiceLevelConnection(expectedBroadcastIndex);
+
+        verify(mHeadsetClientService, timeout(STANDARD_WAIT_MILLIS).times(1))
+                .updateBatteryLevel();
+    }
 }
diff --git a/tests/unit/src/com/android/bluetooth/hid/HidDeviceTest.java b/tests/unit/src/com/android/bluetooth/hid/HidDeviceTest.java
index eae1c1c..ad67968 100644
--- a/tests/unit/src/com/android/bluetooth/hid/HidDeviceTest.java
+++ b/tests/unit/src/com/android/bluetooth/hid/HidDeviceTest.java
@@ -38,6 +38,7 @@
 import com.android.bluetooth.R;
 import com.android.bluetooth.TestUtils;
 import com.android.bluetooth.btservice.AdapterService;
+import com.android.bluetooth.btservice.storage.DatabaseManager;
 
 import org.junit.After;
 import org.junit.Assert;
@@ -74,6 +75,7 @@
     private static final int CALLBACK_ON_VIRTUAL_UNPLUG = 6;
 
     @Mock private AdapterService mAdapterService;
+    @Mock private DatabaseManager mDatabaseManager;
     @Mock private HidDeviceNativeInterface mHidDeviceNativeInterface;
 
     private BluetoothAdapter mAdapter;
@@ -108,6 +110,8 @@
         // Set up mocks and test assets
         MockitoAnnotations.initMocks(this);
         TestUtils.setAdapterService(mAdapterService);
+        doReturn(mDatabaseManager).when(mAdapterService).getDatabase();
+        doReturn(true, false).when(mAdapterService).isStartedProfile(anyString());
         setHidDeviceNativeInterfaceInstance(mHidDeviceNativeInterface);
         // This line must be called to make sure relevant objects are initialized properly
         mAdapter = BluetoothAdapter.getDefaultAdapter();
@@ -163,7 +167,7 @@
             try {
                 mConnectionStateChangedQueue.put(intent);
             } catch (InterruptedException e) {
-                Assert.fail("Cannot add Intent to the queue");
+                throw new AssertionError("Cannot add Intent to the queue", e);
             }
         }
     }
@@ -174,9 +178,8 @@
             Assert.assertNotNull(intent);
             return intent;
         } catch (InterruptedException e) {
-            Assert.fail("Cannot obtain an Intent from the queue");
+            throw new AssertionError("Cannot obtain an Intent from the queue", e);
         }
-        return null;
     }
 
     private void verifyConnectionStateIntent(int timeoutMs, BluetoothDevice device, int newState,
@@ -197,7 +200,7 @@
             int lastCallbackType = lastCallback;
             Assert.assertEquals(callbackType, lastCallbackType);
         } catch (InterruptedException e) {
-            Assert.fail("Cannot obtain a callback from the queue");
+            throw new AssertionError("Cannot obtain a callback from the queue", e);
         }
     }
 
@@ -210,7 +213,7 @@
                     mCallbackQueue.put(CALLBACK_APP_UNREGISTERED);
                 }
             } catch (InterruptedException e) {
-                Assert.fail("Cannot add Intent to the queue");
+                throw new AssertionError("Cannot add Intent to the queue", e);
             }
         }
 
@@ -222,7 +225,7 @@
             try {
                 mCallbackQueue.put(CALLBACK_ON_GET_REPORT);
             } catch (InterruptedException e) {
-                Assert.fail("Cannot add Intent to the queue");
+                throw new AssertionError("Cannot add Intent to the queue", e);
             }
         }
 
@@ -230,7 +233,7 @@
             try {
                 mCallbackQueue.put(CALLBACK_ON_SET_REPORT);
             } catch (InterruptedException e) {
-                Assert.fail("Cannot add Intent to the queue");
+                throw new AssertionError("Cannot add Intent to the queue", e);
             }
         }
 
@@ -238,7 +241,7 @@
             try {
                 mCallbackQueue.put(CALLBACK_ON_SET_PROTOCOL);
             } catch (InterruptedException e) {
-                Assert.fail("Cannot add Intent to the queue");
+                throw new AssertionError("Cannot add Intent to the queue", e);
             }
         }
 
@@ -246,7 +249,8 @@
             try {
                 mCallbackQueue.put(CALLBACK_ON_INTR_DATA);
             } catch (InterruptedException e) {
-                Assert.fail("Cannot add Intent to the queue");
+                throw new AssertionError("Cannot add Intent to the queue", e);
+
             }
         }
 
@@ -254,7 +258,7 @@
             try {
                 mCallbackQueue.put(CALLBACK_ON_VIRTUAL_UNPLUG);
             } catch (InterruptedException e) {
-                Assert.fail("Cannot add Intent to the queue");
+                throw new AssertionError("Cannot add Intent to the queue", e);
             }
         }
     }
diff --git a/tests/unit/src/com/android/bluetooth/hid/HidHostServiceTest.java b/tests/unit/src/com/android/bluetooth/hid/HidHostServiceTest.java
index c88fb75..72bd648 100644
--- a/tests/unit/src/com/android/bluetooth/hid/HidHostServiceTest.java
+++ b/tests/unit/src/com/android/bluetooth/hid/HidHostServiceTest.java
@@ -62,6 +62,8 @@
                 mTargetContext.getResources().getBoolean(R.bool.profile_supported_hid_host));
         MockitoAnnotations.initMocks(this);
         TestUtils.setAdapterService(mAdapterService);
+        when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager);
+        when(mAdapterService.isStartedProfile(anyString())).thenReturn(true);
         TestUtils.startService(mServiceRule, HidHostService.class);
         mService = HidHostService.getHidHostService();
         Assert.assertNotNull(mService);
@@ -78,6 +80,7 @@
         if (!mTargetContext.getResources().getBoolean(R.bool.profile_supported_hid_host)) {
             return;
         }
+        when(mAdapterService.isStartedProfile(anyString())).thenReturn(false);
         TestUtils.stopService(mServiceRule, HidHostService.class);
         mService = HidHostService.getHidHostService();
         Assert.assertNull(mService);
@@ -128,9 +131,6 @@
                 badBondState, BluetoothProfile.CONNECTION_POLICY_ALLOWED, false);
         testOkToConnectCase(mTestDevice,
                 badBondState, badPriorityValue, false);
-        // Restore prirority to undefined for this test device
-        Assert.assertTrue(mService.setConnectionPolicy(
-                mTestDevice, BluetoothProfile.CONNECTION_POLICY_UNKNOWN));
     }
 
     /**
@@ -144,7 +144,6 @@
     private void testOkToConnectCase(BluetoothDevice device, int bondState, int priority,
             boolean expected) {
         doReturn(bondState).when(mAdapterService).getBondState(device);
-        when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager);
         when(mDatabaseManager.getProfileConnectionPolicy(device, BluetoothProfile.HID_HOST))
                 .thenReturn(priority);
 
diff --git a/tests/unit/src/com/android/bluetooth/le_audio/LeAudioServiceTest.java b/tests/unit/src/com/android/bluetooth/le_audio/LeAudioServiceTest.java
new file mode 100644
index 0000000..94c2468
--- /dev/null
+++ b/tests/unit/src/com/android/bluetooth/le_audio/LeAudioServiceTest.java
@@ -0,0 +1,717 @@
+/*
+ * Copyright 2020 HIMSA II K/S - www.himsa.com.
+ * Represented by EHIMA - www.ehima.com
+ *
+ * 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.le_audio;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyString;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.when;
+
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothLeAudio;
+import android.bluetooth.BluetoothProfile;
+import android.bluetooth.BluetoothUuid;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.media.AudioManager;
+import android.os.ParcelUuid;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.MediumTest;
+import androidx.test.rule.ServiceTestRule;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.bluetooth.TestUtils;
+import com.android.bluetooth.btservice.AdapterService;
+import com.android.bluetooth.btservice.storage.DatabaseManager;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.TimeoutException;
+
+@MediumTest
+@RunWith(AndroidJUnit4.class)
+public class LeAudioServiceTest {
+    private BluetoothAdapter mAdapter;
+    private Context mTargetContext;
+    private LeAudioService mService;
+    private BluetoothDevice mLeftDevice;
+    private BluetoothDevice mRightDevice;
+    private BluetoothDevice mSingleDevice;
+    private HashMap<BluetoothDevice, LinkedBlockingQueue<Intent>> mDeviceQueueMap;
+    private static final int TIMEOUT_MS = 1000;
+
+    private BroadcastReceiver mLeAudioIntentReceiver;
+
+    @Mock private AdapterService mAdapterService;
+    @Mock private DatabaseManager mDatabaseManager;
+    @Mock private LeAudioNativeInterface mNativeInterface;
+    @Mock private AudioManager mAudioManager;
+
+    @Rule public final ServiceTestRule mServiceRule = new ServiceTestRule();
+
+    @Before
+    public void setUp() throws Exception {
+        mTargetContext = InstrumentationRegistry.getTargetContext();
+        // Set up mocks and test assets
+        MockitoAnnotations.initMocks(this);
+
+        TestUtils.setAdapterService(mAdapterService);
+        doReturn(mDatabaseManager).when(mAdapterService).getDatabase();
+        doReturn(true, false).when(mAdapterService).isStartedProfile(anyString());
+
+        mAdapter = BluetoothAdapter.getDefaultAdapter();
+
+        startService();
+        mService.mLeAudioNativeInterface = mNativeInterface;
+
+        // Override the timeout value to speed up the test
+        LeAudioStateMachine.sConnectTimeoutMs = TIMEOUT_MS;    // 1s
+
+        // Set up the Connection State Changed receiver
+        IntentFilter filter = new IntentFilter();
+        filter.addAction(BluetoothLeAudio.ACTION_LE_AUDIO_CONNECTION_STATE_CHANGED);
+        mLeAudioIntentReceiver = new LeAudioIntentReceiver();
+        mTargetContext.registerReceiver(mLeAudioIntentReceiver, filter);
+
+        // Get a device for testing
+        mLeftDevice = TestUtils.getTestDevice(mAdapter, 0);
+        mRightDevice = TestUtils.getTestDevice(mAdapter, 1);
+        mSingleDevice = TestUtils.getTestDevice(mAdapter, 2);
+        mDeviceQueueMap = new HashMap<>();
+        mDeviceQueueMap.put(mLeftDevice, new LinkedBlockingQueue<>());
+        mDeviceQueueMap.put(mRightDevice, new LinkedBlockingQueue<>());
+        mDeviceQueueMap.put(mSingleDevice, new LinkedBlockingQueue<>());
+        doReturn(BluetoothDevice.BOND_BONDED).when(mAdapterService)
+                .getBondState(any(BluetoothDevice.class));
+        doReturn(new ParcelUuid[]{BluetoothUuid.LE_AUDIO}).when(mAdapterService)
+                .getRemoteUuids(any(BluetoothDevice.class));
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        stopService();
+        mTargetContext.unregisterReceiver(mLeAudioIntentReceiver);
+        mDeviceQueueMap.clear();
+        TestUtils.clearAdapterService(mAdapterService);
+    }
+
+    private void startService() throws TimeoutException {
+        TestUtils.startService(mServiceRule, LeAudioService.class);
+        mService = LeAudioService.getLeAudioService();
+        assertThat(mService).isNotNull();
+    }
+
+    private void stopService() throws TimeoutException {
+        TestUtils.stopService(mServiceRule, LeAudioService.class);
+        mService = LeAudioService.getLeAudioService();
+        assertThat(mService).isNull();
+    }
+
+    private class LeAudioIntentReceiver extends BroadcastReceiver {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            if (BluetoothLeAudio.ACTION_LE_AUDIO_CONNECTION_STATE_CHANGED
+                    .equals(intent.getAction())) {
+                try {
+                    BluetoothDevice device = intent.getParcelableExtra(
+                            BluetoothDevice.EXTRA_DEVICE);
+                    assertThat(device).isNotNull();
+                    LinkedBlockingQueue<Intent> queue = mDeviceQueueMap.get(device);
+                    assertThat(queue).isNotNull();
+                    queue.put(intent);
+                } catch (InterruptedException e) {
+                    assertWithMessage("Cannot add Intent to the Connection State queue: "
+                            + e.getMessage()).fail();
+                }
+            }
+        }
+    }
+
+    private void verifyConnectionStateIntent(int timeoutMs, BluetoothDevice device,
+            int newState, int prevState) {
+        Intent intent = TestUtils.waitForIntent(timeoutMs, mDeviceQueueMap.get(device));
+        assertThat(intent).isNotNull();
+        assertThat(intent.getAction())
+                .isEqualTo(BluetoothLeAudio.ACTION_LE_AUDIO_CONNECTION_STATE_CHANGED);
+        assertThat(device).isEqualTo(intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE));
+        assertThat(newState).isEqualTo(intent.getIntExtra(BluetoothProfile.EXTRA_STATE, -1));
+        assertThat(prevState).isEqualTo(intent.getIntExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE,
+                -1));
+    }
+
+    /**
+     * Test getting LeAudio Service: getLeAudioService()
+     */
+    @Test
+    public void testGetLeAudioService() {
+        assertThat(mService).isEqualTo(LeAudioService.getLeAudioService());
+    }
+
+    /**
+     * Test stop LeAudio Service
+     */
+    @Test
+    public void testStopLeAudioService() {
+        // Prepare: connect
+        connectDevice(mLeftDevice);
+        // LeAudio Service is already running: test stop(). Note: must be done on the main thread
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
+            public void run() {
+                assertThat(mService.stop()).isTrue();
+            }
+        });
+        // Try to restart the service. Note: must be done on the main thread
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
+            public void run() {
+                assertThat(mService.start()).isTrue();
+            }
+        });
+    }
+
+    /**
+     * Test get/set priority for BluetoothDevice
+     */
+    @Test
+    public void testGetSetPriority() {
+        when(mDatabaseManager.getProfileConnectionPolicy(mLeftDevice, BluetoothProfile.LE_AUDIO))
+                .thenReturn(BluetoothProfile.CONNECTION_POLICY_UNKNOWN);
+        assertWithMessage("Initial device priority")
+                .that(BluetoothProfile.CONNECTION_POLICY_UNKNOWN)
+                .isEqualTo(mService.getConnectionPolicy(mLeftDevice));
+
+        when(mDatabaseManager.getProfileConnectionPolicy(mLeftDevice, BluetoothProfile.LE_AUDIO))
+                .thenReturn(BluetoothProfile.CONNECTION_POLICY_FORBIDDEN);
+        assertWithMessage("Setting device priority to PRIORITY_OFF")
+                .that(BluetoothProfile.CONNECTION_POLICY_FORBIDDEN)
+                .isEqualTo(mService.getConnectionPolicy(mLeftDevice));
+
+        when(mDatabaseManager.getProfileConnectionPolicy(mLeftDevice, BluetoothProfile.LE_AUDIO))
+                .thenReturn(BluetoothProfile.CONNECTION_POLICY_ALLOWED);
+        assertWithMessage("Setting device priority to PRIORITY_ON")
+                .that(BluetoothProfile.CONNECTION_POLICY_ALLOWED)
+                .isEqualTo(mService.getConnectionPolicy(mLeftDevice));
+    }
+
+    /**
+     *  Helper function to test okToConnect() method
+     *
+     *  @param device test device
+     *  @param bondState bond state value, could be invalid
+     *  @param priority value, could be invalid, could be invalid
+     *  @param expected expected result from okToConnect()
+     */
+    private void testOkToConnectCase(BluetoothDevice device, int bondState, int priority,
+            boolean expected) {
+        doReturn(bondState).when(mAdapterService).getBondState(device);
+        when(mDatabaseManager.getProfileConnectionPolicy(device, BluetoothProfile.LE_AUDIO))
+                .thenReturn(priority);
+        assertThat(expected).isEqualTo(mService.okToConnect(device));
+    }
+
+    /**
+     *  Test okToConnect method using various test cases
+     */
+    @Test
+    public void testOkToConnect() {
+        int badPriorityValue = 1024;
+        int badBondState = 42;
+        testOkToConnectCase(mSingleDevice,
+                BluetoothDevice.BOND_NONE, BluetoothProfile.CONNECTION_POLICY_UNKNOWN, false);
+        testOkToConnectCase(mSingleDevice,
+                BluetoothDevice.BOND_NONE, BluetoothProfile.CONNECTION_POLICY_FORBIDDEN, false);
+        testOkToConnectCase(mSingleDevice,
+                BluetoothDevice.BOND_NONE, BluetoothProfile.CONNECTION_POLICY_ALLOWED, false);
+        testOkToConnectCase(mSingleDevice,
+                BluetoothDevice.BOND_NONE, badPriorityValue, false);
+        testOkToConnectCase(mSingleDevice,
+                BluetoothDevice.BOND_BONDING, BluetoothProfile.CONNECTION_POLICY_UNKNOWN, false);
+        testOkToConnectCase(mSingleDevice,
+                BluetoothDevice.BOND_BONDING, BluetoothProfile.CONNECTION_POLICY_FORBIDDEN, false);
+        testOkToConnectCase(mSingleDevice,
+                BluetoothDevice.BOND_BONDING, BluetoothProfile.CONNECTION_POLICY_ALLOWED, false);
+        testOkToConnectCase(mSingleDevice,
+                BluetoothDevice.BOND_BONDING, badPriorityValue, false);
+        testOkToConnectCase(mSingleDevice,
+                BluetoothDevice.BOND_BONDED, BluetoothProfile.CONNECTION_POLICY_UNKNOWN, true);
+        testOkToConnectCase(mSingleDevice,
+                BluetoothDevice.BOND_BONDED, BluetoothProfile.CONNECTION_POLICY_FORBIDDEN, false);
+        testOkToConnectCase(mSingleDevice,
+                BluetoothDevice.BOND_BONDED, BluetoothProfile.CONNECTION_POLICY_ALLOWED, true);
+        testOkToConnectCase(mSingleDevice,
+                BluetoothDevice.BOND_BONDED, badPriorityValue, false);
+        testOkToConnectCase(mSingleDevice,
+                badBondState, BluetoothProfile.CONNECTION_POLICY_UNKNOWN, false);
+        testOkToConnectCase(mSingleDevice,
+                badBondState, BluetoothProfile.CONNECTION_POLICY_FORBIDDEN, false);
+        testOkToConnectCase(mSingleDevice,
+                badBondState, BluetoothProfile.CONNECTION_POLICY_ALLOWED, false);
+        testOkToConnectCase(mSingleDevice,
+                badBondState, badPriorityValue, false);
+    }
+
+    /**
+     * Test that an outgoing connection to device that does not have Le Audio UUID is rejected
+     */
+    @Test
+    public void testOutgoingConnectMissingLeAudioUuid() {
+        // Update the device priority so okToConnect() returns true
+        when(mDatabaseManager.getProfileConnectionPolicy(mLeftDevice, BluetoothProfile.LE_AUDIO))
+                .thenReturn(BluetoothProfile.CONNECTION_POLICY_ALLOWED);
+        when(mDatabaseManager
+                .getProfileConnectionPolicy(mRightDevice, BluetoothProfile.LE_AUDIO))
+                .thenReturn(BluetoothProfile.CONNECTION_POLICY_FORBIDDEN);
+        when(mDatabaseManager
+                .getProfileConnectionPolicy(mSingleDevice, BluetoothProfile.LE_AUDIO))
+                .thenReturn(BluetoothProfile.CONNECTION_POLICY_FORBIDDEN);
+        doReturn(true).when(mNativeInterface).connectLeAudio(any(BluetoothDevice.class));
+        doReturn(true).when(mNativeInterface).disconnectLeAudio(any(BluetoothDevice.class));
+
+        // Return No UUID
+        doReturn(new ParcelUuid[]{}).when(mAdapterService)
+                .getRemoteUuids(any(BluetoothDevice.class));
+
+        // Send a connect request
+        assertWithMessage("Connect expected to fail").that(mService.connect(mLeftDevice)).isFalse();
+    }
+
+    /**
+     * Test that an outgoing connection to device with PRIORITY_OFF is rejected
+     */
+    @Test
+    public void testOutgoingConnectPriorityOff() {
+        doReturn(true).when(mNativeInterface).connectLeAudio(any(BluetoothDevice.class));
+        doReturn(true).when(mNativeInterface).disconnectLeAudio(any(BluetoothDevice.class));
+
+        // Set the device priority to PRIORITY_OFF so connect() should fail
+        when(mDatabaseManager
+                .getProfileConnectionPolicy(mLeftDevice, BluetoothProfile.LE_AUDIO))
+                .thenReturn(BluetoothProfile.CONNECTION_POLICY_FORBIDDEN);
+
+        // Send a connect request
+        assertWithMessage("Connect expected to fail").that(mService.connect(mLeftDevice)).isFalse();
+    }
+
+    /**
+     * Test that an outgoing connection times out
+     */
+    @Test
+    public void testOutgoingConnectTimeout() {
+        // Update the device priority so okToConnect() returns true
+        when(mDatabaseManager
+                .getProfileConnectionPolicy(mLeftDevice, BluetoothProfile.LE_AUDIO))
+                .thenReturn(BluetoothProfile.CONNECTION_POLICY_ALLOWED);
+        when(mDatabaseManager
+                .getProfileConnectionPolicy(mRightDevice, BluetoothProfile.LE_AUDIO))
+                .thenReturn(BluetoothProfile.CONNECTION_POLICY_FORBIDDEN);
+        when(mDatabaseManager
+                .getProfileConnectionPolicy(mSingleDevice, BluetoothProfile.LE_AUDIO))
+                .thenReturn(BluetoothProfile.CONNECTION_POLICY_FORBIDDEN);
+        doReturn(true).when(mNativeInterface).connectLeAudio(any(BluetoothDevice.class));
+        doReturn(true).when(mNativeInterface).disconnectLeAudio(any(BluetoothDevice.class));
+
+        // Send a connect request
+        assertWithMessage("Connect failed").that(mService.connect(mLeftDevice)).isTrue();
+
+        // Verify the connection state broadcast, and that we are in Connecting state
+        verifyConnectionStateIntent(TIMEOUT_MS, mLeftDevice, BluetoothProfile.STATE_CONNECTING,
+                BluetoothProfile.STATE_DISCONNECTED);
+        assertThat(mService.getConnectionState(mLeftDevice))
+                .isEqualTo(BluetoothProfile.STATE_CONNECTING);
+
+        // Verify the connection state broadcast, and that we are in Disconnected state
+        verifyConnectionStateIntent(LeAudioStateMachine.sConnectTimeoutMs * 2,
+                mLeftDevice, BluetoothProfile.STATE_DISCONNECTED,
+                BluetoothProfile.STATE_CONNECTING);
+        assertThat(mService.getConnectionState(mLeftDevice))
+                .isEqualTo(BluetoothProfile.STATE_DISCONNECTED);
+    }
+
+    /**
+     * Test that the outgoing connect/disconnect and audio switch is successful.
+     */
+    @Test
+    public void testAudioManagerConnectDisconnect() {
+        // Update the device priority so okToConnect() returns true
+        when(mDatabaseManager
+                .getProfileConnectionPolicy(mLeftDevice, BluetoothProfile.LE_AUDIO))
+                .thenReturn(BluetoothProfile.CONNECTION_POLICY_ALLOWED);
+        when(mDatabaseManager
+                .getProfileConnectionPolicy(mRightDevice, BluetoothProfile.LE_AUDIO))
+                .thenReturn(BluetoothProfile.CONNECTION_POLICY_ALLOWED);
+        when(mDatabaseManager
+                .getProfileConnectionPolicy(mSingleDevice, BluetoothProfile.LE_AUDIO))
+                .thenReturn(BluetoothProfile.CONNECTION_POLICY_FORBIDDEN);
+        doReturn(true).when(mNativeInterface).connectLeAudio(any(BluetoothDevice.class));
+        doReturn(true).when(mNativeInterface).disconnectLeAudio(any(BluetoothDevice.class));
+
+        // Send a connect request
+        assertWithMessage("Connect failed").that(mService.connect(mLeftDevice)).isTrue();
+        assertWithMessage("Connect failed").that(mService.connect(mRightDevice)).isTrue();
+
+        // Verify the connection state broadcast, and that we are in Connecting state
+        verifyConnectionStateIntent(TIMEOUT_MS, mLeftDevice, BluetoothProfile.STATE_CONNECTING,
+                BluetoothProfile.STATE_DISCONNECTED);
+        assertThat(mService.getConnectionState(mLeftDevice))
+                .isEqualTo(BluetoothProfile.STATE_CONNECTING);
+        verifyConnectionStateIntent(TIMEOUT_MS, mRightDevice, BluetoothProfile.STATE_CONNECTING,
+                BluetoothProfile.STATE_DISCONNECTED);
+        assertThat(mService.getConnectionState(mRightDevice))
+                .isEqualTo(BluetoothProfile.STATE_CONNECTING);
+
+        LeAudioStackEvent connCompletedEvent;
+        // Send a message to trigger connection completed
+        connCompletedEvent = new LeAudioStackEvent(
+                LeAudioStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED);
+        connCompletedEvent.device = mLeftDevice;
+        connCompletedEvent.valueInt1 = LeAudioStackEvent.CONNECTION_STATE_CONNECTED;
+        mService.messageFromNative(connCompletedEvent);
+
+        // Verify the connection state broadcast, and that we are in Connected state
+        verifyConnectionStateIntent(TIMEOUT_MS, mLeftDevice, BluetoothProfile.STATE_CONNECTED,
+                BluetoothProfile.STATE_CONNECTING);
+        assertThat(mService.getConnectionState(mLeftDevice))
+                .isEqualTo(BluetoothProfile.STATE_CONNECTED);
+
+        // Send a message to trigger connection completed for right side
+        connCompletedEvent = new LeAudioStackEvent(
+                LeAudioStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED);
+        connCompletedEvent.device = mRightDevice;
+        connCompletedEvent.valueInt1 = LeAudioStackEvent.CONNECTION_STATE_CONNECTED;
+        mService.messageFromNative(connCompletedEvent);
+
+        // Verify the connection state broadcast, and that we are in Connected state for right side
+        verifyConnectionStateIntent(TIMEOUT_MS, mRightDevice, BluetoothProfile.STATE_CONNECTED,
+                BluetoothProfile.STATE_CONNECTING);
+        assertThat(mService.getConnectionState(mRightDevice))
+                .isEqualTo(BluetoothProfile.STATE_CONNECTED);
+
+        // Verify the list of connected devices
+        assertThat(mService.getConnectedDevices().contains(mLeftDevice)).isTrue();
+        assertThat(mService.getConnectedDevices().contains(mRightDevice)).isTrue();
+
+        // Send a disconnect request
+        assertWithMessage("Disconnect failed").that(mService.disconnect(mLeftDevice)).isTrue();
+        assertWithMessage("Disconnect failed").that(mService.disconnect(mRightDevice)).isTrue();
+
+        // Verify the connection state broadcast, and that we are in Disconnecting state
+        verifyConnectionStateIntent(TIMEOUT_MS, mLeftDevice, BluetoothProfile.STATE_DISCONNECTING,
+                BluetoothProfile.STATE_CONNECTED);
+        assertThat(BluetoothProfile.STATE_DISCONNECTING)
+                .isEqualTo(mService.getConnectionState(mLeftDevice));
+        verifyConnectionStateIntent(TIMEOUT_MS, mRightDevice, BluetoothProfile.STATE_DISCONNECTING,
+                BluetoothProfile.STATE_CONNECTED);
+        assertThat(BluetoothProfile.STATE_DISCONNECTING)
+                .isEqualTo(mService.getConnectionState(mRightDevice));
+
+        // Send a message to trigger disconnection completed
+        connCompletedEvent = new LeAudioStackEvent(
+                LeAudioStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED);
+        connCompletedEvent.device = mLeftDevice;
+        connCompletedEvent.valueInt1 = LeAudioStackEvent.CONNECTION_STATE_DISCONNECTED;
+        mService.messageFromNative(connCompletedEvent);
+
+        // Verify the connection state broadcast, and that we are in Disconnected state
+        verifyConnectionStateIntent(TIMEOUT_MS, mLeftDevice, BluetoothProfile.STATE_DISCONNECTED,
+                BluetoothProfile.STATE_DISCONNECTING);
+        assertThat(BluetoothProfile.STATE_DISCONNECTED)
+                .isEqualTo(mService.getConnectionState(mLeftDevice));
+
+        // Send a message to trigger disconnection completed to the right device
+        connCompletedEvent = new LeAudioStackEvent(
+                LeAudioStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED);
+        connCompletedEvent.device = mRightDevice;
+        connCompletedEvent.valueInt1 = LeAudioStackEvent.CONNECTION_STATE_DISCONNECTED;
+        mService.messageFromNative(connCompletedEvent);
+
+        // Verify the connection state broadcast, and that we are in Disconnected state
+        verifyConnectionStateIntent(TIMEOUT_MS, mRightDevice, BluetoothProfile.STATE_DISCONNECTED,
+                BluetoothProfile.STATE_DISCONNECTING);
+        assertThat(BluetoothProfile.STATE_DISCONNECTED)
+                .isEqualTo(mService.getConnectionState(mRightDevice));
+
+        // Verify the list of connected devices
+        assertThat(mService.getConnectedDevices().contains(mLeftDevice)).isFalse();
+        assertThat(mService.getConnectedDevices().contains(mRightDevice)).isFalse();
+    }
+
+    /**
+     * Test that only CONNECTION_STATE_CONNECTED or CONNECTION_STATE_CONNECTING Le Audio stack
+     * events will create a state machine.
+     */
+    @Test
+    public void testCreateStateMachineStackEvents() {
+        // Update the device priority so okToConnect() returns true
+        when(mDatabaseManager
+                .getProfileConnectionPolicy(mLeftDevice, BluetoothProfile.LE_AUDIO))
+                .thenReturn(BluetoothProfile.CONNECTION_POLICY_ALLOWED);
+        when(mDatabaseManager
+                .getProfileConnectionPolicy(mRightDevice, BluetoothProfile.LE_AUDIO))
+                .thenReturn(BluetoothProfile.CONNECTION_POLICY_FORBIDDEN);
+        when(mDatabaseManager
+                .getProfileConnectionPolicy(mSingleDevice, BluetoothProfile.LE_AUDIO))
+                .thenReturn(BluetoothProfile.CONNECTION_POLICY_FORBIDDEN);
+        doReturn(true).when(mNativeInterface).connectLeAudio(any(BluetoothDevice.class));
+        doReturn(true).when(mNativeInterface).disconnectLeAudio(any(BluetoothDevice.class));
+
+        // Le Audio stack event: CONNECTION_STATE_CONNECTING - state machine should be created
+        generateConnectionMessageFromNative(mLeftDevice, BluetoothProfile.STATE_CONNECTING,
+                BluetoothProfile.STATE_DISCONNECTED);
+        assertThat(BluetoothProfile.STATE_CONNECTING)
+                .isEqualTo(mService.getConnectionState(mLeftDevice));
+        assertThat(mService.getDevices().contains(mLeftDevice)).isTrue();
+
+        // LeAudio stack event: CONNECTION_STATE_DISCONNECTED - state machine should be removed
+        generateConnectionMessageFromNative(mLeftDevice, BluetoothProfile.STATE_DISCONNECTED,
+                BluetoothProfile.STATE_CONNECTING);
+        assertThat(BluetoothProfile.STATE_DISCONNECTED)
+                .isEqualTo(mService.getConnectionState(mLeftDevice));
+        assertThat(mService.getDevices().contains(mLeftDevice)).isTrue();
+        mService.bondStateChanged(mLeftDevice, BluetoothDevice.BOND_NONE);
+        assertThat(mService.getDevices().contains(mLeftDevice)).isFalse();
+
+        // stack event: CONNECTION_STATE_CONNECTED - state machine should be created
+        generateConnectionMessageFromNative(mLeftDevice, BluetoothProfile.STATE_CONNECTED,
+                BluetoothProfile.STATE_DISCONNECTED);
+        assertThat(BluetoothProfile.STATE_CONNECTED)
+                .isEqualTo(mService.getConnectionState(mLeftDevice));
+        assertThat(mService.getDevices().contains(mLeftDevice)).isTrue();
+
+        // stack event: CONNECTION_STATE_DISCONNECTED - state machine should be removed
+        generateConnectionMessageFromNative(mLeftDevice, BluetoothProfile.STATE_DISCONNECTED,
+                BluetoothProfile.STATE_CONNECTED);
+        assertThat(BluetoothProfile.STATE_DISCONNECTED)
+                .isEqualTo(mService.getConnectionState(mLeftDevice));
+        assertThat(mService.getDevices().contains(mLeftDevice)).isTrue();
+        mService.bondStateChanged(mLeftDevice, BluetoothDevice.BOND_NONE);
+        assertThat(mService.getDevices().contains(mLeftDevice)).isFalse();
+
+        // stack event: CONNECTION_STATE_DISCONNECTING - state machine should not be created
+        generateUnexpectedConnectionMessageFromNative(mLeftDevice,
+                BluetoothProfile.STATE_DISCONNECTING,
+                BluetoothProfile.STATE_DISCONNECTED);
+        assertThat(BluetoothProfile.STATE_DISCONNECTED)
+                .isEqualTo(mService.getConnectionState(mLeftDevice));
+        assertThat(mService.getDevices().contains(mLeftDevice)).isFalse();
+
+        // stack event: CONNECTION_STATE_DISCONNECTED - state machine should not be created
+        generateUnexpectedConnectionMessageFromNative(mLeftDevice,
+                BluetoothProfile.STATE_DISCONNECTED,
+                BluetoothProfile.STATE_DISCONNECTED);
+        assertThat(BluetoothProfile.STATE_DISCONNECTED)
+                .isEqualTo(mService.getConnectionState(mLeftDevice));
+        assertThat(mService.getDevices().contains(mLeftDevice)).isFalse();
+    }
+
+    /**
+     * Test that a state machine in DISCONNECTED state is removed only after the device is unbond.
+     */
+    @Test
+    public void testDeleteStateMachineUnbondEvents() {
+        // Update the device priority so okToConnect() returns true
+        when(mDatabaseManager
+                .getProfileConnectionPolicy(mLeftDevice, BluetoothProfile.LE_AUDIO))
+                .thenReturn(BluetoothProfile.CONNECTION_POLICY_ALLOWED);
+        when(mDatabaseManager
+                .getProfileConnectionPolicy(mRightDevice, BluetoothProfile.LE_AUDIO))
+                .thenReturn(BluetoothProfile.CONNECTION_POLICY_FORBIDDEN);
+        when(mDatabaseManager
+                .getProfileConnectionPolicy(mSingleDevice, BluetoothProfile.LE_AUDIO))
+                .thenReturn(BluetoothProfile.CONNECTION_POLICY_FORBIDDEN);
+        doReturn(true).when(mNativeInterface).connectLeAudio(any(BluetoothDevice.class));
+        doReturn(true).when(mNativeInterface).disconnectLeAudio(any(BluetoothDevice.class));
+
+        // LeAudio stack event: CONNECTION_STATE_CONNECTING - state machine should be created
+        generateConnectionMessageFromNative(mLeftDevice, BluetoothProfile.STATE_CONNECTING,
+                BluetoothProfile.STATE_DISCONNECTED);
+        assertThat(BluetoothProfile.STATE_CONNECTING)
+                .isEqualTo(mService.getConnectionState(mLeftDevice));
+        assertThat(mService.getDevices().contains(mLeftDevice)).isTrue();
+        // Device unbond - state machine is not removed
+        mService.bondStateChanged(mLeftDevice, BluetoothDevice.BOND_NONE);
+        assertThat(mService.getDevices().contains(mLeftDevice)).isTrue();
+
+        // LeAudio stack event: CONNECTION_STATE_CONNECTED - state machine is not removed
+        mService.bondStateChanged(mLeftDevice, BluetoothDevice.BOND_BONDED);
+        generateConnectionMessageFromNative(mLeftDevice, BluetoothProfile.STATE_CONNECTED,
+                BluetoothProfile.STATE_CONNECTING);
+        assertThat(BluetoothProfile.STATE_CONNECTED)
+                .isEqualTo(mService.getConnectionState(mLeftDevice));
+        assertThat(mService.getDevices().contains(mLeftDevice)).isTrue();
+        // Device unbond - state machine is not removed
+        mService.bondStateChanged(mLeftDevice, BluetoothDevice.BOND_NONE);
+        assertThat(mService.getDevices().contains(mLeftDevice)).isTrue();
+
+        // LeAudio stack event: CONNECTION_STATE_DISCONNECTING - state machine is not removed
+        mService.bondStateChanged(mLeftDevice, BluetoothDevice.BOND_BONDED);
+        generateConnectionMessageFromNative(mLeftDevice, BluetoothProfile.STATE_DISCONNECTING,
+                BluetoothProfile.STATE_CONNECTED);
+        assertThat(BluetoothProfile.STATE_DISCONNECTING)
+                .isEqualTo(mService.getConnectionState(mLeftDevice));
+        assertThat(mService.getDevices().contains(mLeftDevice)).isTrue();
+        // Device unbond - state machine is not removed
+        mService.bondStateChanged(mLeftDevice, BluetoothDevice.BOND_NONE);
+        assertThat(mService.getDevices().contains(mLeftDevice)).isTrue();
+
+        // LeAudio stack event: CONNECTION_STATE_DISCONNECTED - state machine is not removed
+        mService.bondStateChanged(mLeftDevice, BluetoothDevice.BOND_BONDED);
+        generateConnectionMessageFromNative(mLeftDevice, BluetoothProfile.STATE_DISCONNECTED,
+                BluetoothProfile.STATE_DISCONNECTING);
+        assertThat(BluetoothProfile.STATE_DISCONNECTED)
+                .isEqualTo(mService.getConnectionState(mLeftDevice));
+        assertThat(mService.getDevices().contains(mLeftDevice)).isTrue();
+        // Device unbond - state machine is removed
+        mService.bondStateChanged(mLeftDevice, BluetoothDevice.BOND_NONE);
+        assertThat(mService.getDevices().contains(mLeftDevice)).isFalse();
+    }
+
+    /**
+     * Test that a CONNECTION_STATE_DISCONNECTED Le Audio stack event will remove the state
+     * machine only if the device is unbond.
+     */
+    @Test
+    public void testDeleteStateMachineDisconnectEvents() {
+        // Update the device priority so okToConnect() returns true
+        when(mDatabaseManager
+                .getProfileConnectionPolicy(mLeftDevice, BluetoothProfile.LE_AUDIO))
+                .thenReturn(BluetoothProfile.CONNECTION_POLICY_ALLOWED);
+        when(mDatabaseManager
+                .getProfileConnectionPolicy(mRightDevice, BluetoothProfile.LE_AUDIO))
+                .thenReturn(BluetoothProfile.CONNECTION_POLICY_FORBIDDEN);
+        when(mDatabaseManager
+                .getProfileConnectionPolicy(mSingleDevice, BluetoothProfile.LE_AUDIO))
+                .thenReturn(BluetoothProfile.CONNECTION_POLICY_FORBIDDEN);
+        doReturn(true).when(mNativeInterface).connectLeAudio(any(BluetoothDevice.class));
+        doReturn(true).when(mNativeInterface).disconnectLeAudio(any(BluetoothDevice.class));
+
+        // LeAudio stack event: CONNECTION_STATE_CONNECTING - state machine should be created
+        generateConnectionMessageFromNative(mLeftDevice, BluetoothProfile.STATE_CONNECTING,
+                BluetoothProfile.STATE_DISCONNECTED);
+        assertThat(BluetoothProfile.STATE_CONNECTING)
+                .isEqualTo(mService.getConnectionState(mLeftDevice));
+        assertThat(mService.getDevices().contains(mLeftDevice)).isTrue();
+
+        // LeAudio stack event: CONNECTION_STATE_DISCONNECTED - state machine is not removed
+        generateConnectionMessageFromNative(mLeftDevice, BluetoothProfile.STATE_DISCONNECTED,
+                BluetoothProfile.STATE_CONNECTING);
+        assertThat(BluetoothProfile.STATE_DISCONNECTED)
+                .isEqualTo(mService.getConnectionState(mLeftDevice));
+        assertThat(mService.getDevices().contains(mLeftDevice)).isTrue();
+
+        // LeAudio stack event: CONNECTION_STATE_CONNECTING - state machine remains
+        generateConnectionMessageFromNative(mLeftDevice, BluetoothProfile.STATE_CONNECTING,
+                BluetoothProfile.STATE_DISCONNECTED);
+        assertThat(BluetoothProfile.STATE_CONNECTING)
+                .isEqualTo(mService.getConnectionState(mLeftDevice));
+        assertThat(mService.getDevices().contains(mLeftDevice)).isTrue();
+
+        // Device bond state marked as unbond - state machine is not removed
+        doReturn(BluetoothDevice.BOND_NONE).when(mAdapterService)
+                .getBondState(any(BluetoothDevice.class));
+        assertThat(mService.getDevices().contains(mLeftDevice)).isTrue();
+
+        // LeAudio stack event: CONNECTION_STATE_DISCONNECTED - state machine is removed
+        generateConnectionMessageFromNative(mLeftDevice, BluetoothProfile.STATE_DISCONNECTED,
+                BluetoothProfile.STATE_CONNECTING);
+        assertThat(BluetoothProfile.STATE_DISCONNECTED)
+                .isEqualTo(mService.getConnectionState(mLeftDevice));
+        assertThat(mService.getDevices().contains(mLeftDevice)).isFalse();
+    }
+
+    private void connectDevice(BluetoothDevice device) {
+        LeAudioStackEvent connCompletedEvent;
+
+        List<BluetoothDevice> prevConnectedDevices = mService.getConnectedDevices();
+
+        when(mDatabaseManager.getProfileConnectionPolicy(device, BluetoothProfile.LE_AUDIO))
+                .thenReturn(BluetoothProfile.CONNECTION_POLICY_ALLOWED);
+        doReturn(true).when(mNativeInterface).connectLeAudio(device);
+        doReturn(true).when(mNativeInterface).disconnectLeAudio(device);
+
+        // Send a connect request
+        assertWithMessage("Connect failed").that(mService.connect(device)).isTrue();
+
+        // Verify the connection state broadcast, and that we are in Connecting state
+        verifyConnectionStateIntent(TIMEOUT_MS, device, BluetoothProfile.STATE_CONNECTING,
+                BluetoothProfile.STATE_DISCONNECTED);
+        assertThat(BluetoothProfile.STATE_CONNECTING)
+                .isEqualTo(mService.getConnectionState(device));
+
+        // Send a message to trigger connection completed
+        connCompletedEvent = new LeAudioStackEvent(
+                LeAudioStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED);
+        connCompletedEvent.device = device;
+        connCompletedEvent.valueInt1 = LeAudioStackEvent.CONNECTION_STATE_CONNECTED;
+        mService.messageFromNative(connCompletedEvent);
+
+        // Verify the connection state broadcast, and that we are in Connected state
+        verifyConnectionStateIntent(TIMEOUT_MS, device, BluetoothProfile.STATE_CONNECTED,
+                BluetoothProfile.STATE_CONNECTING);
+        assertThat(BluetoothProfile.STATE_CONNECTED)
+                .isEqualTo(mService.getConnectionState(device));
+
+        // Verify that the device is in the list of connected devices
+        assertThat(mService.getConnectedDevices().contains(device)).isTrue();
+        // Verify the list of previously connected devices
+        for (BluetoothDevice prevDevice : prevConnectedDevices) {
+            assertThat(mService.getConnectedDevices().contains(prevDevice)).isTrue();
+        }
+    }
+
+    private void generateConnectionMessageFromNative(BluetoothDevice device, int newConnectionState,
+            int oldConnectionState) {
+        LeAudioStackEvent stackEvent =
+                new LeAudioStackEvent(LeAudioStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED);
+        stackEvent.device = device;
+        stackEvent.valueInt1 = newConnectionState;
+        mService.messageFromNative(stackEvent);
+        // Verify the connection state broadcast
+        verifyConnectionStateIntent(TIMEOUT_MS, device, newConnectionState, oldConnectionState);
+    }
+
+    private void generateUnexpectedConnectionMessageFromNative(BluetoothDevice device,
+            int newConnectionState, int oldConnectionState) {
+        LeAudioStackEvent stackEvent =
+                new LeAudioStackEvent(LeAudioStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED);
+        stackEvent.device = device;
+        stackEvent.valueInt1 = newConnectionState;
+        mService.messageFromNative(stackEvent);
+        // Verify the connection state broadcast
+        verifyNoConnectionStateIntent(TIMEOUT_MS, device);
+    }
+
+    private void verifyNoConnectionStateIntent(int timeoutMs, BluetoothDevice device) {
+        Intent intent = TestUtils.waitForNoIntent(timeoutMs, mDeviceQueueMap.get(device));
+        assertThat(intent).isNull();
+    }
+}
diff --git a/tests/unit/src/com/android/bluetooth/le_audio/LeAudioStateMachineTest.java b/tests/unit/src/com/android/bluetooth/le_audio/LeAudioStateMachineTest.java
new file mode 100644
index 0000000..e75f2fd
--- /dev/null
+++ b/tests/unit/src/com/android/bluetooth/le_audio/LeAudioStateMachineTest.java
@@ -0,0 +1,258 @@
+/*
+ * Copyright 2020 HIMSA II K/S - www.himsa.com.
+ * Represented by EHIMA - www.ehima.com
+ *
+ * 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.le_audio;
+
+import static org.mockito.Mockito.after;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyString;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.verify;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothProfile;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.HandlerThread;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.MediumTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.bluetooth.TestUtils;
+import com.android.bluetooth.btservice.AdapterService;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@MediumTest
+@RunWith(AndroidJUnit4.class)
+public class LeAudioStateMachineTest {
+    private BluetoothAdapter mAdapter;
+    private Context mTargetContext;
+    private HandlerThread mHandlerThread;
+    private LeAudioStateMachine mLeAudioStateMachine;
+    private BluetoothDevice mTestDevice;
+    private static final int TIMEOUT_MS = 1000;
+
+    @Mock private AdapterService mAdapterService;
+    @Mock private LeAudioService mLeAudioService;
+    @Mock private LeAudioNativeInterface mLeAudioNativeInterface;
+
+    @Before
+    public void setUp() throws Exception {
+        mTargetContext = InstrumentationRegistry.getTargetContext();
+        // Set up mocks and test assets
+        MockitoAnnotations.initMocks(this);
+        TestUtils.setAdapterService(mAdapterService);
+
+        mAdapter = BluetoothAdapter.getDefaultAdapter();
+
+        // Get a device for testing
+        mTestDevice = mAdapter.getRemoteDevice("00:01:02:03:04:05");
+
+        // Set up thread and looper
+        mHandlerThread = new HandlerThread("LeAudioStateMachineTestHandlerThread");
+        mHandlerThread.start();
+        mLeAudioStateMachine = new LeAudioStateMachine(mTestDevice, mLeAudioService,
+                mLeAudioNativeInterface, mHandlerThread.getLooper());
+        // Override the timeout value to speed up the test
+        mLeAudioStateMachine.sConnectTimeoutMs = 1000;     // 1s
+        mLeAudioStateMachine.start();
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        mLeAudioStateMachine.doQuit();
+        mHandlerThread.quit();
+        TestUtils.clearAdapterService(mAdapterService);
+    }
+
+    /**
+     * Test that default state is disconnected
+     */
+    @Test
+    public void testDefaultDisconnectedState() {
+        assertThat(BluetoothProfile.STATE_DISCONNECTED).isEqualTo(
+                mLeAudioStateMachine.getConnectionState());
+    }
+
+    /**
+     * Allow/disallow connection to any device.
+     *
+     * @param allow if true, connection is allowed
+     */
+    private void allowConnection(boolean allow) {
+        doReturn(allow).when(mLeAudioService).okToConnect(any(BluetoothDevice.class));
+    }
+
+    /**
+     * Test that an incoming connection with low priority is rejected
+     */
+    @Test
+    public void testIncomingPriorityReject() {
+        allowConnection(false);
+
+        // Inject an event for when incoming connection is requested
+        LeAudioStackEvent connStCh =
+                new LeAudioStackEvent(LeAudioStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED);
+        connStCh.device = mTestDevice;
+        connStCh.valueInt1 = LeAudioStackEvent.CONNECTION_STATE_CONNECTED;
+        mLeAudioStateMachine.sendMessage(LeAudioStateMachine.STACK_EVENT, connStCh);
+
+        // Verify that no connection state broadcast is executed
+        verify(mLeAudioService, after(TIMEOUT_MS).never()).sendBroadcast(any(Intent.class),
+                anyString(), any(Bundle.class));
+        // Check that we are in Disconnected state
+        assertThat(mLeAudioStateMachine.getCurrentState())
+                .isInstanceOf(LeAudioStateMachine.Disconnected.class);
+    }
+
+    /**
+     * Test that an incoming connection with high priority is accepted
+     */
+    @Test
+    public void testIncomingPriorityAccept() {
+        allowConnection(true);
+
+        // Inject an event for when incoming connection is requested
+        LeAudioStackEvent connStCh =
+                    new LeAudioStackEvent(LeAudioStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED);
+        connStCh.device = mTestDevice;
+        connStCh.valueInt1 = LeAudioStackEvent.CONNECTION_STATE_CONNECTING;
+        mLeAudioStateMachine.sendMessage(LeAudioStateMachine.STACK_EVENT, connStCh);
+
+        // Verify that one connection state broadcast is executed
+        ArgumentCaptor<Intent> intentArgument1 = ArgumentCaptor.forClass(Intent.class);
+        verify(mLeAudioService, timeout(TIMEOUT_MS).times(1)).sendBroadcast(
+                intentArgument1.capture(), anyString(), any(Bundle.class));
+        assertThat(BluetoothProfile.STATE_CONNECTING).isEqualTo(
+                intentArgument1.getValue().getIntExtra(BluetoothProfile.EXTRA_STATE, -1));
+
+        // Check that we are in Connecting state
+        assertThat(mLeAudioStateMachine.getCurrentState())
+                .isInstanceOf(LeAudioStateMachine.Connecting.class);
+
+        // Send a message to trigger connection completed
+        LeAudioStackEvent connCompletedEvent =
+                new LeAudioStackEvent(LeAudioStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED);
+        connCompletedEvent.device = mTestDevice;
+        connCompletedEvent.valueInt1 = LeAudioStackEvent.CONNECTION_STATE_CONNECTED;
+        mLeAudioStateMachine.sendMessage(LeAudioStateMachine.STACK_EVENT, connCompletedEvent);
+
+        // Verify that the expected number of broadcasts are executed:
+        // - two calls to broadcastConnectionState(): Disconnected -> Conecting -> Connected
+        ArgumentCaptor<Intent> intentArgument2 = ArgumentCaptor.forClass(Intent.class);
+        verify(mLeAudioService, timeout(TIMEOUT_MS).times(2)).sendBroadcast(
+                intentArgument2.capture(), anyString(), any(Bundle.class));
+        // Check that we are in Connected state
+        assertThat(mLeAudioStateMachine.getCurrentState())
+                .isInstanceOf(LeAudioStateMachine.Connected.class);
+    }
+
+    /**
+     * Test that an outgoing connection times out
+     */
+    @Test
+    public void testOutgoingTimeout() {
+        allowConnection(true);
+        doReturn(true).when(mLeAudioNativeInterface).connectLeAudio(any(
+                BluetoothDevice.class));
+        doReturn(true).when(mLeAudioNativeInterface).disconnectLeAudio(any(
+                BluetoothDevice.class));
+
+        // Send a connect request
+        mLeAudioStateMachine.sendMessage(LeAudioStateMachine.CONNECT, mTestDevice);
+
+        // Verify that one connection state broadcast is executed
+        ArgumentCaptor<Intent> intentArgument1 = ArgumentCaptor.forClass(Intent.class);
+        verify(mLeAudioService, timeout(TIMEOUT_MS).times(1)).sendBroadcast(
+                intentArgument1.capture(),
+                anyString(), any(Bundle.class));
+        assertThat(BluetoothProfile.STATE_CONNECTING).isEqualTo(
+                intentArgument1.getValue().getIntExtra(BluetoothProfile.EXTRA_STATE, -1));
+
+        // Check that we are in Connecting state
+        assertThat(mLeAudioStateMachine.getCurrentState())
+                .isInstanceOf(LeAudioStateMachine.Connecting.class);
+
+        // Verify that one connection state broadcast is executed
+        ArgumentCaptor<Intent> intentArgument2 = ArgumentCaptor.forClass(Intent.class);
+        verify(mLeAudioService, timeout(LeAudioStateMachine.sConnectTimeoutMs * 2).times(
+                2)).sendBroadcast(intentArgument2.capture(), anyString(),
+                any(Bundle.class));
+        assertThat(BluetoothProfile.STATE_DISCONNECTED).isEqualTo(
+                intentArgument2.getValue().getIntExtra(BluetoothProfile.EXTRA_STATE, -1));
+
+        // Check that we are in Disconnected state
+        assertThat(mLeAudioStateMachine.getCurrentState())
+                .isInstanceOf(LeAudioStateMachine.Disconnected.class);
+    }
+
+    /**
+     * Test that an incoming connection times out
+     */
+    @Test
+    public void testIncomingTimeout() {
+        allowConnection(true);
+        doReturn(true).when(mLeAudioNativeInterface).connectLeAudio(any(
+                BluetoothDevice.class));
+        doReturn(true).when(mLeAudioNativeInterface).disconnectLeAudio(any(
+                BluetoothDevice.class));
+
+        // Inject an event for when incoming connection is requested
+        LeAudioStackEvent connStCh =
+                new LeAudioStackEvent(LeAudioStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED);
+        connStCh.device = mTestDevice;
+        connStCh.valueInt1 = LeAudioStackEvent.CONNECTION_STATE_CONNECTING;
+        mLeAudioStateMachine.sendMessage(LeAudioStateMachine.STACK_EVENT, connStCh);
+
+        // Verify that one connection state broadcast is executed
+        ArgumentCaptor<Intent> intentArgument1 = ArgumentCaptor.forClass(Intent.class);
+        verify(mLeAudioService, timeout(TIMEOUT_MS).times(1)).sendBroadcast(
+                intentArgument1.capture(),
+                anyString(), any(Bundle.class));
+        assertThat(BluetoothProfile.STATE_CONNECTING).isEqualTo(
+                intentArgument1.getValue().getIntExtra(BluetoothProfile.EXTRA_STATE, -1));
+
+        // Check that we are in Connecting state
+        assertThat(mLeAudioStateMachine.getCurrentState())
+                .isInstanceOf(LeAudioStateMachine.Connecting.class);
+
+        // Verify that one connection state broadcast is executed
+        ArgumentCaptor<Intent> intentArgument2 = ArgumentCaptor.forClass(Intent.class);
+        verify(mLeAudioService, timeout(LeAudioStateMachine.sConnectTimeoutMs * 2).times(
+                2)).sendBroadcast(intentArgument2.capture(), anyString(),
+                any(Bundle.class));
+        assertThat(intentArgument2.getValue().getIntExtra(BluetoothProfile.EXTRA_STATE, -1))
+                .isEqualTo(BluetoothProfile.STATE_DISCONNECTED);
+
+        // Check that we are in Disconnected state
+        assertThat(mLeAudioStateMachine.getCurrentState())
+                .isInstanceOf(LeAudioStateMachine.Disconnected.class);
+    }
+}
diff --git a/tests/unit/src/com/android/bluetooth/map/BluetoothMapContentObserverTest.java b/tests/unit/src/com/android/bluetooth/map/BluetoothMapContentObserverTest.java
index cca340e..222dc08 100644
--- a/tests/unit/src/com/android/bluetooth/map/BluetoothMapContentObserverTest.java
+++ b/tests/unit/src/com/android/bluetooth/map/BluetoothMapContentObserverTest.java
@@ -18,6 +18,7 @@
 
 import static org.mockito.Mockito.*;
 
+import android.content.ContentValues;
 import android.content.Context;
 import android.database.Cursor;
 import android.database.sqlite.SQLiteException;
@@ -25,6 +26,8 @@
 import android.os.Looper;
 import android.os.RemoteException;
 import android.os.UserManager;
+import android.provider.Telephony.Mms;
+import android.provider.Telephony.Sms;
 import android.telephony.TelephonyManager;
 import android.test.mock.MockContentProvider;
 import android.test.mock.MockContentResolver;
@@ -40,20 +43,42 @@
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.Mockito;
+
+import java.io.IOException;
+import java.util.HashSet;
 
 @MediumTest
 @RunWith(AndroidJUnit4.class)
 public class BluetoothMapContentObserverTest {
+    static final String TEST_NUMBER_ONE = "5551212";
+    static final String TEST_NUMBER_TWO = "5551234";
     private Context mTargetContext;
 
-    class ExceptionTestProvider extends MockContentProvider {
+    static class ExceptionTestProvider extends MockContentProvider {
+        HashSet<String> mContents = new HashSet<String>();
         public ExceptionTestProvider(Context context) {
             super(context);
         }
 
         @Override
         public Cursor query(Uri uri, String[] b, String s, String[] c, String d) {
-            throw new SQLiteException();
+            // Throw exception for SMS queries for easy initialization
+            if (Sms.CONTENT_URI.equals(uri)) throw new SQLiteException();
+
+            // Return a cursor otherwise for Thread IDs
+            Cursor cursor = Mockito.mock(Cursor.class);
+            when(cursor.moveToFirst()).thenReturn(true);
+            when(cursor.getLong(anyInt())).thenReturn(0L);
+            return cursor;
+        }
+
+        @Override
+        public Uri insert(Uri uri, ContentValues values) {
+            // Store addresses for later verification
+            Object address = values.get(Mms.Addr.ADDRESS);
+            if (address != null) mContents.add((String) address);
+            return Uri.withAppendedPath(Mms.Outbox.CONTENT_URI, "0");
         }
     }
 
@@ -94,4 +119,66 @@
             Assert.fail("Threw SQLiteException instead of Assert.failing cleanly");
         }
     }
+
+    @Test
+    public void testPushGroupMMS() {
+        if (Looper.myLooper() == null) {
+            Looper.prepare();
+        }
+        Context mockContext = mock(Context.class);
+        MockContentResolver mockResolver = new MockContentResolver();
+        ExceptionTestProvider mockProvider = new ExceptionTestProvider(mockContext);
+
+        mockResolver.addProvider("sms", mockProvider);
+        mockResolver.addProvider("mms", mockProvider);
+        mockResolver.addProvider("mms-sms", mockProvider);
+        TelephonyManager mockTelephony = mock(TelephonyManager.class);
+        UserManager mockUserService = mock(UserManager.class);
+        BluetoothMapMasInstance mockMas = mock(BluetoothMapMasInstance.class);
+
+        // Functions that get called when BluetoothMapContentObserver is created
+        when(mockUserService.isUserUnlocked()).thenReturn(true);
+        when(mockContext.getContentResolver()).thenReturn(mockResolver);
+        when(mockContext.getSystemService(Context.TELEPHONY_SERVICE)).thenReturn(mockTelephony);
+        when(mockContext.getSystemService(Context.USER_SERVICE)).thenReturn(mockUserService);
+
+        BluetoothMapbMessageMime message = new BluetoothMapbMessageMime();
+        message.setType(BluetoothMapUtils.TYPE.MMS);
+        message.setFolder("telecom/msg/outbox");
+        message.addSender("Zero", "0");
+        message.addRecipient("One", new String[] {TEST_NUMBER_ONE}, null);
+        message.addRecipient("Two", new String[] {TEST_NUMBER_TWO}, null);
+        BluetoothMapbMessageMime.MimePart body =  message.addMimePart();
+        try {
+            body.mContentType = "text/plain";
+            body.mData = "HelloWorld".getBytes("utf-8");
+        } catch (Exception e) {
+            Assert.fail("Failed to setup test message");
+        }
+
+        BluetoothMapAppParams appParams = new BluetoothMapAppParams();
+        BluetoothMapFolderElement folderElement = new BluetoothMapFolderElement("outbox", null);
+
+        try {
+            // The constructor of BluetoothMapContentObserver calls initMsgList
+            BluetoothMapContentObserver observer =
+                    new BluetoothMapContentObserver(mockContext, null, mockMas, null, true);
+            observer.pushMessage(message, folderElement, appParams, null);
+        } catch (RemoteException e) {
+            Assert.fail("Failed to created BluetoothMapContentObserver object");
+        } catch (SQLiteException e) {
+            Assert.fail("Threw SQLiteException instead of Assert.failing cleanly");
+        } catch (IOException e) {
+            Assert.fail("Threw IOException");
+        } catch (NullPointerException e) {
+            //expected that the test case will end in a NPE as part of the sendMultimediaMessage
+            //pendingSendIntent
+        }
+
+        // Validate that 3 addresses were inserted into the database with 2 being the recipients
+        Assert.assertEquals(3, mockProvider.mContents.size());
+        Assert.assertTrue(mockProvider.mContents.contains(TEST_NUMBER_ONE));
+        Assert.assertTrue(mockProvider.mContents.contains(TEST_NUMBER_TWO));
+    }
+
 }
diff --git a/tests/unit/src/com/android/bluetooth/map/BluetoothMapServiceTest.java b/tests/unit/src/com/android/bluetooth/map/BluetoothMapServiceTest.java
index 0a000a2..062aa67 100644
--- a/tests/unit/src/com/android/bluetooth/map/BluetoothMapServiceTest.java
+++ b/tests/unit/src/com/android/bluetooth/map/BluetoothMapServiceTest.java
@@ -15,6 +15,9 @@
  */
 package com.android.bluetooth.map;
 
+import static org.mockito.Mockito.anyString;
+import static org.mockito.Mockito.doReturn;
+
 import android.bluetooth.BluetoothAdapter;
 import android.content.Context;
 
@@ -26,6 +29,7 @@
 import com.android.bluetooth.R;
 import com.android.bluetooth.TestUtils;
 import com.android.bluetooth.btservice.AdapterService;
+import com.android.bluetooth.btservice.storage.DatabaseManager;
 
 import org.junit.After;
 import org.junit.Assert;
@@ -47,6 +51,7 @@
     @Rule public final ServiceTestRule mServiceRule = new ServiceTestRule();
 
     @Mock private AdapterService mAdapterService;
+    @Mock private DatabaseManager mDatabaseManager;
 
     @Before
     public void setUp() throws Exception {
@@ -55,6 +60,8 @@
                 mTargetContext.getResources().getBoolean(R.bool.profile_supported_map));
         MockitoAnnotations.initMocks(this);
         TestUtils.setAdapterService(mAdapterService);
+        doReturn(mDatabaseManager).when(mAdapterService).getDatabase();
+        doReturn(true, false).when(mAdapterService).isStartedProfile(anyString());
         TestUtils.startService(mServiceRule, BluetoothMapService.class);
         mService = BluetoothMapService.getBluetoothMapService();
         Assert.assertNotNull(mService);
diff --git a/tests/unit/src/com/android/bluetooth/mapclient/MapClientContentTest.java b/tests/unit/src/com/android/bluetooth/mapclient/MapClientContentTest.java
new file mode 100644
index 0000000..2ddd6d1
--- /dev/null
+++ b/tests/unit/src/com/android/bluetooth/mapclient/MapClientContentTest.java
@@ -0,0 +1,436 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.bluetooth.mapclient;
+
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothMapClient;
+import android.content.ContentValues;
+import android.content.Context;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Handler;
+import android.provider.Telephony.Mms;
+import android.provider.Telephony.Sms;
+import android.telephony.SubscriptionInfo;
+import android.telephony.SubscriptionManager;
+import android.test.mock.MockContentProvider;
+import android.test.mock.MockContentResolver;
+import android.util.Log;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.MediumTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.bluetooth.btservice.AdapterService;
+import com.android.bluetooth.btservice.storage.DatabaseManager;
+import com.android.vcard.VCardConstants;
+import com.android.vcard.VCardEntry;
+import com.android.vcard.VCardProperty;
+
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+
+@MediumTest
+@RunWith(AndroidJUnit4.class)
+public class MapClientContentTest {
+
+    private static final String TAG = "MapClientContentTest";
+    private static final int READ = 1;
+
+    private BluetoothAdapter mAdapter;
+    private BluetoothDevice mTestDevice;
+    private Context mTargetContext;
+
+    private Handler mHandler;
+    private Bmessage mTestMessage1;
+    private Bmessage mTestMessage2;
+    private Long mTestMessage1Timestamp = 1234L;
+    private String mTestMessage1Handle = "0001";
+    private String mTestMessage2Handle = "0002";
+
+
+    private VCardEntry mOriginator;
+
+    private ArgumentCaptor<Uri> mUriArgument = ArgumentCaptor.forClass(Uri.class);
+
+    private MapClientContent mMapClientContent;
+
+    @Mock
+    private AdapterService mAdapterService;
+    @Mock
+    private DatabaseManager mDatabaseManager;
+    @Mock
+    private MapClientService mMockMapClientService;
+    @Mock
+    private Context mMockContext;
+    @Mock
+    private MapClientContent.Callbacks mCallbacks;
+
+    private MockContentResolver mMockContentResolver;
+    private FakeContentProvider mMockSmsContentProvider;
+    private FakeContentProvider mMockMmsContentProvider;
+    private FakeContentProvider mMockThreadContentProvider;
+
+    @Mock
+    private SubscriptionManager mMockSubscriptionManager;
+    @Mock
+    private SubscriptionInfo mMockSubscription;
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+        mTargetContext = InstrumentationRegistry.getTargetContext();
+
+        mMockSmsContentProvider = Mockito.spy(new FakeContentProvider(mTargetContext));
+
+        mMockMmsContentProvider = Mockito.spy(new FakeContentProvider(mTargetContext));
+        mMockThreadContentProvider = Mockito.spy(new FakeContentProvider(mTargetContext));
+
+
+        mAdapter = BluetoothAdapter.getDefaultAdapter();
+        mTestDevice = mAdapter.getRemoteDevice("00:01:02:03:04:05");
+        mMockContentResolver = Mockito.spy(new MockContentResolver());
+        mMockContentResolver.addProvider("sms", mMockSmsContentProvider);
+        mMockContentResolver.addProvider("mms", mMockMmsContentProvider);
+        mMockContentResolver.addProvider("mms-sms", mMockThreadContentProvider);
+
+        when(mMockContext.getContentResolver()).thenReturn(mMockContentResolver);
+        when(mMockContext.getSystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE))
+                .thenReturn(mMockSubscriptionManager);
+
+        when(mMockSubscriptionManager.getActiveSubscriptionInfoList())
+                .thenReturn(Arrays.asList(mMockSubscription));
+        createTestMessages();
+
+    }
+
+    @After
+    public void tearDown() throws Exception {
+    }
+
+    /**
+     * Test that everything initializes correctly with an empty content provider
+     */
+    @Test
+    public void testCreateMapClientContent() {
+        mMapClientContent = new MapClientContent(mMockContext, mCallbacks, mTestDevice);
+        verify(mMockSubscriptionManager).addSubscriptionInfoRecord(any(), any(), anyInt(),
+                eq(SubscriptionManager.SUBSCRIPTION_TYPE_REMOTE_SIM));
+        Assert.assertEquals(0, mMockSmsContentProvider.mContentValues.size());
+    }
+
+    /**
+     * Test that a dirty database gets cleaned at startup.
+     */
+    @Test
+    public void testCleanDirtyDatabase() {
+        mMapClientContent = new MapClientContent(mMockContext, mCallbacks, mTestDevice);
+        mMapClientContent.storeMessage(mTestMessage1, mTestMessage1Handle, mTestMessage1Timestamp);
+        verify(mMockSubscriptionManager).addSubscriptionInfoRecord(any(), any(), anyInt(),
+                eq(SubscriptionManager.SUBSCRIPTION_TYPE_REMOTE_SIM));
+        Assert.assertEquals(1, mMockSmsContentProvider.mContentValues.size());
+        mMapClientContent = new MapClientContent(mMockContext, mCallbacks, mTestDevice);
+        Assert.assertEquals(0, mMockSmsContentProvider.mContentValues.size());
+    }
+
+    /**
+     * Test inserting 2 SMS messages and then clearing out the database.
+     */
+    @Test
+    public void testStoreTwoSMS() {
+        mMapClientContent = new MapClientContent(mMockContext, mCallbacks, mTestDevice);
+        mMapClientContent.storeMessage(mTestMessage1, mTestMessage1Handle, mTestMessage1Timestamp);
+        verify(mMockSubscriptionManager).addSubscriptionInfoRecord(any(), any(), anyInt(),
+                eq(SubscriptionManager.SUBSCRIPTION_TYPE_REMOTE_SIM));
+        Assert.assertEquals(1, mMockSmsContentProvider.mContentValues.size());
+
+        mMapClientContent.storeMessage(mTestMessage1, mTestMessage1Handle, mTestMessage1Timestamp);
+        Assert.assertEquals(2, mMockSmsContentProvider.mContentValues.size());
+        Assert.assertEquals(0, mMockMmsContentProvider.mContentValues.size());
+
+        mMapClientContent.cleanUp();
+        Assert.assertEquals(0, mMockSmsContentProvider.mContentValues.size());
+        Assert.assertEquals(0, mMockThreadContentProvider.mContentValues.size());
+    }
+
+    /**
+     * Test inserting 2 MMS messages and then clearing out the database.
+     */
+    @Test
+    public void testStoreTwoMMS() {
+        mMapClientContent = new MapClientContent(mMockContext, mCallbacks, mTestDevice);
+        mMapClientContent.storeMessage(mTestMessage2, mTestMessage1Handle, mTestMessage1Timestamp);
+        verify(mMockSubscriptionManager).addSubscriptionInfoRecord(any(), any(), anyInt(),
+                eq(SubscriptionManager.SUBSCRIPTION_TYPE_REMOTE_SIM));
+        Assert.assertEquals(1, mMockMmsContentProvider.mContentValues.size());
+
+        mMapClientContent.storeMessage(mTestMessage2, mTestMessage1Handle, mTestMessage1Timestamp);
+        Assert.assertEquals(2, mMockMmsContentProvider.mContentValues.size());
+
+        mMapClientContent.cleanUp();
+        Assert.assertEquals(0, mMockMmsContentProvider.mContentValues.size());
+    }
+
+    /**
+     * Test that SMS and MMS messages end up in their respective databases.
+     */
+    @Test
+    public void testStoreOneSMSOneMMS() {
+        mMapClientContent = new MapClientContent(mMockContext, mCallbacks, mTestDevice);
+        mMapClientContent.storeMessage(mTestMessage2, mTestMessage1Handle, mTestMessage1Timestamp);
+        verify(mMockSubscriptionManager).addSubscriptionInfoRecord(any(), any(), anyInt(),
+                eq(SubscriptionManager.SUBSCRIPTION_TYPE_REMOTE_SIM));
+        Assert.assertEquals(1, mMockMmsContentProvider.mContentValues.size());
+
+        mMapClientContent.storeMessage(mTestMessage2, mTestMessage2Handle, mTestMessage1Timestamp);
+        Assert.assertEquals(2, mMockMmsContentProvider.mContentValues.size());
+
+        mMapClientContent.cleanUp();
+        Assert.assertEquals(0, mMockMmsContentProvider.mContentValues.size());
+    }
+
+    /**
+     * Test read status changed
+     */
+    @Test
+    public void testReadStatusChanged() {
+        mMapClientContent = new MapClientContent(mMockContext, mCallbacks, mTestDevice);
+        mMapClientContent.storeMessage(mTestMessage2, mTestMessage1Handle, mTestMessage1Timestamp);
+        verify(mMockSubscriptionManager).addSubscriptionInfoRecord(any(), any(), anyInt(),
+                eq(SubscriptionManager.SUBSCRIPTION_TYPE_REMOTE_SIM));
+        Assert.assertEquals(1, mMockMmsContentProvider.mContentValues.size());
+
+        mMapClientContent.storeMessage(mTestMessage2, mTestMessage1Handle, mTestMessage1Timestamp);
+        Assert.assertEquals(2, mMockMmsContentProvider.mContentValues.size());
+
+        mMapClientContent.markRead(mTestMessage1Handle);
+
+        mMapClientContent.cleanUp();
+        Assert.assertEquals(0, mMockMmsContentProvider.mContentValues.size());
+    }
+
+    /**
+     * Test read status changed in local provider
+     *
+     * Insert a message, and notify the observer about a change
+     * The cursor is configured to return messages marked as read
+     * Verify that the local change is observed and propagated to the remote
+     */
+    @Test
+    public void testLocalReadStatusChanged() {
+        mMapClientContent = new MapClientContent(mMockContext, mCallbacks, mTestDevice);
+        mMapClientContent.storeMessage(mTestMessage2, mTestMessage1Handle, mTestMessage1Timestamp);
+        Assert.assertEquals(1, mMockMmsContentProvider.mContentValues.size());
+        mMapClientContent.mContentObserver.onChange(false);
+        verify(mCallbacks).onMessageStatusChanged(eq(mTestMessage1Handle),
+                eq(BluetoothMapClient.READ));
+    }
+
+    /**
+     * Test remote message deleted
+     *
+     * Add a message to the database Simulate the message getting
+     * deleted on the phone Verify that the message is deleted locally
+     */
+    @Test
+    public void testMessageDeleted() {
+        mMapClientContent = new MapClientContent(mMockContext, mCallbacks, mTestDevice);
+        mMapClientContent.storeMessage(mTestMessage1, mTestMessage1Handle, mTestMessage1Timestamp);
+        verify(mMockSubscriptionManager).addSubscriptionInfoRecord(any(), any(), anyInt(),
+                eq(SubscriptionManager.SUBSCRIPTION_TYPE_REMOTE_SIM));
+        Assert.assertEquals(1, mMockSmsContentProvider.mContentValues.size());
+        // attempt to delete an invalid handle, nothing should be removed.
+        mMapClientContent.deleteMessage(mTestMessage2Handle);
+        Assert.assertEquals(1, mMockSmsContentProvider.mContentValues.size());
+
+        // delete a valid handle
+        mMapClientContent.deleteMessage(mTestMessage1Handle);
+        Assert.assertEquals(0, mMockSmsContentProvider.mContentValues.size());
+    }
+
+    /**
+     * Test read status changed in local provider
+     *
+     * Insert a message, manually remove it and notify the observer about a change
+     * Verify that the local change is observed and propagated to the remote
+     */
+    @Test
+    public void testLocalMessageDeleted() {
+        mMapClientContent = new MapClientContent(mMockContext, mCallbacks, mTestDevice);
+        mMapClientContent.storeMessage(mTestMessage1, mTestMessage1Handle, mTestMessage1Timestamp);
+        verify(mMockSubscriptionManager).addSubscriptionInfoRecord(any(), any(), anyInt(),
+                eq(SubscriptionManager.SUBSCRIPTION_TYPE_REMOTE_SIM));
+        Assert.assertEquals(1, mMockSmsContentProvider.mContentValues.size());
+        mMockSmsContentProvider.mContentValues.clear();
+        mMapClientContent.mContentObserver.onChange(false);
+        verify(mCallbacks).onMessageStatusChanged(eq(mTestMessage1Handle),
+                eq(BluetoothMapClient.DELETED));
+    }
+
+    /**
+     * Test parse own phone number Attempt to parse your phone number from a received SMS message
+     * and fail Receive an MMS message and successfully parse your phone number
+     */
+    @Test
+    public void testParseNumber() {
+        mMapClientContent = new MapClientContent(mMockContext, mCallbacks, mTestDevice);
+        Assert.assertNull(mMapClientContent.mPhoneNumber);
+        mMapClientContent.storeMessage(mTestMessage1, mTestMessage1Handle, mTestMessage1Timestamp);
+        Assert.assertNull(mMapClientContent.mPhoneNumber);
+        mMapClientContent.storeMessage(mTestMessage2, mTestMessage1Handle, mTestMessage1Timestamp);
+        Assert.assertEquals("5551212", mMapClientContent.mPhoneNumber);
+    }
+
+    /**
+     * Test to validate that some poorly formatted messages don't crash.
+     */
+    @Test
+    public void testStoreBadMessage() {
+        mMapClientContent = new MapClientContent(mMockContext, mCallbacks, mTestDevice);
+        mTestMessage1 = new Bmessage();
+        mTestMessage1.setBodyContent("HelloWorld");
+        mTestMessage1.setType(Bmessage.Type.SMS_GSM);
+        mTestMessage1.setFolder("telecom/msg/sent");
+        mMapClientContent.storeMessage(mTestMessage1, mTestMessage1Handle, mTestMessage1Timestamp);
+
+        mTestMessage2 = new Bmessage();
+        mTestMessage2.setBodyContent("HelloWorld");
+        mTestMessage2.setType(Bmessage.Type.MMS);
+        mTestMessage2.setFolder("telecom/msg/inbox");
+        mMapClientContent.storeMessage(mTestMessage2, mTestMessage2Handle, mTestMessage1Timestamp);
+    }
+
+    /**
+     * Test to validate that an exception in the Subscription manager won't crash Bluetooth during
+     * disconnect.
+     */
+    @Test
+    public void testCleanUpRemoteException() {
+        mMapClientContent = new MapClientContent(mMockContext, mCallbacks, mTestDevice);
+        doThrow(java.lang.NullPointerException.class).when(mMockSubscriptionManager)
+                .removeSubscriptionInfoRecord(any(), anyInt());
+        mMapClientContent.cleanUp();
+    }
+
+    /**
+     * Test to validate old subscriptions are removed at startup.
+     */
+    @Test
+    public void testCleanUpAtStartup() {
+        MapClientContent.clearAllContent(mMockContext);
+        verify(mMockSubscriptionManager, never()).removeSubscriptionInfoRecord(any(), anyInt());
+
+        when(mMockSubscription.getSubscriptionType())
+                .thenReturn(SubscriptionManager.SUBSCRIPTION_TYPE_REMOTE_SIM);
+        MapClientContent.clearAllContent(mMockContext);
+        verify(mMockSubscriptionManager).removeSubscriptionInfoRecord(any(),
+                eq(SubscriptionManager.SUBSCRIPTION_TYPE_REMOTE_SIM));
+    }
+
+    void createTestMessages() {
+        mOriginator = new VCardEntry();
+        VCardProperty property = new VCardProperty();
+        property.setName(VCardConstants.PROPERTY_TEL);
+        property.addValues("555-1212");
+        mOriginator.addProperty(property);
+        mTestMessage1 = new Bmessage();
+        mTestMessage1.setBodyContent("HelloWorld");
+        mTestMessage1.setType(Bmessage.Type.SMS_GSM);
+        mTestMessage1.setFolder("telecom/msg/inbox");
+        mTestMessage1.addOriginator(mOriginator);
+
+        mTestMessage2 = new Bmessage();
+        mTestMessage2.setBodyContent("HelloWorld");
+        mTestMessage2.setType(Bmessage.Type.MMS);
+        mTestMessage2.setFolder("telecom/msg/inbox");
+        mTestMessage2.addOriginator(mOriginator);
+        mTestMessage2.addRecipient(mOriginator);
+    }
+
+    public class FakeContentProvider extends MockContentProvider {
+
+        Map<Uri, ContentValues> mContentValues = new HashMap<>();
+        FakeContentProvider(Context context) {
+            super(context);
+        }
+
+        @Override
+        public int delete(Uri uri, String selection, String[] selectionArgs) {
+            Log.i(TAG, "Delete " + uri);
+            Log.i(TAG, "Contents" + mContentValues.toString());
+            mContentValues.remove(uri);
+            if (uri.equals(Sms.CONTENT_URI) || uri.equals(Mms.CONTENT_URI)) {
+                mContentValues.clear();
+            }
+            return 1;
+        }
+
+        @Override
+        public Uri insert(Uri uri, ContentValues values) {
+            Log.i(TAG, "URI = " + uri);
+            if (uri.equals(Mms.Inbox.CONTENT_URI)) uri = Mms.CONTENT_URI;
+            Uri returnUri = Uri.withAppendedPath(uri, String.valueOf(mContentValues.size() + 1));
+            //only store top level message parts
+            if (uri.equals(Sms.Inbox.CONTENT_URI) || uri.equals(Mms.CONTENT_URI)) {
+                Log.i(TAG, "adding content" + values);
+                mContentValues.put(returnUri, values);
+                Log.i(TAG, "ContentSize = " + mContentValues.size());
+            }
+            return returnUri;
+        }
+
+        @Override
+        public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
+                String sortOrder) {
+            Cursor cursor = Mockito.mock(Cursor.class);
+
+            when(cursor.moveToFirst()).thenReturn(true);
+            when(cursor.moveToNext()).thenReturn(true).thenReturn(false);
+
+            when(cursor.getLong(anyInt())).thenReturn((long) mContentValues.size());
+            when(cursor.getString(anyInt())).thenReturn(String.valueOf(mContentValues.size()));
+            when(cursor.getInt(anyInt())).thenReturn(READ);
+            return cursor;
+        }
+
+        @Override
+        public int update(Uri uri, ContentValues values, Bundle extras) {
+            return 0;
+        }
+    }
+}
diff --git a/tests/unit/src/com/android/bluetooth/mapclient/MapClientStateMachineTest.java b/tests/unit/src/com/android/bluetooth/mapclient/MapClientStateMachineTest.java
index ccfd9f8..7a124a2 100644
--- a/tests/unit/src/com/android/bluetooth/mapclient/MapClientStateMachineTest.java
+++ b/tests/unit/src/com/android/bluetooth/mapclient/MapClientStateMachineTest.java
@@ -16,64 +16,111 @@
 
 package com.android.bluetooth.mapclient;
 
+import static android.Manifest.permission.BLUETOOTH_CONNECT;
 import static org.mockito.Mockito.*;
 
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothMapClient;
 import android.bluetooth.BluetoothProfile;
 import android.bluetooth.SdpMasRecord;
+import android.content.ContentValues;
 import android.content.Context;
 import android.content.Intent;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Bundle;
 import android.os.Handler;
 import android.os.Looper;
 import android.os.Message;
+import android.provider.Telephony.Sms;
+import android.telephony.SubscriptionManager;
+import android.test.mock.MockContentProvider;
+import android.test.mock.MockContentResolver;
 import android.util.Log;
 
 import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.MediumTest;
+import androidx.test.rule.ServiceTestRule;
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.bluetooth.R;
-import com.android.bluetooth.btservice.ProfileService;
+import com.android.bluetooth.TestUtils;
+import com.android.bluetooth.btservice.AdapterService;
+import com.android.bluetooth.btservice.storage.DatabaseManager;
+import com.android.vcard.VCardConstants;
+import com.android.vcard.VCardEntry;
+import com.android.vcard.VCardProperty;
 
 import org.junit.After;
 import org.junit.Assert;
 import org.junit.Assume;
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
 import org.mockito.Mock;
+import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
 
+import java.util.HashMap;
+import java.util.Map;
+
 @MediumTest
 @RunWith(AndroidJUnit4.class)
 public class MapClientStateMachineTest {
+
     private static final String TAG = "MapStateMachineTest";
+    private static final String FOLDER_SENT = "sent";
 
     private static final int ASYNC_CALL_TIMEOUT_MILLIS = 100;
-
+    private static final int DISCONNECT_TIMEOUT = 3000;
+    @Rule
+    public final ServiceTestRule mServiceRule = new ServiceTestRule();
     private BluetoothAdapter mAdapter;
     private MceStateMachine mMceStateMachine = null;
     private BluetoothDevice mTestDevice;
     private Context mTargetContext;
-
     private Handler mHandler;
-
     private ArgumentCaptor<Intent> mIntentArgument = ArgumentCaptor.forClass(Intent.class);
-
+    @Mock
+    private AdapterService mAdapterService;
+    @Mock
+    private DatabaseManager mDatabaseManager;
     @Mock
     private MapClientService mMockMapClientService;
-
+    private MockContentResolver mMockContentResolver;
+    private MockSmsContentProvider mMockContentProvider;
     @Mock
     private MasClient mMockMasClient;
 
+    @Mock
+    private RequestPushMessage mMockRequestPushMessage;
+
+    @Mock
+    private SubscriptionManager mMockSubscriptionManager;
+
     @Before
-    public void setUp() {
+    public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
         mTargetContext = InstrumentationRegistry.getTargetContext();
+        mMockContentProvider = new MockSmsContentProvider();
+        mMockContentResolver = new MockContentResolver();
+
         Assume.assumeTrue("Ignore test when MapClientService is not enabled",
                 mTargetContext.getResources().getBoolean(R.bool.profile_supported_mapmce));
+        TestUtils.setAdapterService(mAdapterService);
+        when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager);
+        doReturn(true, false).when(mAdapterService).isStartedProfile(anyString());
+        TestUtils.startService(mServiceRule, MapClientService.class);
+        mMockContentResolver.addProvider("sms", mMockContentProvider);
+        mMockContentResolver.addProvider("mms", mMockContentProvider);
+        mMockContentResolver.addProvider("mms-sms", mMockContentProvider);
+
+        when(mMockMapClientService.getContentResolver()).thenReturn(mMockContentResolver);
+        when(mMockMapClientService.getSystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE))
+                .thenReturn(mMockSubscriptionManager);
 
         doReturn(mTargetContext.getResources()).when(mMockMapClientService).getResources();
 
@@ -92,13 +139,16 @@
     }
 
     @After
-    public void tearDown() {
+    public void tearDown() throws Exception {
         if (!mTargetContext.getResources().getBoolean(R.bool.profile_supported_mapmce)) {
             return;
         }
+
         if (mMceStateMachine != null) {
             mMceStateMachine.doQuit();
         }
+        TestUtils.stopService(mServiceRule, MapClientService.class);
+        TestUtils.clearAdapterService(mAdapterService);
     }
 
     /**
@@ -111,8 +161,8 @@
     }
 
     /**
-     * Test transition from
-     *      STATE_CONNECTING --> (receive MSG_MAS_DISCONNECTED) --> STATE_DISCONNECTED
+     * Test transition from STATE_CONNECTING --> (receive MSG_MAS_DISCONNECTED) -->
+     * STATE_DISCONNECTED
      */
     @Test
     public void testStateTransitionFromConnectingToDisconnected() {
@@ -126,7 +176,8 @@
         // state from STATE_CONNECTING to STATE_DISCONNECTED
         verify(mMockMapClientService,
                 timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(2)).sendBroadcast(
-                mIntentArgument.capture(), eq(ProfileService.BLUETOOTH_PERM));
+                mIntentArgument.capture(), eq(BLUETOOTH_CONNECT),
+                any(Bundle.class));
         Assert.assertEquals(BluetoothProfile.STATE_DISCONNECTED, mMceStateMachine.getState());
     }
 
@@ -146,13 +197,14 @@
         // state from STATE_CONNECTING to STATE_CONNECTED
         verify(mMockMapClientService,
                 timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(2)).sendBroadcast(
-                mIntentArgument.capture(), eq(ProfileService.BLUETOOTH_PERM));
+                mIntentArgument.capture(), eq(BLUETOOTH_CONNECT),
+                any(Bundle.class));
         Assert.assertEquals(BluetoothProfile.STATE_CONNECTED, mMceStateMachine.getState());
     }
 
-     /**
-     * Test transition from STATE_CONNECTING --> (receive MSG_MAS_CONNECTED) --> STATE_CONNECTED
-     * --> (receive MSG_MAS_DISCONNECTED) --> STATE_DISCONNECTED
+    /**
+     * Test transition from STATE_CONNECTING --> (receive MSG_MAS_CONNECTED) --> STATE_CONNECTED -->
+     * (receive MSG_MAS_DISCONNECTED) --> STATE_DISCONNECTED
      */
     @Test
     public void testStateTransitionFromConnectedWithMasDisconnected() {
@@ -167,14 +219,16 @@
         // state from STATE_CONNECTING to STATE_CONNECTED
         verify(mMockMapClientService,
                 timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(2)).sendBroadcast(
-                mIntentArgument.capture(), eq(ProfileService.BLUETOOTH_PERM));
+                mIntentArgument.capture(), eq(BLUETOOTH_CONNECT),
+                any(Bundle.class));
         Assert.assertEquals(BluetoothProfile.STATE_CONNECTED, mMceStateMachine.getState());
 
         msg = Message.obtain(mHandler, MceStateMachine.MSG_MAS_DISCONNECTED);
         mMceStateMachine.sendMessage(msg);
         verify(mMockMapClientService,
                 timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(4)).sendBroadcast(
-                mIntentArgument.capture(), eq(ProfileService.BLUETOOTH_PERM));
+                mIntentArgument.capture(), eq(BLUETOOTH_CONNECT),
+                any(Bundle.class));
 
         Assert.assertEquals(BluetoothProfile.STATE_DISCONNECTED, mMceStateMachine.getState());
     }
@@ -194,7 +248,8 @@
         // state from STATE_CONNECTING to STATE_CONNECTED
         verify(mMockMapClientService,
                 timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(2)).sendBroadcast(
-                mIntentArgument.capture(), eq(ProfileService.BLUETOOTH_PERM));
+                mIntentArgument.capture(), eq(BLUETOOTH_CONNECT),
+                any(Bundle.class));
         Assert.assertEquals(BluetoothProfile.STATE_CONNECTED, mMceStateMachine.getState());
 
         // Send an empty notification event, verify the mMceStateMachine is still connected
@@ -203,11 +258,149 @@
         Assert.assertEquals(BluetoothProfile.STATE_CONNECTED, mMceStateMachine.getState());
     }
 
+    /**
+     * Test set message status
+     */
+    @Test
+    public void testSetMessageStatus() {
+        setupSdpRecordReceipt();
+        Message msg = Message.obtain(mHandler, MceStateMachine.MSG_MAS_CONNECTED);
+        mMceStateMachine.sendMessage(msg);
+
+        // Wait until the message is processed and a broadcast request is sent to
+        // to MapClientService to change
+        // state from STATE_CONNECTING to STATE_CONNECTED
+        verify(mMockMapClientService,
+                timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(2)).sendBroadcast(
+                mIntentArgument.capture(), eq(BLUETOOTH_CONNECT),
+                any(Bundle.class));
+        Assert.assertEquals(BluetoothProfile.STATE_CONNECTED, mMceStateMachine.getState());
+        Assert.assertTrue(
+                mMceStateMachine.setMessageStatus("123456789AB", BluetoothMapClient.READ));
+    }
+
+
+    /**
+     * Test disconnect
+     */
+    @Test
+    public void testDisconnect() {
+        setupSdpRecordReceipt();
+        doAnswer(invocation -> {
+            mMceStateMachine.sendMessage(MceStateMachine.MSG_MAS_DISCONNECTED);
+            return null;
+        }).when(mMockMasClient).shutdown();
+        Message msg = Message.obtain(mHandler, MceStateMachine.MSG_MAS_CONNECTED);
+        mMceStateMachine.sendMessage(msg);
+
+        // Wait until the message is processed and a broadcast request is sent to
+        // to MapClientService to change
+        // state from STATE_CONNECTING to STATE_CONNECTED
+        verify(mMockMapClientService,
+                timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(2)).sendBroadcast(
+                mIntentArgument.capture(), eq(BLUETOOTH_CONNECT),
+                any(Bundle.class));
+        Assert.assertEquals(BluetoothProfile.STATE_CONNECTED, mMceStateMachine.getState());
+
+        mMceStateMachine.disconnect();
+        verify(mMockMapClientService,
+                timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(4)).sendBroadcast(
+                mIntentArgument.capture(), eq(BLUETOOTH_CONNECT),
+                any(Bundle.class));
+        Assert.assertEquals(BluetoothProfile.STATE_DISCONNECTED, mMceStateMachine.getState());
+    }
+
+    /**
+     * Test disconnect timeout
+     */
+    @Test
+    public void testDisconnectTimeout() {
+        setupSdpRecordReceipt();
+        Message msg = Message.obtain(mHandler, MceStateMachine.MSG_MAS_CONNECTED);
+        mMceStateMachine.sendMessage(msg);
+
+        // Wait until the message is processed and a broadcast request is sent to
+        // to MapClientService to change
+        // state from STATE_CONNECTING to STATE_CONNECTED
+        verify(mMockMapClientService,
+                timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(2)).sendBroadcast(
+                mIntentArgument.capture(), eq(BLUETOOTH_CONNECT),
+                any(Bundle.class));
+        Assert.assertEquals(BluetoothProfile.STATE_CONNECTED, mMceStateMachine.getState());
+
+        mMceStateMachine.disconnect();
+        verify(mMockMapClientService,
+                after(DISCONNECT_TIMEOUT / 2).times(3)).sendBroadcast(
+                mIntentArgument.capture(), eq(BLUETOOTH_CONNECT),
+                any(Bundle.class));
+        Assert.assertEquals(BluetoothProfile.STATE_DISCONNECTING, mMceStateMachine.getState());
+
+        verify(mMockMapClientService,
+                timeout(DISCONNECT_TIMEOUT).times(4)).sendBroadcast(
+                mIntentArgument.capture(), eq(BLUETOOTH_CONNECT),
+                any(Bundle.class));
+        Assert.assertEquals(BluetoothProfile.STATE_DISCONNECTED, mMceStateMachine.getState());
+    }
+
+    /**
+     * Test sending a message
+     */
+    @Test
+    public void testSendSMSMessage() {
+        setupSdpRecordReceipt();
+        Message msg = Message.obtain(mHandler, MceStateMachine.MSG_MAS_CONNECTED);
+        mMceStateMachine.sendMessage(msg);
+        TestUtils.waitForLooperToFinishScheduledTask(mMceStateMachine.getHandler().getLooper());
+        Assert.assertEquals(BluetoothProfile.STATE_CONNECTED, mMceStateMachine.getState());
+
+        String testMessage = "Hello World!";
+        Uri[] contacts = new Uri[] {Uri.parse("tel://5551212")};
+
+        verify(mMockMasClient, times(0)).makeRequest(any(RequestPushMessage.class));
+        mMceStateMachine.sendMapMessage(contacts, testMessage, null, null);
+        verify(mMockMasClient, timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(1))
+                .makeRequest(any(RequestPushMessage.class));
+    }
+
+    /**
+     * Test message sent successfully
+     */
+    @Test
+    public void testSMSMessageSent() {
+        setupSdpRecordReceipt();
+        Message msg = Message.obtain(mHandler, MceStateMachine.MSG_MAS_CONNECTED);
+        mMceStateMachine.sendMessage(msg);
+        TestUtils.waitForLooperToFinishScheduledTask(mMceStateMachine.getHandler().getLooper());
+        Assert.assertEquals(BluetoothProfile.STATE_CONNECTED, mMceStateMachine.getState());
+
+        String testMessage = "Hello World!";
+        VCardEntry recipient = new VCardEntry();
+        VCardProperty property = new VCardProperty();
+        property.setName(VCardConstants.PROPERTY_TEL);
+        property.addValues("555-1212");
+        recipient.addProperty(property);
+        Bmessage testBmessage = new Bmessage();
+        testBmessage.setType(Bmessage.Type.SMS_GSM);
+        testBmessage.setBodyContent(testMessage);
+        testBmessage.addRecipient(recipient);
+        RequestPushMessage testRequest =
+                new RequestPushMessage(FOLDER_SENT, testBmessage, null, false, false);
+        when(mMockRequestPushMessage.getMsgHandle()).thenReturn("12345");
+        when(mMockRequestPushMessage.getBMsg()).thenReturn(testBmessage);
+        Message msgSent = Message.obtain(mHandler, MceStateMachine.MSG_MAS_REQUEST_COMPLETED,
+                mMockRequestPushMessage);
+
+        mMceStateMachine.sendMessage(msgSent);
+        TestUtils.waitForLooperToFinishScheduledTask(mMceStateMachine.getHandler().getLooper());
+        Assert.assertEquals(1, mMockContentProvider.mInsertOperationCount);
+    }
+
     private void setupSdpRecordReceipt() {
         // Perform first part of MAP connection logic.
         verify(mMockMapClientService,
                 timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(1)).sendBroadcast(
-                mIntentArgument.capture(), eq(ProfileService.BLUETOOTH_PERM));
+                mIntentArgument.capture(), eq(BLUETOOTH_CONNECT),
+                any(Bundle.class));
         Assert.assertEquals(BluetoothProfile.STATE_CONNECTING, mMceStateMachine.getState());
 
         // Setup receipt of SDP record
@@ -216,4 +409,33 @@
         mMceStateMachine.sendMessage(msg);
     }
 
+    private class MockSmsContentProvider extends MockContentProvider {
+        Map<Uri, ContentValues> mContentValues = new HashMap<>();
+        int mInsertOperationCount = 0;
+
+        @Override
+        public int delete(Uri uri, String selection, String[] selectionArgs) {
+            return 0;
+        }
+
+        @Override
+        public Uri insert(Uri uri, ContentValues values) {
+            mInsertOperationCount++;
+            return Uri.withAppendedPath(Sms.CONTENT_URI, String.valueOf(mInsertOperationCount));
+        }
+
+        @Override
+        public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
+                String sortOrder) {
+            Cursor cursor = Mockito.mock(Cursor.class);
+
+            when(cursor.moveToFirst()).thenReturn(true);
+            when(cursor.moveToNext()).thenReturn(true).thenReturn(false);
+
+            when(cursor.getLong(anyInt())).thenReturn((long) mContentValues.size());
+            when(cursor.getString(anyInt())).thenReturn(String.valueOf(mContentValues.size()));
+            return cursor;
+        }
+    }
+
 }
diff --git a/tests/unit/src/com/android/bluetooth/mapclient/MapClientTest.java b/tests/unit/src/com/android/bluetooth/mapclient/MapClientTest.java
index 002cac9..394d5c7 100644
--- a/tests/unit/src/com/android/bluetooth/mapclient/MapClientTest.java
+++ b/tests/unit/src/com/android/bluetooth/mapclient/MapClientTest.java
@@ -21,7 +21,9 @@
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothDevice;
 import android.bluetooth.BluetoothProfile;
+import android.bluetooth.IBluetoothMapClient;
 import android.content.Context;
+import android.os.UserHandle;
 
 import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.MediumTest;
@@ -30,6 +32,7 @@
 
 import com.android.bluetooth.R;
 import com.android.bluetooth.TestUtils;
+import com.android.bluetooth.Utils;
 import com.android.bluetooth.btservice.AdapterService;
 import com.android.bluetooth.btservice.storage.DatabaseManager;
 
@@ -68,12 +71,13 @@
                 mTargetContext.getResources().getBoolean(R.bool.profile_supported_mapmce));
         MockitoAnnotations.initMocks(this);
         TestUtils.setAdapterService(mAdapterService);
+        when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager);
+        doReturn(true, false).when(mAdapterService).isStartedProfile(anyString());
         MapUtils.setMnsService(mMockMnsService);
         TestUtils.startService(mServiceRule, MapClientService.class);
         mService = MapClientService.getMapClientService();
         Assert.assertNotNull(mService);
         mAdapter = BluetoothAdapter.getDefaultAdapter();
-        when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager);
     }
 
     @After
@@ -181,6 +185,22 @@
         Assert.assertFalse(mService.connect(last));
     }
 
+    /**
+     * Test calling connect via Binder
+     */
+    @Test
+    public void testConnectViaBinder() {
+        BluetoothDevice device = makeBluetoothDevice("11:11:11:11:11:11");
+        mockDevicePriority(device, BluetoothProfile.CONNECTION_POLICY_ALLOWED);
+        IBluetoothMapClient.Stub mapClientBinder = (IBluetoothMapClient.Stub) mService.initBinder();
+        try {
+            Utils.setForegroundUserId(UserHandle.getCallingUserId());
+            Assert.assertTrue(mapClientBinder.connect(device, mAdapter.getAttributionSource()));
+        } catch (Exception e) {
+            Assert.fail(e.toString());
+        }
+    }
+
     private BluetoothDevice makeBluetoothDevice(String address) {
         return mAdapter.getRemoteDevice(address);
     }
diff --git a/tests/unit/src/com/android/bluetooth/newavrcp/BrowserPlayerWrapperTest.java b/tests/unit/src/com/android/bluetooth/newavrcp/BrowserPlayerWrapperTest.java
deleted file mode 100644
index 760edae..0000000
--- a/tests/unit/src/com/android/bluetooth/newavrcp/BrowserPlayerWrapperTest.java
+++ /dev/null
@@ -1,271 +0,0 @@
-/*
- * Copyright 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.bluetooth.avrcp;
-
-import static org.mockito.Mockito.*;
-
-import android.media.MediaDescription;
-import android.media.browse.MediaBrowser.MediaItem;
-import android.media.session.PlaybackState;
-import android.os.Handler;
-import android.os.HandlerThread;
-
-import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
-
-import org.junit.Assert;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
-import org.mockito.Captor;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-import java.util.ArrayList;
-import java.util.List;
-
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-public class BrowserPlayerWrapperTest {
-
-    @Captor ArgumentCaptor<MediaBrowser.ConnectionCallback> mBrowserConnCb;
-    @Captor ArgumentCaptor<MediaBrowser.SubscriptionCallback> mSubscriptionCb;
-    @Captor ArgumentCaptor<MediaController.Callback> mControllerCb;
-    @Captor ArgumentCaptor<Handler> mTimeoutHandler;
-    @Captor ArgumentCaptor<List<ListItem>> mWrapperBrowseCb;
-    @Mock MediaBrowser mMockBrowser;
-    @Mock BrowsedPlayerWrapper.ConnectionCallback mConnCb;
-    @Mock BrowsedPlayerWrapper.BrowseCallback mBrowseCb;
-    private HandlerThread mThread;
-
-    @Before
-    public void setUp() {
-        MockitoAnnotations.initMocks(this);
-
-        // Set up Looper thread for the timeout handler
-        mThread = new HandlerThread("MediaPlayerWrapperTestThread");
-        mThread.start();
-
-        when(mMockBrowser.getRoot()).thenReturn("root_folder");
-
-        MediaBrowserFactory.inject(mMockBrowser);
-    }
-
-    @Test
-    public void testWrap() {
-        BrowsedPlayerWrapper wrapper =
-                BrowsedPlayerWrapper.wrap(null, mThread.getLooper(), "test", "test");
-        wrapper.connect(mConnCb);
-        verify(mMockBrowser).testInit(any(), any(), mBrowserConnCb.capture(), any());
-        verify(mMockBrowser).connect();
-
-        MediaBrowser.ConnectionCallback browserConnCb = mBrowserConnCb.getValue();
-        browserConnCb.onConnected();
-
-        verify(mConnCb).run(eq(BrowsedPlayerWrapper.STATUS_SUCCESS), eq(wrapper));
-        verify(mMockBrowser).disconnect();
-    }
-
-    @Test
-    public void testConnect_Successful() {
-        BrowsedPlayerWrapper wrapper =
-                BrowsedPlayerWrapper.wrap(null, mThread.getLooper(), "test", "test");
-        wrapper.connect(mConnCb);
-        verify(mMockBrowser).testInit(any(), any(), mBrowserConnCb.capture(), any());
-        MediaBrowser.ConnectionCallback browserConnCb = mBrowserConnCb.getValue();
-
-        verify(mMockBrowser, times(1)).connect();
-        browserConnCb.onConnected();
-        verify(mConnCb).run(eq(BrowsedPlayerWrapper.STATUS_SUCCESS), eq(wrapper));
-        verify(mMockBrowser, times(1)).disconnect();
-    }
-
-    @Test
-    public void testConnect_Suspended() {
-        BrowsedPlayerWrapper wrapper =
-                BrowsedPlayerWrapper.wrap(null, mThread.getLooper(), "test", "test");
-        wrapper.connect(mConnCb);
-        verify(mMockBrowser).testInit(any(), any(), mBrowserConnCb.capture(), any());
-        MediaBrowser.ConnectionCallback browserConnCb = mBrowserConnCb.getValue();
-
-        verify(mMockBrowser, times(1)).connect();
-        browserConnCb.onConnectionSuspended();
-        verify(mConnCb).run(eq(BrowsedPlayerWrapper.STATUS_CONN_ERROR), eq(wrapper));
-        // Twice because our mConnCb is wrapped when using the plain connect() call and disconnect
-        // is called for us when the callback is invoked in addition to error handling calling
-        // disconnect.
-        verify(mMockBrowser, times(2)).disconnect();
-    }
-
-    @Test
-    public void testConnect_Failed() {
-        BrowsedPlayerWrapper wrapper =
-                BrowsedPlayerWrapper.wrap(null, mThread.getLooper(), "test", "test");
-        wrapper.connect(mConnCb);
-        verify(mMockBrowser).testInit(any(), any(), mBrowserConnCb.capture(), any());
-        MediaBrowser.ConnectionCallback browserConnCb = mBrowserConnCb.getValue();
-
-        verify(mMockBrowser, times(1)).connect();
-        browserConnCb.onConnectionFailed();
-        verify(mConnCb).run(eq(BrowsedPlayerWrapper.STATUS_CONN_ERROR), eq(wrapper));
-        verify(mMockBrowser, times(1)).disconnect();
-    }
-
-    @Test
-    public void testEmptyRoot() {
-        BrowsedPlayerWrapper wrapper =
-                BrowsedPlayerWrapper.wrap(null, mThread.getLooper(), "test", "test");
-
-        doReturn("").when(mMockBrowser).getRoot();
-
-        wrapper.connect(mConnCb);
-        verify(mMockBrowser).testInit(any(), any(), mBrowserConnCb.capture(), any());
-        MediaBrowser.ConnectionCallback browserConnCb = mBrowserConnCb.getValue();
-
-        verify(mMockBrowser, times(1)).connect();
-
-        browserConnCb.onConnected();
-        verify(mConnCb).run(eq(BrowsedPlayerWrapper.STATUS_CONN_ERROR), eq(wrapper));
-        verify(mMockBrowser, times(1)).disconnect();
-    }
-
-    @Test
-    public void testDisconnect() {
-        BrowsedPlayerWrapper wrapper =
-                BrowsedPlayerWrapper.wrap(null, mThread.getLooper(), "test", "test");
-        wrapper.connect(mConnCb);
-        verify(mMockBrowser).testInit(any(), any(), mBrowserConnCb.capture(), any());
-        MediaBrowser.ConnectionCallback browserConnCb = mBrowserConnCb.getValue();
-        browserConnCb.onConnected();
-        verify(mMockBrowser).disconnect();
-    }
-
-    @Test
-    public void testGetRootId() {
-        BrowsedPlayerWrapper wrapper =
-                BrowsedPlayerWrapper.wrap(null, mThread.getLooper(), "test", "test");
-        wrapper.connect(mConnCb);
-        verify(mMockBrowser).testInit(any(), any(), mBrowserConnCb.capture(), any());
-        MediaBrowser.ConnectionCallback browserConnCb = mBrowserConnCb.getValue();
-        browserConnCb.onConnected();
-
-        Assert.assertEquals("root_folder", wrapper.getRootId());
-        verify(mMockBrowser).disconnect();
-    }
-
-    @Test
-    public void testPlayItem() {
-        BrowsedPlayerWrapper wrapper =
-                BrowsedPlayerWrapper.wrap(null, mThread.getLooper(), "test", "test");
-        verify(mMockBrowser).testInit(any(), any(), mBrowserConnCb.capture(), any());
-        MediaBrowser.ConnectionCallback browserConnCb = mBrowserConnCb.getValue();
-
-        wrapper.playItem("test_item");
-        verify(mMockBrowser, times(1)).connect();
-
-        MediaController mockController = mock(MediaController.class);
-        MediaController.TransportControls mockTransport =
-                mock(MediaController.TransportControls.class);
-        when(mockController.getTransportControls()).thenReturn(mockTransport);
-        MediaControllerFactory.inject(mockController);
-
-        browserConnCb.onConnected();
-        verify(mockTransport).playFromMediaId(eq("test_item"), eq(null));
-
-        // Do not immediately disconnect. Non-foreground playback serves will likely fail
-        verify(mMockBrowser, times(0)).disconnect();
-
-        verify(mockController).registerCallback(mControllerCb.capture(), any());
-        MediaController.Callback controllerCb = mControllerCb.getValue();
-        PlaybackState.Builder builder = new PlaybackState.Builder();
-
-        // Do not disconnect on an event that isn't "playing"
-        builder.setState(PlaybackState.STATE_PAUSED, 0, 1);
-        controllerCb.onPlaybackStateChanged(builder.build());
-        verify(mMockBrowser, times(0)).disconnect();
-
-        // Once we're told we're playing, make sure we disconnect
-        builder.setState(PlaybackState.STATE_PLAYING, 0, 1);
-        controllerCb.onPlaybackStateChanged(builder.build());
-        verify(mMockBrowser, times(1)).disconnect();
-    }
-
-    @Test
-    public void testPlayItem_Timeout() {
-        BrowsedPlayerWrapper wrapper =
-                BrowsedPlayerWrapper.wrap(null, mThread.getLooper(), "test", "test");
-        verify(mMockBrowser).testInit(any(), any(), mBrowserConnCb.capture(), any());
-        MediaBrowser.ConnectionCallback browserConnCb = mBrowserConnCb.getValue();
-
-        wrapper.playItem("test_item");
-        verify(mMockBrowser, times(1)).connect();
-
-        MediaController mockController = mock(MediaController.class);
-        MediaController.TransportControls mockTransport =
-                mock(MediaController.TransportControls.class);
-        when(mockController.getTransportControls()).thenReturn(mockTransport);
-        MediaControllerFactory.inject(mockController);
-
-        browserConnCb.onConnected();
-        verify(mockTransport).playFromMediaId(eq("test_item"), eq(null));
-
-        verify(mockController).registerCallback(any(), mTimeoutHandler.capture());
-        Handler timeoutHandler = mTimeoutHandler.getValue();
-
-        timeoutHandler.sendEmptyMessage(BrowsedPlayerWrapper.TimeoutHandler.MSG_TIMEOUT);
-
-        verify(mMockBrowser, timeout(2000).times(1)).disconnect();
-    }
-
-    @Test
-    public void testGetFolderItems() {
-        BrowsedPlayerWrapper wrapper =
-                BrowsedPlayerWrapper.wrap(null, mThread.getLooper(), "test", "test");
-        verify(mMockBrowser).testInit(any(), any(), mBrowserConnCb.capture(), any());
-        MediaBrowser.ConnectionCallback browserConnCb = mBrowserConnCb.getValue();
-
-        wrapper.getFolderItems("test_folder", mBrowseCb);
-
-
-        browserConnCb.onConnected();
-        verify(mMockBrowser).subscribe(any(), mSubscriptionCb.capture());
-        MediaBrowser.SubscriptionCallback subscriptionCb = mSubscriptionCb.getValue();
-
-        ArrayList<MediaItem> items = new ArrayList<MediaItem>();
-        MediaDescription.Builder bob = new MediaDescription.Builder();
-        bob.setTitle("test_song1");
-        bob.setMediaId("ts1");
-        items.add(new MediaItem(bob.build(), 0));
-        bob.setTitle("test_song2");
-        bob.setMediaId("ts2");
-        items.add(new MediaItem(bob.build(), 0));
-
-        subscriptionCb.onChildrenLoaded("test_folder", items);
-        verify(mMockBrowser).unsubscribe(eq("test_folder"));
-        verify(mBrowseCb).run(eq(BrowsedPlayerWrapper.STATUS_SUCCESS), eq("test_folder"),
-                mWrapperBrowseCb.capture());
-
-        List<ListItem> item_list = mWrapperBrowseCb.getValue();
-        for (int i = 0; i < item_list.size(); i++) {
-            Assert.assertFalse(item_list.get(i).isFolder);
-            Assert.assertEquals(item_list.get(i).song, Util.toMetadata(items.get(i)));
-        }
-
-        verify(mMockBrowser).disconnect();
-    }
-}
diff --git a/tests/unit/src/com/android/bluetooth/opp/BluetoothOppServiceTest.java b/tests/unit/src/com/android/bluetooth/opp/BluetoothOppServiceTest.java
index 7f479d4..637310f 100644
--- a/tests/unit/src/com/android/bluetooth/opp/BluetoothOppServiceTest.java
+++ b/tests/unit/src/com/android/bluetooth/opp/BluetoothOppServiceTest.java
@@ -15,6 +15,9 @@
  */
 package com.android.bluetooth.opp;
 
+import static org.mockito.Mockito.anyString;
+import static org.mockito.Mockito.doReturn;
+
 import android.bluetooth.BluetoothAdapter;
 import android.content.Context;
 
@@ -55,6 +58,7 @@
                 mTargetContext.getResources().getBoolean(R.bool.profile_supported_opp));
         MockitoAnnotations.initMocks(this);
         TestUtils.setAdapterService(mAdapterService);
+        doReturn(true, false).when(mAdapterService).isStartedProfile(anyString());
         TestUtils.startService(mServiceRule, BluetoothOppService.class);
         mService = BluetoothOppService.getBluetoothOppService();
         Assert.assertNotNull(mService);
diff --git a/tests/unit/src/com/android/bluetooth/pan/PanServiceTest.java b/tests/unit/src/com/android/bluetooth/pan/PanServiceTest.java
index 3573f84..6a7b825 100644
--- a/tests/unit/src/com/android/bluetooth/pan/PanServiceTest.java
+++ b/tests/unit/src/com/android/bluetooth/pan/PanServiceTest.java
@@ -15,6 +15,8 @@
  */
 package com.android.bluetooth.pan;
 
+import static org.mockito.Mockito.anyString;
+import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.when;
 
 import android.bluetooth.BluetoothAdapter;
@@ -30,6 +32,7 @@
 import com.android.bluetooth.R;
 import com.android.bluetooth.TestUtils;
 import com.android.bluetooth.btservice.AdapterService;
+import com.android.bluetooth.btservice.storage.DatabaseManager;
 
 import org.junit.After;
 import org.junit.Assert;
@@ -51,6 +54,7 @@
     @Rule public final ServiceTestRule mServiceRule = new ServiceTestRule();
 
     @Mock private AdapterService mAdapterService;
+    @Mock private DatabaseManager mDatabaseManager;
     @Mock private UserManager mMockUserManager;
 
     @Before
@@ -60,6 +64,8 @@
                 mTargetContext.getResources().getBoolean(R.bool.profile_supported_pan));
         MockitoAnnotations.initMocks(this);
         TestUtils.setAdapterService(mAdapterService);
+        doReturn(mDatabaseManager).when(mAdapterService).getDatabase();
+        doReturn(true, false).when(mAdapterService).isStartedProfile(anyString());
         TestUtils.startService(mServiceRule, PanService.class);
         mService = PanService.getPanService();
         Assert.assertNotNull(mService);
diff --git a/tests/unit/src/com/android/bluetooth/pbap/BluetoothPbapServiceTest.java b/tests/unit/src/com/android/bluetooth/pbap/BluetoothPbapServiceTest.java
index 8990eb7..d983d9a 100644
--- a/tests/unit/src/com/android/bluetooth/pbap/BluetoothPbapServiceTest.java
+++ b/tests/unit/src/com/android/bluetooth/pbap/BluetoothPbapServiceTest.java
@@ -15,6 +15,9 @@
  */
 package com.android.bluetooth.pbap;
 
+import static org.mockito.Mockito.anyString;
+import static org.mockito.Mockito.doReturn;
+
 import android.bluetooth.BluetoothAdapter;
 import android.content.Context;
 
@@ -26,6 +29,7 @@
 import com.android.bluetooth.R;
 import com.android.bluetooth.TestUtils;
 import com.android.bluetooth.btservice.AdapterService;
+import com.android.bluetooth.btservice.storage.DatabaseManager;
 
 import org.junit.After;
 import org.junit.Assert;
@@ -47,6 +51,7 @@
     @Rule public final ServiceTestRule mServiceRule = new ServiceTestRule();
 
     @Mock private AdapterService mAdapterService;
+    @Mock private DatabaseManager mDatabaseManager;
 
     @Before
     public void setUp() throws Exception {
@@ -55,6 +60,8 @@
                 mTargetContext.getResources().getBoolean(R.bool.profile_supported_pbap));
         MockitoAnnotations.initMocks(this);
         TestUtils.setAdapterService(mAdapterService);
+        doReturn(mDatabaseManager).when(mAdapterService).getDatabase();
+        doReturn(true, false).when(mAdapterService).isStartedProfile(anyString());
         TestUtils.startService(mServiceRule, BluetoothPbapService.class);
         mService = BluetoothPbapService.getBluetoothPbapService();
         Assert.assertNotNull(mService);
diff --git a/tests/unit/src/com/android/bluetooth/pbapclient/PbapClientServiceTest.java b/tests/unit/src/com/android/bluetooth/pbapclient/PbapClientServiceTest.java
index 5e9b5ae..de76c1d 100644
--- a/tests/unit/src/com/android/bluetooth/pbapclient/PbapClientServiceTest.java
+++ b/tests/unit/src/com/android/bluetooth/pbapclient/PbapClientServiceTest.java
@@ -15,6 +15,9 @@
  */
 package com.android.bluetooth.pbapclient;
 
+import static org.mockito.Mockito.anyString;
+import static org.mockito.Mockito.doReturn;
+
 import android.bluetooth.BluetoothAdapter;
 import android.content.Context;
 
@@ -26,6 +29,7 @@
 import com.android.bluetooth.R;
 import com.android.bluetooth.TestUtils;
 import com.android.bluetooth.btservice.AdapterService;
+import com.android.bluetooth.btservice.storage.DatabaseManager;
 
 import org.junit.After;
 import org.junit.Assert;
@@ -48,6 +52,8 @@
 
     @Mock private AdapterService mAdapterService;
 
+    @Mock private DatabaseManager mDatabaseManager;
+
     @Before
     public void setUp() throws Exception {
         mTargetContext = InstrumentationRegistry.getTargetContext();
@@ -55,6 +61,8 @@
                 mTargetContext.getResources().getBoolean(R.bool.profile_supported_pbapclient));
         MockitoAnnotations.initMocks(this);
         TestUtils.setAdapterService(mAdapterService);
+        doReturn(mDatabaseManager).when(mAdapterService).getDatabase();
+        doReturn(true, false).when(mAdapterService).isStartedProfile(anyString());
         TestUtils.startService(mServiceRule, PbapClientService.class);
         mService = PbapClientService.getPbapClientService();
         Assert.assertNotNull(mService);
diff --git a/tests/unit/src/com/android/bluetooth/sap/SapServiceTest.java b/tests/unit/src/com/android/bluetooth/sap/SapServiceTest.java
index bc5bffe..d7aa471 100644
--- a/tests/unit/src/com/android/bluetooth/sap/SapServiceTest.java
+++ b/tests/unit/src/com/android/bluetooth/sap/SapServiceTest.java
@@ -15,6 +15,9 @@
  */
 package com.android.bluetooth.sap;
 
+import static org.mockito.Mockito.anyString;
+import static org.mockito.Mockito.doReturn;
+
 import android.bluetooth.BluetoothAdapter;
 import android.content.Context;
 
@@ -55,6 +58,7 @@
                 mTargetContext.getResources().getBoolean(R.bool.profile_supported_sap));
         MockitoAnnotations.initMocks(this);
         TestUtils.setAdapterService(mAdapterService);
+        doReturn(true, false).when(mAdapterService).isStartedProfile(anyString());
         TestUtils.startService(mServiceRule, SapService.class);
         mService = SapService.getSapService();
         Assert.assertNotNull(mService);
diff --git a/tests/unit/src/com/android/bluetooth/sdp/DipTest.java b/tests/unit/src/com/android/bluetooth/sdp/DipTest.java
new file mode 100755
index 0000000..b2cd072
--- /dev/null
+++ b/tests/unit/src/com/android/bluetooth/sdp/DipTest.java
@@ -0,0 +1,141 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.bluetooth.sdp;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Mockito.verify;
+
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothProfile;
+import android.bluetooth.BluetoothUuid;
+import android.bluetooth.SdpDipRecord;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Bundle;
+import android.os.Looper;
+import android.os.ParcelUuid;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.bluetooth.R;
+import com.android.bluetooth.sdp.SdpManager;
+import com.android.bluetooth.TestUtils;
+import com.android.bluetooth.btservice.AbstractionLayer;
+import com.android.bluetooth.btservice.AdapterService;
+import com.android.bluetooth.Utils;
+
+import org.junit.After;
+import org.junit.Assume;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class DipTest {
+    private BluetoothAdapter mAdapter;
+    private Context mTargetContext;
+    private SdpManager mSdpManager;
+    private BluetoothDevice mTestDevice;
+
+    private ArgumentCaptor<Intent> mIntentArgument = ArgumentCaptor.forClass(Intent.class);
+    private ArgumentCaptor<String> mStringArgument = ArgumentCaptor.forClass(String.class);
+    private ArgumentCaptor<Bundle> mBundleArgument = ArgumentCaptor.forClass(Bundle.class);
+
+    @Mock private AdapterService mAdapterService = null;
+
+    @Before
+    public void setUp() throws Exception {
+        mTargetContext = InstrumentationRegistry.getTargetContext();
+        // Set up mocks and test assets
+        MockitoAnnotations.initMocks(this);
+
+        TestUtils.setAdapterService(mAdapterService);
+
+        if (Looper.myLooper() == null) {
+            Looper.prepare();
+        }
+
+        mAdapter = BluetoothAdapter.getDefaultAdapter();
+        mSdpManager = SdpManager.init(mAdapterService);
+
+        // Get a device for testing
+        mTestDevice = mAdapter.getRemoteDevice("00:01:02:03:04:05");
+    }
+
+    @After
+    public void tearDown() throws Exception {
+    }
+
+    private void verifyDipSdpRecordIntent(ArgumentCaptor<Intent> intentArgument,
+            int status, BluetoothDevice device,
+            byte[] uuid,  int specificationId,
+            int vendorId, int vendorIdSource,
+            int productId, int version,
+            boolean primaryRecord) {
+        Intent intent = intentArgument.getValue();
+
+        assertThat(intent).isNotEqualTo(null);
+        assertThat(intent.getAction()).isEqualTo(BluetoothDevice.ACTION_SDP_RECORD);
+        assertThat(device).isEqualTo(intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE));
+        assertThat(Utils.byteArrayToUuid(uuid)[0]).isEqualTo(intent.getParcelableExtra(BluetoothDevice.EXTRA_UUID));
+        assertThat(status).isEqualTo(intent.getIntExtra(BluetoothDevice.EXTRA_SDP_SEARCH_STATUS, -1));
+
+        SdpDipRecord record = intent.getParcelableExtra(BluetoothDevice.EXTRA_SDP_RECORD);
+        assertThat(record).isNotEqualTo(null);
+        assertThat(specificationId).isEqualTo(record.getSpecificationId());
+        assertThat(vendorId).isEqualTo(record.getVendorId());
+        assertThat(vendorIdSource).isEqualTo(record.getVendorIdSource());
+        assertThat(productId).isEqualTo(record.getProductId());
+        assertThat(version).isEqualTo(record.getVersion());
+        assertThat(primaryRecord).isEqualTo(record.getPrimaryRecord());
+    }
+
+    /**
+     * Test that an outgoing connection/disconnection succeeds
+     */
+    @Test
+    @SmallTest
+    public void testDipCallbackSuccess() {
+        // DIP uuid in bytes
+        byte[] uuid = {0, 0, 18, 0, 0, 0, 16, 0, -128, 0, 0, -128, 95, -101, 52, -5};
+        int specificationId = 0x0103;
+        int vendorId = 0x18d1;
+        int vendorIdSource = 1;
+        int productId = 0x1234;
+        int version = 0x0100;
+        boolean primaryRecord = true;
+        boolean moreResults = false;
+
+        mSdpManager.sdpSearch(mTestDevice, BluetoothUuid.DIP);
+        mSdpManager.sdpDipRecordFoundCallback(AbstractionLayer.BT_STATUS_SUCCESS,
+                Utils.getByteAddress(mTestDevice), uuid, specificationId,
+                vendorId, vendorIdSource, productId, version, primaryRecord, moreResults);
+        verify(mAdapterService).sendBroadcast(mIntentArgument.capture(), mStringArgument.capture(),
+                mBundleArgument.capture());
+        verifyDipSdpRecordIntent(mIntentArgument, AbstractionLayer.BT_STATUS_SUCCESS, mTestDevice,
+                uuid, specificationId, vendorId, vendorIdSource, productId, version, primaryRecord);
+    }
+}
diff --git a/tests/unit/src/com/android/bluetooth/telephony/BluetoothInCallServiceTest.java b/tests/unit/src/com/android/bluetooth/telephony/BluetoothInCallServiceTest.java
new file mode 100644
index 0000000..dd5dbe9
--- /dev/null
+++ b/tests/unit/src/com/android/bluetooth/telephony/BluetoothInCallServiceTest.java
@@ -0,0 +1,1176 @@
+/*
+ * Copyright (C) 2020 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.telephony;
+
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.*;
+
+import android.bluetooth.BluetoothAdapter;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.net.Uri;
+import android.os.Binder;
+import android.os.IBinder;
+import android.telecom.Call;
+import android.telecom.Connection;
+import android.telecom.GatewayInfo;
+import android.telecom.PhoneAccount;
+import android.telecom.PhoneAccountHandle;
+import android.telecom.TelecomManager;
+import android.telephony.PhoneNumberUtils;
+import android.telephony.TelephonyManager;
+import android.util.Log;
+
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.rule.ServiceTestRule;
+import androidx.test.filters.MediumTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.bluetooth.hfp.BluetoothHeadsetProxy;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.UUID;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Tests for {@link BluetoothInCallService}
+ */
+@MediumTest
+@RunWith(AndroidJUnit4.class)
+public class BluetoothInCallServiceTest {
+
+    private static final int TEST_DTMF_TONE = 0;
+    private static final String TEST_ACCOUNT_ADDRESS = "//foo.com/";
+    private static final int TEST_ACCOUNT_INDEX = 0;
+
+    private static final int CALL_STATE_ACTIVE = 0;
+    private static final int CALL_STATE_HELD = 1;
+    private static final int CALL_STATE_DIALING = 2;
+    private static final int CALL_STATE_ALERTING = 3;
+    private static final int CALL_STATE_INCOMING = 4;
+    private static final int CALL_STATE_WAITING = 5;
+    private static final int CALL_STATE_IDLE = 6;
+    private static final int CALL_STATE_DISCONNECTED = 7;
+    // Terminate all held or set UDUB("busy") to a waiting call
+    private static final int CHLD_TYPE_RELEASEHELD = 0;
+    // Terminate all active calls and accepts a waiting/held call
+    private static final int CHLD_TYPE_RELEASEACTIVE_ACCEPTHELD = 1;
+    // Hold all active calls and accepts a waiting/held call
+    private static final int CHLD_TYPE_HOLDACTIVE_ACCEPTHELD = 2;
+    // Add all held calls to a conference
+    private static final int CHLD_TYPE_ADDHELDTOCONF = 3;
+
+    private TestableBluetoothInCallService mBluetoothInCallService;
+    @Rule public final ServiceTestRule mServiceRule
+            = ServiceTestRule.withTimeout(1, TimeUnit.SECONDS);
+
+    @Mock private BluetoothHeadsetProxy mMockBluetoothHeadset;
+    @Mock private BluetoothInCallService.CallInfo mMockCallInfo;
+    @Mock private TelephonyManager mMockTelephonyManager;
+
+    public class TestableBluetoothInCallService extends BluetoothInCallService {
+        @Override
+        public IBinder onBind(Intent intent) {
+            IBinder binder = super.onBind(intent);
+            IntentFilter intentFilter = new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED);
+            registerReceiver(mBluetoothAdapterReceiver, intentFilter);
+            mTelephonyManager = getSystemService(TelephonyManager.class);
+            mTelecomManager = getSystemService(TelecomManager.class);
+            return binder;
+        }
+        @Override
+        protected void enforceModifyPermission() {}
+    }
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+
+        // Create the service Intent.
+        Intent serviceIntent =
+                new Intent(ApplicationProvider.getApplicationContext(),
+                        TestableBluetoothInCallService.class);
+        // Bind the service
+        mServiceRule.bindService(serviceIntent);
+
+        // Ensure initialization does not actually try to access any of the CallsManager fields.
+        // This also works to return null if it is not overwritten later in the test.
+        doReturn(null).when(mMockCallInfo).getActiveCall();
+        doReturn(null).when(mMockCallInfo)
+                .getRingingOrSimulatedRingingCall();
+        doReturn(null).when(mMockCallInfo).getHeldCall();
+        doReturn(null).when(mMockCallInfo).getOutgoingCall();
+        doReturn(0).when(mMockCallInfo).getNumHeldCalls();
+        doReturn(false).when(mMockCallInfo).hasOnlyDisconnectedCalls();
+        doReturn(true).when(mMockCallInfo).isNullCall(null);
+        doReturn(false).when(mMockCallInfo).isNullCall(notNull());
+
+        mBluetoothInCallService = new TestableBluetoothInCallService();
+        mBluetoothInCallService.setBluetoothHeadset(mMockBluetoothHeadset);
+        mBluetoothInCallService.mCallInfo = mMockCallInfo;
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        mServiceRule.unbindService();
+        mBluetoothInCallService = null;
+    }
+
+    @Test
+    public void testHeadsetAnswerCall() throws Exception {
+        BluetoothCall mockCall = createRingingCall();
+
+        boolean callAnswered = mBluetoothInCallService.answerCall();
+        verify(mockCall).answer(any(int.class));
+
+        Assert.assertTrue(callAnswered);
+    }
+
+    @Test
+    public void testHeadsetAnswerCallNull() throws Exception {
+        when(mMockCallInfo.getRingingOrSimulatedRingingCall()).thenReturn(null);
+
+        boolean callAnswered = mBluetoothInCallService.answerCall();
+        Assert.assertFalse(callAnswered);
+    }
+
+    @Test
+    public void testHeadsetHangupCall() throws Exception {
+        BluetoothCall mockCall = createForegroundCall();
+
+        boolean callHungup = mBluetoothInCallService.hangupCall();
+
+        verify(mockCall).disconnect();
+        Assert.assertTrue(callHungup);
+    }
+
+    @Test
+    public void testHeadsetHangupCallNull() throws Exception {
+        when(mMockCallInfo.getForegroundCall()).thenReturn(null);
+
+        boolean callHungup = mBluetoothInCallService.hangupCall();
+        Assert.assertFalse(callHungup);
+    }
+
+    @Test
+    public void testHeadsetSendDTMF() throws Exception {
+        BluetoothCall mockCall = createForegroundCall();
+
+        boolean sentDtmf = mBluetoothInCallService.sendDtmf(TEST_DTMF_TONE);
+
+        verify(mockCall).playDtmfTone(eq((char) TEST_DTMF_TONE));
+        verify(mockCall).stopDtmfTone();
+        Assert.assertTrue(sentDtmf);
+    }
+
+    @Test
+    public void testHeadsetSendDTMFNull() throws Exception {
+        when(mMockCallInfo.getForegroundCall()).thenReturn(null);
+
+        boolean sentDtmf = mBluetoothInCallService.sendDtmf(TEST_DTMF_TONE);
+        Assert.assertFalse(sentDtmf);
+    }
+
+    @Test
+    public void testGetNetworkOperator() throws Exception {
+        PhoneAccount fakePhoneAccount = makeQuickAccount("id0", TEST_ACCOUNT_INDEX);
+        when(mMockCallInfo.getBestPhoneAccount()).thenReturn(fakePhoneAccount);
+
+        String networkOperator = mBluetoothInCallService.getNetworkOperator();
+        Assert.assertEquals(networkOperator, "label0");
+    }
+
+    @Test
+    public void testGetNetworkOperatorNoPhoneAccount() throws Exception {
+        when(mMockCallInfo.getForegroundCall()).thenReturn(null);
+        when(mMockTelephonyManager.getNetworkOperatorName()).thenReturn("label1");
+        mBluetoothInCallService.mTelephonyManager = mMockTelephonyManager;
+
+        String networkOperator = mBluetoothInCallService.getNetworkOperator();
+        Assert.assertEquals(networkOperator, "label1");
+    }
+
+    @Test
+    public void testGetSubscriberNumber() throws Exception {
+        PhoneAccount fakePhoneAccount = makeQuickAccount("id0", TEST_ACCOUNT_INDEX);
+        when(mMockCallInfo.getBestPhoneAccount()).thenReturn(fakePhoneAccount);
+
+        String subscriberNumber = mBluetoothInCallService.getSubscriberNumber();
+        Assert.assertEquals(subscriberNumber, TEST_ACCOUNT_ADDRESS + TEST_ACCOUNT_INDEX);
+    }
+
+    @Test
+    public void testGetSubscriberNumberFallbackToTelephony() throws Exception {
+        String fakeNumber = "8675309";
+        when(mMockCallInfo.getBestPhoneAccount()).thenReturn(null);
+        when(mMockTelephonyManager.getLine1Number())
+                .thenReturn(fakeNumber);
+        mBluetoothInCallService.mTelephonyManager = mMockTelephonyManager;
+
+        String subscriberNumber = mBluetoothInCallService.getSubscriberNumber();
+        Assert.assertEquals(subscriberNumber, fakeNumber);
+    }
+
+    @Test
+    public void testListCurrentCallsOneCall() throws Exception {
+        ArrayList<BluetoothCall> calls = new ArrayList<>();
+        BluetoothCall activeCall = createActiveCall();
+        when(activeCall.getState()).thenReturn(Call.STATE_ACTIVE);
+        calls.add(activeCall);
+        mBluetoothInCallService.onCallAdded(activeCall);
+        when(activeCall.isConference()).thenReturn(false);
+        when(activeCall.getHandle()).thenReturn(Uri.parse("tel:555-000"));
+        when(mMockCallInfo.getBluetoothCalls()).thenReturn(calls);
+
+        clearInvocations(mMockBluetoothHeadset);
+        mBluetoothInCallService.listCurrentCalls();
+
+        verify(mMockBluetoothHeadset).clccResponse(eq(1), eq(0), eq(0), eq(0), eq(false),
+                eq("555000"), eq(PhoneNumberUtils.TOA_Unknown));
+        verify(mMockBluetoothHeadset).clccResponse(0, 0, 0, 0, false, null, 0);
+    }
+
+    @Test
+    public void testListCurrentCallsSilentRinging() throws Exception {
+        ArrayList<BluetoothCall> calls = new ArrayList<>();
+        BluetoothCall silentRingingCall = createActiveCall();
+        when(silentRingingCall.getState()).thenReturn(Call.STATE_RINGING);
+        when(silentRingingCall.isSilentRingingRequested()).thenReturn(true);
+        calls.add(silentRingingCall);
+        mBluetoothInCallService.onCallAdded(silentRingingCall);
+
+        when(silentRingingCall.isConference()).thenReturn(false);
+        when(silentRingingCall.getHandle()).thenReturn(Uri.parse("tel:555-000"));
+        when(mMockCallInfo.getBluetoothCalls()).thenReturn(calls);
+        when(mMockCallInfo.getRingingOrSimulatedRingingCall()).thenReturn(silentRingingCall);
+
+        clearInvocations(mMockBluetoothHeadset);
+        mBluetoothInCallService.listCurrentCalls();
+
+        verify(mMockBluetoothHeadset, never()).clccResponse(eq(1), eq(0), eq(0), eq(0), eq(false),
+                eq("555000"), eq(PhoneNumberUtils.TOA_Unknown));
+        verify(mMockBluetoothHeadset).clccResponse(0, 0, 0, 0, false, null, 0);
+    }
+
+    @Test
+    public void testConferenceInProgressCDMA() throws Exception {
+        // If two calls are being conferenced and updateHeadsetWithCallState runs while this is
+        // still occurring, it will look like there is an active and held BluetoothCall still while
+        // we are transitioning into a conference.
+        // BluetoothCall has been put into a CDMA "conference" with one BluetoothCall on hold.
+        ArrayList<BluetoothCall>   calls = new ArrayList<>();
+        BluetoothCall parentCall = createActiveCall();
+        final BluetoothCall confCall1 = getMockCall();
+        final BluetoothCall confCall2 = createHeldCall();
+        calls.add(parentCall);
+        calls.add(confCall1);
+        calls.add(confCall2);
+        mBluetoothInCallService.onCallAdded(parentCall);
+        mBluetoothInCallService.onCallAdded(confCall1);
+        mBluetoothInCallService.onCallAdded(confCall2);
+
+        when(mMockCallInfo.getBluetoothCalls()).thenReturn(calls);
+        when(confCall1.getState()).thenReturn(Call.STATE_ACTIVE);
+        when(confCall2.getState()).thenReturn(Call.STATE_ACTIVE);
+        when(confCall1.isIncoming()).thenReturn(false);
+        when(confCall2.isIncoming()).thenReturn(true);
+        when(confCall1.getGatewayInfo()).thenReturn(
+                new GatewayInfo(null, null, Uri.parse("tel:555-0000")));
+        when(confCall2.getGatewayInfo()).thenReturn(
+                new GatewayInfo(null, null, Uri.parse("tel:555-0001")));
+        addCallCapability(parentCall, Connection.CAPABILITY_MERGE_CONFERENCE);
+        addCallCapability(parentCall, Connection.CAPABILITY_SWAP_CONFERENCE);
+        removeCallCapability(parentCall, Connection.CAPABILITY_CONFERENCE_HAS_NO_CHILDREN);
+        String confCall1Id = confCall1.getTelecomCallId();
+        when(parentCall.getGenericConferenceActiveChildCallId())
+                .thenReturn(confCall1Id);
+        when(parentCall.isConference()).thenReturn(true);
+        List<String> childrenIds = Arrays.asList(confCall1.getTelecomCallId(),
+                confCall2.getTelecomCallId());
+        when(parentCall.getChildrenIds()).thenReturn(childrenIds);
+        //Add links from child calls to parent
+        String parentId = parentCall.getTelecomCallId();
+        when(confCall1.getParentId()).thenReturn(parentId);
+        when(confCall2.getParentId()).thenReturn(parentId);
+
+        clearInvocations(mMockBluetoothHeadset);
+        mBluetoothInCallService.queryPhoneState();
+        verify(mMockBluetoothHeadset).phoneStateChanged(eq(1), eq(1), eq(CALL_STATE_IDLE),
+                eq(""), eq(128), nullable(String.class));
+
+        when(parentCall.wasConferencePreviouslyMerged()).thenReturn(true);
+        List<BluetoothCall> children =
+                mBluetoothInCallService.getBluetoothCallsByIds(parentCall.getChildrenIds());
+        mBluetoothInCallService.getCallback(parentCall)
+                .onChildrenChanged(parentCall, children);
+        verify(mMockBluetoothHeadset).phoneStateChanged(eq(1), eq(0), eq(CALL_STATE_IDLE),
+                eq(""), eq(128), nullable(String.class));
+
+        when(mMockCallInfo.getHeldCall()).thenReturn(null);
+        // Spurious BluetoothCall to onIsConferencedChanged.
+        mBluetoothInCallService.getCallback(parentCall)
+                .onChildrenChanged(parentCall, children);
+        // Make sure the BluetoothCall has only occurred collectively 2 times (not on the third)
+        verify(mMockBluetoothHeadset, times(2)).phoneStateChanged(any(int.class),
+                any(int.class), any(int.class), nullable(String.class), any(int.class),
+                nullable(String.class));
+    }
+
+    @Test
+    public void testListCurrentCallsCdmaHold() throws Exception {
+        // BluetoothCall has been put into a CDMA "conference" with one BluetoothCall on hold.
+        List<BluetoothCall> calls = new ArrayList<BluetoothCall>();
+        BluetoothCall parentCall = createActiveCall();
+        final BluetoothCall foregroundCall = getMockCall();
+        final BluetoothCall heldCall = createHeldCall();
+        calls.add(parentCall);
+        calls.add(foregroundCall);
+        calls.add(heldCall);
+        mBluetoothInCallService.onCallAdded(parentCall);
+        mBluetoothInCallService.onCallAdded(foregroundCall);
+        mBluetoothInCallService.onCallAdded(heldCall);
+
+        when(mMockCallInfo.getBluetoothCalls()).thenReturn(calls);
+        when(foregroundCall.getState()).thenReturn(Call.STATE_ACTIVE);
+        when(heldCall.getState()).thenReturn(Call.STATE_ACTIVE);
+        when(foregroundCall.isIncoming()).thenReturn(false);
+        when(heldCall.isIncoming()).thenReturn(true);
+        when(foregroundCall.getGatewayInfo()).thenReturn(
+                new GatewayInfo(null, null, Uri.parse("tel:555-0000")));
+        when(heldCall.getGatewayInfo()).thenReturn(
+                new GatewayInfo(null, null, Uri.parse("tel:555-0001")));
+        addCallCapability(parentCall, Connection.CAPABILITY_MERGE_CONFERENCE);
+        addCallCapability(parentCall, Connection.CAPABILITY_SWAP_CONFERENCE);
+        removeCallCapability(parentCall, Connection.CAPABILITY_CONFERENCE_HAS_NO_CHILDREN);
+
+        String foregroundCallId = foregroundCall.getTelecomCallId();
+        when(parentCall.getGenericConferenceActiveChildCallId()).thenReturn(foregroundCallId);
+        when(parentCall.isConference()).thenReturn(true);
+        List<String> childrenIds = Arrays.asList(foregroundCall.getTelecomCallId(),
+                heldCall.getTelecomCallId());
+        when(parentCall.getChildrenIds()).thenReturn(childrenIds);
+        //Add links from child calls to parent
+        String parentId = parentCall.getTelecomCallId();
+        when(foregroundCall.getParentId()).thenReturn(parentId);
+        when(heldCall.getParentId()).thenReturn(parentId);
+
+        clearInvocations(mMockBluetoothHeadset);
+        mBluetoothInCallService.listCurrentCalls();
+
+        verify(mMockBluetoothHeadset).clccResponse(eq(1), eq(0), eq(CALL_STATE_ACTIVE), eq(0),
+                eq(false), eq("5550000"), eq(PhoneNumberUtils.TOA_Unknown));
+        verify(mMockBluetoothHeadset).clccResponse(eq(2), eq(1), eq(CALL_STATE_HELD), eq(0),
+                eq(false), eq("5550001"), eq(PhoneNumberUtils.TOA_Unknown));
+        verify(mMockBluetoothHeadset).clccResponse(0, 0, 0, 0, false, null, 0);
+    }
+
+    @Test
+    public void testListCurrentCallsCdmaConference() throws Exception {
+        // BluetoothCall is in a true CDMA conference
+        ArrayList<BluetoothCall> calls = new ArrayList<>();
+        BluetoothCall parentCall = createActiveCall();
+        final BluetoothCall confCall1 = getMockCall();
+        final BluetoothCall confCall2 = createHeldCall();
+        calls.add(parentCall);
+        calls.add(confCall1);
+        calls.add(confCall2);
+        mBluetoothInCallService.onCallAdded(parentCall);
+        mBluetoothInCallService.onCallAdded(confCall1);
+        mBluetoothInCallService.onCallAdded(confCall2);
+
+        when(mMockCallInfo.getBluetoothCalls()).thenReturn(calls);
+        when(confCall1.getState()).thenReturn(Call.STATE_ACTIVE);
+        when(confCall2.getState()).thenReturn(Call.STATE_ACTIVE);
+        when(confCall1.isIncoming()).thenReturn(false);
+        when(confCall2.isIncoming()).thenReturn(true);
+        when(confCall1.getGatewayInfo()).thenReturn(
+                new GatewayInfo(null, null, Uri.parse("tel:555-0000")));
+        when(confCall2.getGatewayInfo()).thenReturn(
+                new GatewayInfo(null, null, Uri.parse("tel:555-0001")));
+        removeCallCapability(parentCall, Connection.CAPABILITY_MERGE_CONFERENCE);
+        removeCallCapability(parentCall, Connection.CAPABILITY_CONFERENCE_HAS_NO_CHILDREN);
+        when(parentCall.wasConferencePreviouslyMerged()).thenReturn(true);
+        //when(parentCall.getConferenceLevelActiveCall()).thenReturn(confCall1);
+        when(parentCall.isConference()).thenReturn(true);
+        List<String> childrenIds = Arrays.asList(confCall1.getTelecomCallId(),
+            confCall2.getTelecomCallId());
+        when(parentCall.getChildrenIds()).thenReturn(childrenIds);
+        //Add links from child calls to parent
+        String parentId = parentCall.getTelecomCallId();
+        when(confCall1.getParentId()).thenReturn(parentId);
+        when(confCall2.getParentId()).thenReturn(parentId);
+
+        clearInvocations(mMockBluetoothHeadset);
+        mBluetoothInCallService.listCurrentCalls();
+
+        verify(mMockBluetoothHeadset).clccResponse(eq(1), eq(0), eq(CALL_STATE_ACTIVE), eq(0),
+                eq(true), eq("5550000"), eq(PhoneNumberUtils.TOA_Unknown));
+        verify(mMockBluetoothHeadset).clccResponse(eq(2), eq(1), eq(CALL_STATE_ACTIVE), eq(0),
+                eq(true), eq("5550001"), eq(PhoneNumberUtils.TOA_Unknown));
+        verify(mMockBluetoothHeadset).clccResponse(0, 0, 0, 0, false, null, 0);
+    }
+
+    @Test
+    public void testWaitingCallClccResponse() throws Exception {
+        ArrayList<BluetoothCall> calls = new ArrayList<>();
+        when(mMockCallInfo.getBluetoothCalls()).thenReturn(calls);
+        // This test does not define a value for getForegroundCall(), so this ringing
+        // BluetoothCall will be treated as if it is a waiting BluetoothCall
+        // when listCurrentCalls() is invoked.
+        BluetoothCall waitingCall = createRingingCall();
+        calls.add(waitingCall);
+        mBluetoothInCallService.onCallAdded(waitingCall);
+
+        when(waitingCall.isIncoming()).thenReturn(true);
+        when(waitingCall.getGatewayInfo()).thenReturn(
+                new GatewayInfo(null, null, Uri.parse("tel:555-0000")));
+        when(waitingCall.getState()).thenReturn(Call.STATE_RINGING);
+        when(waitingCall.isConference()).thenReturn(false);
+
+        clearInvocations(mMockBluetoothHeadset);
+        mBluetoothInCallService.listCurrentCalls();
+        verify(mMockBluetoothHeadset).clccResponse(1, 1, CALL_STATE_WAITING, 0, false,
+                "5550000", PhoneNumberUtils.TOA_Unknown);
+        verify(mMockBluetoothHeadset).clccResponse(0, 0, 0, 0, false, null, 0);
+        verify(mMockBluetoothHeadset, times(2)).clccResponse(anyInt(),
+                anyInt(), anyInt(), anyInt(), anyBoolean(), nullable(String.class), anyInt());
+    }
+
+    @Test
+    public void testNewCallClccResponse() throws Exception {
+        ArrayList<BluetoothCall> calls = new ArrayList<>();
+        when(mMockCallInfo.getBluetoothCalls()).thenReturn(calls);
+        BluetoothCall newCall = createForegroundCall();
+        calls.add(newCall);
+        mBluetoothInCallService.onCallAdded(newCall);
+
+        when(newCall.getState()).thenReturn(Call.STATE_NEW);
+        when(newCall.isConference()).thenReturn(false);
+
+        clearInvocations(mMockBluetoothHeadset);
+        mBluetoothInCallService.listCurrentCalls();
+        verify(mMockBluetoothHeadset).clccResponse(0, 0, 0, 0, false, null, 0);
+        verify(mMockBluetoothHeadset, times(1)).clccResponse(anyInt(),
+                anyInt(), anyInt(), anyInt(), anyBoolean(), nullable(String.class), anyInt());
+    }
+
+    @Test
+    public void testRingingCallClccResponse() throws Exception {
+        ArrayList<BluetoothCall> calls = new ArrayList<>();
+        when(mMockCallInfo.getBluetoothCalls()).thenReturn(calls);
+        BluetoothCall ringingCall = createForegroundCall();
+        calls.add(ringingCall);
+        mBluetoothInCallService.onCallAdded(ringingCall);
+
+        when(ringingCall.getState()).thenReturn(Call.STATE_RINGING);
+        when(ringingCall.isIncoming()).thenReturn(true);
+        when(ringingCall.isConference()).thenReturn(false);
+        when(ringingCall.getGatewayInfo()).thenReturn(
+                new GatewayInfo(null, null, Uri.parse("tel:555-0000")));
+
+        clearInvocations(mMockBluetoothHeadset);
+        mBluetoothInCallService.listCurrentCalls();
+        verify(mMockBluetoothHeadset).clccResponse(1, 1, CALL_STATE_INCOMING, 0, false,
+                "5550000", PhoneNumberUtils.TOA_Unknown);
+        verify(mMockBluetoothHeadset).clccResponse(0, 0, 0, 0, false, null, 0);
+        verify(mMockBluetoothHeadset, times(2)).clccResponse(anyInt(),
+                anyInt(), anyInt(), anyInt(), anyBoolean(), nullable(String.class), anyInt());
+    }
+
+    @Test
+    public void testCallClccCache() throws Exception {
+        ArrayList<BluetoothCall> calls = new ArrayList<>();
+        when(mMockCallInfo.getBluetoothCalls()).thenReturn(calls);
+        BluetoothCall ringingCall = createForegroundCall();
+        calls.add(ringingCall);
+        mBluetoothInCallService.onCallAdded(ringingCall);
+
+        when(ringingCall.getState()).thenReturn(Call.STATE_RINGING);
+        when(ringingCall.isIncoming()).thenReturn(true);
+        when(ringingCall.isConference()).thenReturn(false);
+        when(ringingCall.getGatewayInfo()).thenReturn(
+                new GatewayInfo(null, null, Uri.parse("tel:5550000")));
+
+        clearInvocations(mMockBluetoothHeadset);
+        mBluetoothInCallService.listCurrentCalls();
+        verify(mMockBluetoothHeadset).clccResponse(1, 1, CALL_STATE_INCOMING, 0, false,
+                "5550000", PhoneNumberUtils.TOA_Unknown);
+
+        // Test Caching of old BluetoothCall indices in clcc
+        when(ringingCall.getState()).thenReturn(Call.STATE_ACTIVE);
+        BluetoothCall newHoldingCall = createHeldCall();
+        calls.add(0, newHoldingCall);
+        mBluetoothInCallService.onCallAdded(newHoldingCall);
+
+        when(newHoldingCall.getState()).thenReturn(Call.STATE_HOLDING);
+        when(newHoldingCall.isIncoming()).thenReturn(true);
+        when(newHoldingCall.isConference()).thenReturn(false);
+        when(newHoldingCall.getGatewayInfo()).thenReturn(
+                new GatewayInfo(null, null, Uri.parse("tel:555-0001")));
+
+        mBluetoothInCallService.listCurrentCalls();
+        verify(mMockBluetoothHeadset).clccResponse(1, 1, CALL_STATE_ACTIVE, 0, false,
+                "5550000", PhoneNumberUtils.TOA_Unknown);
+        verify(mMockBluetoothHeadset).clccResponse(2, 1, CALL_STATE_HELD, 0, false,
+                "5550001", PhoneNumberUtils.TOA_Unknown);
+        verify(mMockBluetoothHeadset, times(2)).clccResponse(0, 0, 0, 0, false, null, 0);
+    }
+
+    @Test
+    public void testAlertingCallClccResponse() throws Exception {
+        ArrayList<BluetoothCall> calls = new ArrayList<>();
+        when(mMockCallInfo.getBluetoothCalls()).thenReturn(calls);
+        BluetoothCall dialingCall = createForegroundCall();
+        calls.add(dialingCall);
+        mBluetoothInCallService.onCallAdded(dialingCall);
+
+        when(dialingCall.getState()).thenReturn(Call.STATE_DIALING);
+        when(dialingCall.isIncoming()).thenReturn(false);
+        when(dialingCall.isConference()).thenReturn(false);
+        when(dialingCall.getGatewayInfo()).thenReturn(
+                new GatewayInfo(null, null, Uri.parse("tel:555-0000")));
+
+        clearInvocations(mMockBluetoothHeadset);
+        mBluetoothInCallService.listCurrentCalls();
+        verify(mMockBluetoothHeadset).clccResponse(1, 0, CALL_STATE_ALERTING, 0, false,
+                "5550000", PhoneNumberUtils.TOA_Unknown);
+        verify(mMockBluetoothHeadset).clccResponse(0, 0, 0, 0, false, null, 0);
+        verify(mMockBluetoothHeadset, times(2)).clccResponse(anyInt(),
+                anyInt(), anyInt(), anyInt(), anyBoolean(), nullable(String.class), anyInt());
+    }
+
+    @Test
+    public void testHoldingCallClccResponse() throws Exception {
+        ArrayList<BluetoothCall> calls = new ArrayList<>();
+        when(mMockCallInfo.getBluetoothCalls()).thenReturn(calls);
+        BluetoothCall dialingCall = createForegroundCall();
+        calls.add(dialingCall);
+        mBluetoothInCallService.onCallAdded(dialingCall);
+
+        when(dialingCall.getState()).thenReturn(Call.STATE_DIALING);
+        when(dialingCall.isIncoming()).thenReturn(false);
+        when(dialingCall.isConference()).thenReturn(false);
+        when(dialingCall.getGatewayInfo()).thenReturn(
+                new GatewayInfo(null, null, Uri.parse("tel:555-0000")));
+        BluetoothCall holdingCall = createHeldCall();
+        calls.add(holdingCall);
+        mBluetoothInCallService.onCallAdded(holdingCall);
+
+        when(holdingCall.getState()).thenReturn(Call.STATE_HOLDING);
+        when(holdingCall.isIncoming()).thenReturn(true);
+        when(holdingCall.isConference()).thenReturn(false);
+        when(holdingCall.getGatewayInfo()).thenReturn(
+                new GatewayInfo(null, null, Uri.parse("tel:555-0001")));
+
+        clearInvocations(mMockBluetoothHeadset);
+        mBluetoothInCallService.listCurrentCalls();
+        verify(mMockBluetoothHeadset).clccResponse(1, 0, CALL_STATE_ALERTING, 0, false,
+                "5550000", PhoneNumberUtils.TOA_Unknown);
+        verify(mMockBluetoothHeadset).clccResponse(2, 1, CALL_STATE_HELD, 0, false,
+                "5550001", PhoneNumberUtils.TOA_Unknown);
+        verify(mMockBluetoothHeadset).clccResponse(0, 0, 0, 0, false, null, 0);
+        verify(mMockBluetoothHeadset, times(3)).clccResponse(anyInt(),
+                anyInt(), anyInt(), anyInt(), anyBoolean(), nullable(String.class), anyInt());
+    }
+
+    @Test
+    public void testListCurrentCallsImsConference() throws Exception {
+        ArrayList<BluetoothCall> calls = new ArrayList<>();
+        BluetoothCall parentCall = createActiveCall();
+        calls.add(parentCall);
+        mBluetoothInCallService.onCallAdded(parentCall);
+
+        addCallCapability(parentCall, Connection.CAPABILITY_CONFERENCE_HAS_NO_CHILDREN);
+        when(parentCall.isConference()).thenReturn(true);
+        when(parentCall.getState()).thenReturn(Call.STATE_ACTIVE);
+        when(parentCall.isIncoming()).thenReturn(true);
+        when(mMockCallInfo.getBluetoothCalls()).thenReturn(calls);
+
+        clearInvocations(mMockBluetoothHeadset);
+        mBluetoothInCallService.listCurrentCalls();
+
+        verify(mMockBluetoothHeadset).clccResponse(eq(1), eq(1), eq(CALL_STATE_ACTIVE), eq(0),
+                eq(true), (String) isNull(), eq(-1));
+        verify(mMockBluetoothHeadset).clccResponse(0, 0, 0, 0, false, null, 0);
+    }
+
+    @Test
+    public void testListCurrentCallsHeldImsCepConference() throws Exception {
+        ArrayList<BluetoothCall> calls = new ArrayList<>();
+        BluetoothCall parentCall = createHeldCall();
+        BluetoothCall childCall1 = createActiveCall();
+        BluetoothCall childCall2 = createActiveCall();
+        calls.add(parentCall);
+        calls.add(childCall1);
+        calls.add(childCall2);
+        mBluetoothInCallService.onCallAdded(parentCall);
+        mBluetoothInCallService.onCallAdded(childCall1);
+        mBluetoothInCallService.onCallAdded(childCall2);
+
+        addCallCapability(parentCall, Connection.CAPABILITY_MANAGE_CONFERENCE);
+        String parentId = parentCall.getTelecomCallId();
+        when(childCall1.getParentId()).thenReturn(parentId);
+        when(childCall2.getParentId()).thenReturn(parentId);
+
+        when(parentCall.isConference()).thenReturn(true);
+        when(parentCall.getState()).thenReturn(Call.STATE_HOLDING);
+        when(childCall1.getState()).thenReturn(Call.STATE_ACTIVE);
+        when(childCall2.getState()).thenReturn(Call.STATE_ACTIVE);
+
+        when(parentCall.isIncoming()).thenReturn(true);
+        when(mMockCallInfo.getBluetoothCalls()).thenReturn(calls);
+
+        clearInvocations(mMockBluetoothHeadset);
+        mBluetoothInCallService.listCurrentCalls();
+
+        verify(mMockBluetoothHeadset).clccResponse(eq(1), eq(0), eq(CALL_STATE_HELD), eq(0),
+                eq(true), (String) isNull(), eq(-1));
+        verify(mMockBluetoothHeadset).clccResponse(eq(2), eq(0), eq(CALL_STATE_HELD), eq(0),
+                eq(true), (String) isNull(), eq(-1));
+        verify(mMockBluetoothHeadset).clccResponse(0, 0, 0, 0, false, null, 0);
+    }
+
+    @Test
+    public void testQueryPhoneState() throws Exception {
+        BluetoothCall ringingCall = createRingingCall();
+        when(ringingCall.getHandle()).thenReturn(Uri.parse("tel:5550000"));
+
+        clearInvocations(mMockBluetoothHeadset);
+        mBluetoothInCallService.queryPhoneState();
+
+        verify(mMockBluetoothHeadset).phoneStateChanged(eq(0), eq(0), eq(CALL_STATE_INCOMING),
+                eq("5550000"), eq(PhoneNumberUtils.TOA_Unknown), nullable(String.class));
+    }
+
+    @Test
+    public void testCDMAConferenceQueryState() throws Exception {
+        BluetoothCall parentConfCall = createActiveCall();
+        final BluetoothCall confCall1 = getMockCall();
+        final BluetoothCall confCall2 = getMockCall();
+        mBluetoothInCallService.onCallAdded(confCall1);
+        mBluetoothInCallService.onCallAdded(confCall2);
+        when(parentConfCall.getHandle()).thenReturn(Uri.parse("tel:555-0000"));
+        addCallCapability(parentConfCall, Connection.CAPABILITY_SWAP_CONFERENCE);
+        removeCallCapability(parentConfCall, Connection.CAPABILITY_CONFERENCE_HAS_NO_CHILDREN);
+        when(parentConfCall.wasConferencePreviouslyMerged()).thenReturn(true);
+        when(parentConfCall.isConference()).thenReturn(true);
+        List<String> childrenIds = Arrays.asList(confCall1.getTelecomCallId(),
+                confCall2.getTelecomCallId());
+        when(parentConfCall.getChildrenIds()).thenReturn(childrenIds);
+
+        clearInvocations(mMockBluetoothHeadset);
+        mBluetoothInCallService.queryPhoneState();
+        verify(mMockBluetoothHeadset).phoneStateChanged(eq(1), eq(0), eq(CALL_STATE_IDLE),
+                eq(""), eq(128), nullable(String.class));
+    }
+
+    @Test
+    public void testProcessChldTypeReleaseHeldRinging() throws Exception {
+        BluetoothCall ringingCall = createRingingCall();
+        Log.i("BluetoothInCallService", "asdf start " + Integer.toString(ringingCall.hashCode()));
+
+        boolean didProcess = mBluetoothInCallService.processChld(CHLD_TYPE_RELEASEHELD);
+
+        verify(ringingCall).reject(eq(false), nullable(String.class));
+        Assert.assertTrue(didProcess);
+    }
+
+    @Test
+    public void testProcessChldTypeReleaseHeldHold() throws Exception {
+        BluetoothCall onHoldCall = createHeldCall();
+        boolean didProcess = mBluetoothInCallService.processChld(CHLD_TYPE_RELEASEHELD);
+
+        verify(onHoldCall).disconnect();
+        Assert.assertTrue(didProcess);
+    }
+
+    @Test
+    public void testProcessChldReleaseActiveRinging() throws Exception {
+        BluetoothCall activeCall = createActiveCall();
+        BluetoothCall ringingCall = createRingingCall();
+
+        boolean didProcess = mBluetoothInCallService.processChld(
+                CHLD_TYPE_RELEASEACTIVE_ACCEPTHELD);
+
+        verify(activeCall).disconnect();
+        verify(ringingCall).answer(any(int.class));
+        Assert.assertTrue(didProcess);
+    }
+
+    @Test
+    public void testProcessChldReleaseActiveHold() throws Exception {
+        BluetoothCall activeCall = createActiveCall();
+        BluetoothCall heldCall = createHeldCall();
+
+        boolean didProcess = mBluetoothInCallService.processChld(
+                CHLD_TYPE_RELEASEACTIVE_ACCEPTHELD);
+
+        verify(activeCall).disconnect();
+        // BluetoothCall unhold will occur as part of CallsManager auto-unholding
+        // the background BluetoothCall on its own.
+        Assert.assertTrue(didProcess);
+    }
+
+    @Test
+    public void testProcessChldHoldActiveRinging() throws Exception {
+        BluetoothCall ringingCall = createRingingCall();
+
+        boolean didProcess = mBluetoothInCallService.processChld(
+                CHLD_TYPE_HOLDACTIVE_ACCEPTHELD);
+
+        verify(ringingCall).answer(any(int.class));
+        Assert.assertTrue(didProcess);
+    }
+
+    @Test
+    public void testProcessChldHoldActiveUnhold() throws Exception {
+        BluetoothCall heldCall = createHeldCall();
+
+        boolean didProcess = mBluetoothInCallService.processChld(
+                CHLD_TYPE_HOLDACTIVE_ACCEPTHELD);
+
+        verify(heldCall).unhold();
+        Assert.assertTrue(didProcess);
+    }
+
+    @Test
+    public void testProcessChldHoldActiveHold() throws Exception {
+        BluetoothCall activeCall = createActiveCall();
+        addCallCapability(activeCall, Connection.CAPABILITY_HOLD);
+
+        boolean didProcess = mBluetoothInCallService.processChld(
+                CHLD_TYPE_HOLDACTIVE_ACCEPTHELD);
+
+        verify(activeCall).hold();
+        Assert.assertTrue(didProcess);
+    }
+
+    @Test
+    public void testProcessChldAddHeldToConfHolding() throws Exception {
+        BluetoothCall activeCall = createActiveCall();
+        addCallCapability(activeCall, Connection.CAPABILITY_MERGE_CONFERENCE);
+
+        boolean didProcess = mBluetoothInCallService.processChld(CHLD_TYPE_ADDHELDTOCONF);
+
+        verify(activeCall).mergeConference();
+        Assert.assertTrue(didProcess);
+    }
+
+    @Test
+    public void testProcessChldAddHeldToConf() throws Exception {
+        BluetoothCall activeCall = createActiveCall();
+        removeCallCapability(activeCall, Connection.CAPABILITY_MERGE_CONFERENCE);
+        BluetoothCall conferenceableCall = getMockCall();
+        ArrayList<String> conferenceableCalls = new ArrayList<>();
+        conferenceableCalls.add(conferenceableCall.getTelecomCallId());
+        mBluetoothInCallService.onCallAdded(conferenceableCall);
+
+        when(activeCall.getConferenceableCalls()).thenReturn(conferenceableCalls);
+
+        boolean didProcess = mBluetoothInCallService.processChld(CHLD_TYPE_ADDHELDTOCONF);
+
+        verify(activeCall).conference(conferenceableCall);
+        Assert.assertTrue(didProcess);
+    }
+
+    @Test
+    public void testProcessChldHoldActiveSwapConference() throws Exception {
+        // Create an active CDMA BluetoothCall with a BluetoothCall on hold
+        // and simulate a swapConference().
+        BluetoothCall parentCall = createActiveCall();
+        final BluetoothCall foregroundCall = getMockCall();
+        final BluetoothCall heldCall = createHeldCall();
+        addCallCapability(parentCall, Connection.CAPABILITY_SWAP_CONFERENCE);
+        removeCallCapability(parentCall, Connection.CAPABILITY_CONFERENCE_HAS_NO_CHILDREN);
+        when(parentCall.isConference()).thenReturn(true);
+        when(parentCall.wasConferencePreviouslyMerged()).thenReturn(false);
+        List<String> childrenIds = Arrays.asList(foregroundCall.getTelecomCallId(),
+                heldCall.getTelecomCallId());
+        when(parentCall.getChildrenIds()).thenReturn(childrenIds);
+
+        clearInvocations(mMockBluetoothHeadset);
+        boolean didProcess = mBluetoothInCallService.processChld(
+                CHLD_TYPE_HOLDACTIVE_ACCEPTHELD);
+
+        verify(parentCall).swapConference();
+        verify(mMockBluetoothHeadset).phoneStateChanged(eq(1), eq(1), eq(CALL_STATE_IDLE), eq(""),
+                eq(128), nullable(String.class));
+        Assert.assertTrue(didProcess);
+    }
+
+    // Testing the CallsManager Listener Functionality on Bluetooth
+    @Test
+    public void testOnCallAddedRinging() throws Exception {
+        BluetoothCall ringingCall = createRingingCall();
+        when(ringingCall.getHandle()).thenReturn(Uri.parse("tel:555000"));
+
+        mBluetoothInCallService.onCallAdded(ringingCall);
+
+        verify(mMockBluetoothHeadset).phoneStateChanged(eq(0), eq(0), eq(CALL_STATE_INCOMING),
+                eq("555000"), eq(PhoneNumberUtils.TOA_Unknown), nullable(String.class));
+    }
+
+    @Test
+    public void testSilentRingingCallState() throws Exception {
+        BluetoothCall ringingCall = createRingingCall();
+        when(ringingCall.isSilentRingingRequested()).thenReturn(true);
+        when(ringingCall.getHandle()).thenReturn(Uri.parse("tel:555000"));
+
+        mBluetoothInCallService.onCallAdded(ringingCall);
+
+        verify(mMockBluetoothHeadset, never()).phoneStateChanged(anyInt(), anyInt(), anyInt(),
+                anyString(), anyInt(), nullable(String.class));
+    }
+
+    @Test
+    public void testOnCallAddedCdmaActiveHold() throws Exception {
+        // BluetoothCall has been put into a CDMA "conference" with one BluetoothCall on hold.
+        BluetoothCall parentCall = createActiveCall();
+        final BluetoothCall foregroundCall = getMockCall();
+        final BluetoothCall heldCall = createHeldCall();
+        addCallCapability(parentCall, Connection.CAPABILITY_MERGE_CONFERENCE);
+        removeCallCapability(parentCall, Connection.CAPABILITY_CONFERENCE_HAS_NO_CHILDREN);
+        when(parentCall.isConference()).thenReturn(true);
+        List<String> childrenIds = Arrays.asList(foregroundCall.getTelecomCallId(),
+                heldCall.getTelecomCallId());
+        when(parentCall.getChildrenIds()).thenReturn(childrenIds);
+
+        mBluetoothInCallService.onCallAdded(parentCall);
+
+        verify(mMockBluetoothHeadset).phoneStateChanged(eq(1), eq(1), eq(CALL_STATE_IDLE),
+                eq(""), eq(128), nullable(String.class));
+    }
+
+    @Test
+    public void testOnCallRemoved() throws Exception {
+        BluetoothCall activeCall = createActiveCall();
+        mBluetoothInCallService.onCallAdded(activeCall);
+        doReturn(null).when(mMockCallInfo).getActiveCall();
+        mBluetoothInCallService.onCallRemoved(activeCall);
+
+        verify(mMockBluetoothHeadset).phoneStateChanged(eq(0), eq(0), eq(CALL_STATE_IDLE),
+                eq(""), eq(128), nullable(String.class));
+    }
+
+    @Test
+    public void testOnCallStateChangedConnectingCall() throws Exception {
+        BluetoothCall activeCall = getMockCall();
+        BluetoothCall connectingCall = getMockCall();
+        when(connectingCall.getState()).thenReturn(Call.STATE_CONNECTING);
+        ArrayList<BluetoothCall> calls = new ArrayList<>();
+        calls.add(connectingCall);
+        calls.add(activeCall);
+        mBluetoothInCallService.onCallAdded(connectingCall);
+        mBluetoothInCallService.onCallAdded(activeCall);
+        when(mMockCallInfo.getBluetoothCalls()).thenReturn(calls);
+
+        mBluetoothInCallService.getCallback(activeCall)
+                .onStateChanged(activeCall, Call.STATE_HOLDING);
+
+        verify(mMockBluetoothHeadset, never()).phoneStateChanged(anyInt(), anyInt(), anyInt(),
+                anyString(), anyInt(), nullable(String.class));
+    }
+
+    @Test
+    public void testOnCallAddedAudioProcessing() throws Exception {
+        BluetoothCall call = getMockCall();
+        when(call.getState()).thenReturn(Call.STATE_AUDIO_PROCESSING);
+        mBluetoothInCallService.onCallAdded(call);
+
+        verify(mMockBluetoothHeadset, never()).phoneStateChanged(anyInt(), anyInt(), anyInt(),
+                anyString(), anyInt(), nullable(String.class));
+    }
+
+    @Test
+    public void testOnCallStateChangedRingingToAudioProcessing() throws Exception {
+        BluetoothCall ringingCall = createRingingCall();
+        when(ringingCall.getHandle()).thenReturn(Uri.parse("tel:555000"));
+
+        mBluetoothInCallService.onCallAdded(ringingCall);
+
+        verify(mMockBluetoothHeadset).phoneStateChanged(eq(0), eq(0), eq(CALL_STATE_INCOMING),
+                eq("555000"), eq(PhoneNumberUtils.TOA_Unknown), nullable(String.class));
+
+        when(ringingCall.getState()).thenReturn(Call.STATE_AUDIO_PROCESSING);
+        when(mMockCallInfo.getRingingOrSimulatedRingingCall()).thenReturn(null);
+
+        mBluetoothInCallService.getCallback(ringingCall)
+                .onStateChanged(ringingCall, Call.STATE_AUDIO_PROCESSING);
+
+        verify(mMockBluetoothHeadset).phoneStateChanged(eq(0), eq(0), eq(CALL_STATE_IDLE),
+                eq(""), eq(128), nullable(String.class));
+    }
+
+    @Test
+    public void testOnCallStateChangedAudioProcessingToSimulatedRinging() throws Exception {
+        BluetoothCall ringingCall = createRingingCall();
+        when(ringingCall.getHandle()).thenReturn(Uri.parse("tel:555-0000"));
+        mBluetoothInCallService.onCallAdded(ringingCall);
+        mBluetoothInCallService.getCallback(ringingCall)
+                .onStateChanged(ringingCall, Call.STATE_SIMULATED_RINGING);
+
+        verify(mMockBluetoothHeadset).phoneStateChanged(eq(0), eq(0), eq(CALL_STATE_INCOMING),
+                eq("555-0000"), eq(PhoneNumberUtils.TOA_Unknown), nullable(String.class));
+    }
+
+    @Test
+    public void testOnCallStateChangedAudioProcessingToActive() throws Exception {
+        BluetoothCall activeCall = createActiveCall();
+        when(activeCall.getState()).thenReturn(Call.STATE_ACTIVE);
+        mBluetoothInCallService.onCallAdded(activeCall);
+        mBluetoothInCallService.getCallback(activeCall)
+                .onStateChanged(activeCall, Call.STATE_ACTIVE);
+
+        verify(mMockBluetoothHeadset).phoneStateChanged(eq(1), eq(0), eq(CALL_STATE_IDLE),
+                eq(""), eq(128), nullable(String.class));
+    }
+
+    @Test
+    public void testOnCallStateChangedDialing() throws Exception {
+        BluetoothCall activeCall = createActiveCall();
+
+        // make "mLastState" STATE_CONNECTING
+        BluetoothInCallService.CallStateCallback callback =
+                mBluetoothInCallService.new CallStateCallback(Call.STATE_CONNECTING);
+        mBluetoothInCallService.mCallbacks.put(
+                activeCall.getTelecomCallId(), callback);
+
+        mBluetoothInCallService.mCallbacks.get(activeCall.getTelecomCallId())
+                .onStateChanged(activeCall, Call.STATE_DIALING);
+
+        verify(mMockBluetoothHeadset, never()).phoneStateChanged(anyInt(), anyInt(), anyInt(),
+                anyString(), anyInt(), nullable(String.class));
+    }
+
+    @Test
+    public void testOnCallStateChangedAlerting() throws Exception {
+        BluetoothCall outgoingCall = createOutgoingCall();
+        mBluetoothInCallService.onCallAdded(outgoingCall);
+        mBluetoothInCallService.getCallback(outgoingCall)
+                .onStateChanged(outgoingCall, Call.STATE_DIALING);
+
+        verify(mMockBluetoothHeadset).phoneStateChanged(eq(0), eq(0), eq(CALL_STATE_DIALING),
+                eq(""), eq(128), nullable(String.class));
+        verify(mMockBluetoothHeadset).phoneStateChanged(eq(0), eq(0), eq(CALL_STATE_ALERTING),
+                eq(""), eq(128), nullable(String.class));
+    }
+
+    @Test
+    public void testOnCallStateChangedDisconnected() throws Exception {
+        BluetoothCall disconnectedCall = createDisconnectedCall();
+        doReturn(true).when(mMockCallInfo).hasOnlyDisconnectedCalls();
+        mBluetoothInCallService.onCallAdded(disconnectedCall);
+        mBluetoothInCallService.getCallback(disconnectedCall)
+                .onStateChanged(disconnectedCall, Call.STATE_DISCONNECTED);
+        verify(mMockBluetoothHeadset).phoneStateChanged(eq(0), eq(0), eq(CALL_STATE_DISCONNECTED),
+                eq(""), eq(128), nullable(String.class));
+    }
+
+    @Test
+    public void testOnCallStateChanged() throws Exception {
+        BluetoothCall ringingCall = createRingingCall();
+        when(ringingCall.getHandle()).thenReturn(Uri.parse("tel:555-0000"));
+        mBluetoothInCallService.onCallAdded(ringingCall);
+
+        verify(mMockBluetoothHeadset).phoneStateChanged(eq(0), eq(0), eq(CALL_STATE_INCOMING),
+                eq("555-0000"), eq(PhoneNumberUtils.TOA_Unknown), nullable(String.class));
+
+        //Switch to active
+        doReturn(null).when(mMockCallInfo).getRingingOrSimulatedRingingCall();
+        when(mMockCallInfo.getActiveCall()).thenReturn(ringingCall);
+
+        mBluetoothInCallService.getCallback(ringingCall)
+                .onStateChanged(ringingCall, Call.STATE_ACTIVE);
+
+        verify(mMockBluetoothHeadset).phoneStateChanged(eq(1), eq(0), eq(CALL_STATE_IDLE),
+                eq(""), eq(128), nullable(String.class));
+    }
+
+    @Test
+    public void testOnCallStateChangedGSMSwap() throws Exception {
+        BluetoothCall heldCall = createHeldCall();
+        when(heldCall.getHandle()).thenReturn(Uri.parse("tel:555-0000"));
+        mBluetoothInCallService.onCallAdded(heldCall);
+        doReturn(2).when(mMockCallInfo).getNumHeldCalls();
+
+        clearInvocations(mMockBluetoothHeadset);
+        mBluetoothInCallService.getCallback(heldCall)
+                .onStateChanged(heldCall, Call.STATE_HOLDING);
+
+        verify(mMockBluetoothHeadset, never()).phoneStateChanged(eq(0), eq(2), eq(CALL_STATE_HELD),
+                eq("5550000"), eq(PhoneNumberUtils.TOA_Unknown), nullable(String.class));
+    }
+
+    @Test
+    public void testOnParentOnChildrenChanged() throws Exception {
+        // Start with two calls that are being merged into a CDMA conference call. The
+        // onIsConferencedChanged method will be called multiple times during the call. Make sure
+        // that the bluetooth phone state is updated properly.
+        BluetoothCall parentCall = createActiveCall();
+        BluetoothCall activeCall = getMockCall();
+        BluetoothCall heldCall = createHeldCall();
+        mBluetoothInCallService.onCallAdded(parentCall);
+        mBluetoothInCallService.onCallAdded(activeCall);
+        mBluetoothInCallService.onCallAdded(heldCall);
+        String parentId = parentCall.getTelecomCallId();
+        when(activeCall.getParentId()).thenReturn(parentId);
+        when(heldCall.getParentId()).thenReturn(parentId);
+
+        ArrayList<String> calls = new ArrayList<>();
+        calls.add(activeCall.getTelecomCallId());
+
+        when(parentCall.getChildrenIds()).thenReturn(calls);
+        when(parentCall.isConference()).thenReturn(true);
+
+        removeCallCapability(parentCall, Connection.CAPABILITY_CONFERENCE_HAS_NO_CHILDREN);
+        addCallCapability(parentCall, Connection.CAPABILITY_SWAP_CONFERENCE);
+        when(parentCall.wasConferencePreviouslyMerged()).thenReturn(false);
+
+        clearInvocations(mMockBluetoothHeadset);
+        // Be sure that onIsConferencedChanged rejects spurious changes during set up of
+        // CDMA "conference"
+        mBluetoothInCallService.getCallback(activeCall).onParentChanged(activeCall);
+        verify(mMockBluetoothHeadset, never()).phoneStateChanged(anyInt(), anyInt(), anyInt(),
+                anyString(), anyInt(), nullable(String.class));
+
+        mBluetoothInCallService.getCallback(heldCall).onParentChanged(heldCall);
+        verify(mMockBluetoothHeadset, never()).phoneStateChanged(anyInt(), anyInt(), anyInt(),
+                anyString(), anyInt(), nullable(String.class));
+
+        mBluetoothInCallService.getCallback(parentCall)
+                .onChildrenChanged(
+                        parentCall,
+                        mBluetoothInCallService.getBluetoothCallsByIds(calls));
+        verify(mMockBluetoothHeadset, never()).phoneStateChanged(anyInt(), anyInt(), anyInt(),
+                anyString(), anyInt(), nullable(String.class));
+
+        calls.add(heldCall.getTelecomCallId());
+        mBluetoothInCallService.onCallAdded(heldCall);
+        mBluetoothInCallService.getCallback(parentCall)
+                .onChildrenChanged(
+                        parentCall,
+                        mBluetoothInCallService.getBluetoothCallsByIds(calls));
+        verify(mMockBluetoothHeadset).phoneStateChanged(eq(1), eq(1), eq(CALL_STATE_IDLE),
+                eq(""), eq(128), nullable(String.class));
+    }
+
+    @Test
+    public void testBluetoothAdapterReceiver() throws Exception {
+        BluetoothCall ringingCall = createRingingCall();
+        when(ringingCall.getHandle()).thenReturn(Uri.parse("tel:5550000"));
+
+        Intent intent = new Intent(BluetoothAdapter.ACTION_STATE_CHANGED);
+        intent.putExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.STATE_ON);
+        clearInvocations(mMockBluetoothHeadset);
+        mBluetoothInCallService.mBluetoothAdapterReceiver
+                = mBluetoothInCallService.new BluetoothAdapterReceiver();
+        mBluetoothInCallService.mBluetoothAdapterReceiver
+                .onReceive(mBluetoothInCallService, intent);
+
+        verify(mMockBluetoothHeadset).phoneStateChanged(eq(0), eq(0), eq(CALL_STATE_INCOMING),
+                eq("5550000"), eq(PhoneNumberUtils.TOA_Unknown), nullable(String.class));
+    }
+
+    private void addCallCapability(BluetoothCall call, int capability) {
+        when(call.can(capability)).thenReturn(true);
+    }
+
+    private void removeCallCapability(BluetoothCall call, int capability) {
+        when(call.can(capability)).thenReturn(false);
+    }
+
+    private BluetoothCall createActiveCall() {
+        BluetoothCall call = getMockCall();
+        when(mMockCallInfo.getActiveCall()).thenReturn(call);
+        return call;
+    }
+
+    private BluetoothCall createRingingCall() {
+        BluetoothCall call = getMockCall();
+        Log.i("BluetoothInCallService", "asdf creaete " + Integer.toString(call.hashCode()));
+        when(mMockCallInfo.getRingingOrSimulatedRingingCall()).thenReturn(call);
+        return call;
+    }
+
+    private BluetoothCall createHeldCall() {
+        BluetoothCall call = getMockCall();
+        when(mMockCallInfo.getHeldCall()).thenReturn(call);
+        return call;
+    }
+
+    private BluetoothCall createOutgoingCall() {
+        BluetoothCall call = getMockCall();
+        when(mMockCallInfo.getOutgoingCall()).thenReturn(call);
+        return call;
+    }
+
+    private BluetoothCall createDisconnectedCall() {
+        BluetoothCall call = getMockCall();
+        when(mMockCallInfo.getCallByState(Call.STATE_DISCONNECTED)).thenReturn(call);
+        return call;
+    }
+
+    private BluetoothCall createForegroundCall() {
+        BluetoothCall call = getMockCall();
+        when(mMockCallInfo.getForegroundCall()).thenReturn(call);
+        return call;
+    }
+
+    private static ComponentName makeQuickConnectionServiceComponentName() {
+        return new ComponentName("com.android.server.telecom.tests",
+                "com.android.server.telecom.tests.MockConnectionService");
+    }
+
+    private static PhoneAccountHandle makeQuickAccountHandle(String id) {
+        return new PhoneAccountHandle(makeQuickConnectionServiceComponentName(), id,
+                Binder.getCallingUserHandle());
+    }
+
+    private PhoneAccount.Builder makeQuickAccountBuilder(String id, int idx) {
+        return new PhoneAccount.Builder(makeQuickAccountHandle(id), "label" + idx);
+    }
+
+    private PhoneAccount makeQuickAccount(String id, int idx) {
+        return makeQuickAccountBuilder(id, idx)
+                .setAddress(Uri.parse(TEST_ACCOUNT_ADDRESS + idx))
+                .setSubscriptionAddress(Uri.parse("tel:555-000" + idx))
+                .setCapabilities(idx)
+                .setShortDescription("desc" + idx)
+                .setIsEnabled(true)
+                .build();
+    }
+
+    private BluetoothCall getMockCall() {
+        BluetoothCall call = mock(com.android.bluetooth.telephony.BluetoothCall.class);
+        String uuid = UUID.randomUUID().toString();
+        when(call.getTelecomCallId()).thenReturn(uuid);
+        return call;
+    }
+}