Merge "Revert "Remove second action on tBTA_DM_PM_SPEC"" into tm-qpr-dev
diff --git a/TEST_MAPPING b/TEST_MAPPING
index 65f2fef..c1b9e29 100644
--- a/TEST_MAPPING
+++ b/TEST_MAPPING
@@ -1,75 +1,220 @@
 {
-  "postsubmit" : [
+  "presubmit": [
+    // android_test targets
     {
-      "name" : "bluetooth_test_common"
+      "name": "CtsBluetoothTestCases"
     },
     {
-      "name" : "bluetoothtbd_test"
+      "name": "BluetoothInstrumentationTests"
     },
     {
-      "name" : "net_test_audio_a2dp_hw"
+      "name": "GoogleBluetoothInstrumentationTests"
     },
     {
-      "name" : "net_test_avrcp"
+      "name": "FrameworkBluetoothTests"
     },
     {
-      "name" : "net_test_btcore"
+      "name": "ServiceBluetoothTests"
+    },
+    // device only tests
+    // Broken
+    //{
+    //  "name": "bluetooth-test-audio-hal-interface"
+    //},
+    {
+      "name": "net_test_audio_a2dp_hw"
     },
     {
-      "name" : "net_test_btif"
+      "name": "net_test_audio_hearing_aid_hw"
+    },
+    // TODO (b/267212763)
+    // {
+    //   "name": "net_test_bluetooth"
+    // },
+    // {
+    //   "name": "net_test_bta"
+    // },
+    // {
+    //   "name": "net_test_btif"
+    // },
+    {
+      "name": "net_test_btif_hf_client_service"
     },
     {
-      "name" : "net_test_btif_profile_queue"
+      "name": "net_test_btif_profile_queue"
     },
     {
-      "name" : "net_test_btpackets"
+      "name": "net_test_device"
     },
     {
-      "name" : "net_test_device"
+      "name": "net_test_gatt_conn_multiplexing"
+    },
+    // TODO (b/267212763)
+    // {
+    //   "name": "net_test_hci"
+    // },
+    {
+      "name": "net_test_hf_client_add_record"
+    },
+    // TODO (b/267212763)
+    // {
+    //   "name": "net_test_stack"
+    // },
+    {
+      "name": "net_test_stack_ad_parser"
     },
     {
-      "name" : "net_test_eatt"
+      "name": "net_test_stack_multi_adv"
+    },
+    // go/a-unit-tests tests (unit_test: true)
+    // Thoses test run on the host in the CI automatically.
+    // Run the one that are available on the device on the
+    // device as well
+    // TODO (b/267212763)
+    // {
+    //   "name": "bluetooth_csis_test"
+    // },
+    {
+      "name": "bluetooth_flatbuffer_tests"
+    },
+    // TODO (b/267212763)
+    // {
+    //   "name": "bluetooth_groups_test"
+    // },
+    // {
+    //   "name": "bluetooth_has_test"
+    // },
+    {
+      "name": "bluetooth_hh_test"
+    },
+    // TODO (b/267212763)
+    // {
+    //   "name": "bluetooth_le_audio_client_test"
+    // },
+    {
+      "name": "bluetooth_le_audio_test"
     },
     {
-      "name" : "net_test_hci"
+      "name": "bluetooth_packet_parser_test"
+    },
+    // TODO (b/267212763)
+    // {
+    //   "name": "bluetooth_test_broadcaster"
+    // },
+    // {
+    //   "name": "bluetooth_test_broadcaster_state_machine"
+    // },
+    {
+      "name": "bluetooth_test_common"
     },
     {
-      "name" : "net_test_performance"
+      "name": "bluetooth_test_gd_unit"
     },
     {
-      "name" : "net_test_stack"
+      "name": "bluetooth_test_sdp"
+    },
+    // TODO (b/267212763)
+    // {
+    //   "name": "bluetooth_vc_test"
+    // },
+    // {
+    //   "name": "bluetoothtbd_test"
+    // },
+    // {
+    //   "name": "bt_host_test_bta"
+    // },
+    {
+      "name": "libaptx_enc_tests"
     },
     {
-      "name" : "net_test_stack_ad_parser"
+      "name": "libaptxhd_enc_tests"
     },
     {
-      "name" : "net_test_stack_multi_adv"
+      "name": "net_test_avrcp"
     },
     {
-      "name" : "net_test_stack_rfcomm"
+      "name": "net_test_btcore"
     },
     {
-      "name" : "net_test_stack_smp"
+      "name": "net_test_btif_config_cache"
+    },
+    // TODO (b/267212763)
+    // {
+    //   "name": "net_test_btif_hh"
+    // },
+    {
+      "name": "net_test_btif_rc"
+    },
+    // TODO (b/267212763)
+    // {
+    //   "name": "net_test_btif_stack"
+    // },
+    {
+      "name": "net_test_btm_iso"
     },
     {
-      "name" : "net_test_types"
-    }
-  ],
-  "presubmit" : [
-    {
-      "name" : "net_test_hf_client_add_record"
+      "name": "net_test_btpackets"
     },
     {
-      "name" : "net_test_btif_hf_client_service"
+      "name": "net_test_eatt"
     },
     {
-      "name" : "libaptx_enc_tests"
+      "name": "net_test_hci_fragmenter_native"
     },
     {
-      "name" : "libaptxhd_enc_tests"
+      "name": "net_test_main_shim"
     },
     {
-      "name" : "net_test_stack_btm"
+      "name": "net_test_osi"
+    },
+    {
+      "name": "net_test_performance"
+    },
+    {
+      "name": "net_test_stack_a2dp_native"
+    },
+    {
+      "name": "net_test_stack_acl"
+    },
+    {
+      "name": "net_test_stack_avdtp"
+    },
+    // TODO (b/267212763)
+    // {
+    //   "name": "net_test_stack_btm"
+    // },
+    {
+      "name": "net_test_stack_btu"
+    },
+    {
+      "name": "net_test_stack_gatt"
+    },
+    {
+      "name": "net_test_stack_gatt_native"
+    },
+    {
+      "name": "net_test_stack_gatt_sr_hash_native"
+    },
+    {
+      "name": "net_test_stack_hci"
+    },
+    {
+      "name": "net_test_stack_hid"
+    },
+    {
+      "name": "net_test_stack_l2cap"
+    },
+    {
+      "name": "net_test_stack_rfcomm"
+    },
+    {
+      "name": "net_test_stack_sdp"
+    },
+    {
+      "name": "net_test_stack_smp"
+    },
+    {
+      "name": "net_test_types"
     }
   ]
 }
diff --git a/android/app/jni/com_android_bluetooth_hfpclient.cpp b/android/app/jni/com_android_bluetooth_hfpclient.cpp
index a36e19c..da4dff9 100644
--- a/android/app/jni/com_android_bluetooth_hfpclient.cpp
+++ b/android/app/jni/com_android_bluetooth_hfpclient.cpp
@@ -879,6 +879,37 @@
   return (status == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE;
 }
 
+static jboolean sendAndroidAtNative(JNIEnv* env, jobject object,
+                                    jbyteArray address, jstring arg_str) {
+  std::shared_lock<std::shared_mutex> lock(interface_mutex);
+  if (!sBluetoothHfpClientInterface) return JNI_FALSE;
+
+  jbyte* addr = env->GetByteArrayElements(address, NULL);
+  if (!addr) {
+    jniThrowIOException(env, EINVAL);
+    return JNI_FALSE;
+  }
+
+  const char* arg = NULL;
+  if (arg_str != NULL) {
+    arg = env->GetStringUTFChars(arg_str, NULL);
+  }
+
+  bt_status_t status = sBluetoothHfpClientInterface->send_android_at(
+      (const RawAddress*)addr, arg);
+
+  if (status != BT_STATUS_SUCCESS) {
+    ALOGE("FAILED to control volume, status: %d", status);
+  }
+
+  if (arg != NULL) {
+    env->ReleaseStringUTFChars(arg_str, arg);
+  }
+
+  env->ReleaseByteArrayElements(address, addr, 0);
+  return (status == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE;
+}
+
 static JNINativeMethod sMethods[] = {
     {"classInitNative", "()V", (void*)classInitNative},
     {"initializeNative", "()V", (void*)initializeNative},
@@ -903,6 +934,8 @@
     {"requestLastVoiceTagNumberNative", "([B)Z",
      (void*)requestLastVoiceTagNumberNative},
     {"sendATCmdNative", "([BIIILjava/lang/String;)Z", (void*)sendATCmdNative},
+    {"sendAndroidAtNative", "([BLjava/lang/String;)Z",
+     (void*)sendAndroidAtNative},
 };
 
 int register_com_android_bluetooth_hfpclient(JNIEnv* env) {
diff --git a/android/app/res/layout/bluetooth_map_settings.xml b/android/app/res/layout/bluetooth_map_settings.xml
index f256a02..0c7e3b3 100644
--- a/android/app/res/layout/bluetooth_map_settings.xml
+++ b/android/app/res/layout/bluetooth_map_settings.xml
@@ -17,7 +17,7 @@
 -->
 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:tools="http://schemas.android.com/tools"
-    android:id="@+id/bluetooth_map_settings_liniar_layout"
+    android:id="@+id/bluetooth_map_settings_linear_layout"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
     android:orientation="vertical"
diff --git a/android/app/res/values-ky/strings.xml b/android/app/res/values-ky/strings.xml
index 954556f..014f3c5 100644
--- a/android/app/res/values-ky/strings.xml
+++ b/android/app/res/values-ky/strings.xml
@@ -121,7 +121,7 @@
     <string name="bluetooth_map_settings_intro" msgid="4748160773998753325">"Bluetooth аркылуу бөлүшө турган аккаунттарды тандаңыз. Туташкан сайын аккаунттарга кирүүгө уруксат берип турушуңуз керек."</string>
     <string name="bluetooth_map_settings_count" msgid="183013143617807702">"Калган көзөнөктөр:"</string>
     <string name="bluetooth_map_settings_app_icon" msgid="3501432663809664982">"Колдонмонун сүрөтчөсү"</string>
-    <string name="bluetooth_map_settings_title" msgid="4226030082708590023">"Bluetooth билдирүү бөлүшүү жөндөөлөрү"</string>
+    <string name="bluetooth_map_settings_title" msgid="4226030082708590023">"Bluetooth билдирүү бөлүшүү параметрлери"</string>
     <string name="bluetooth_map_settings_no_account_slots_left" msgid="755024228476065757">"Аккаунт тандалбай жатат: 0 орун калды"</string>
     <string name="bluetooth_connected" msgid="5687474377090799447">"Bluetooth аудио туташты"</string>
     <string name="bluetooth_disconnected" msgid="6841396291728343534">"Bluetooth аудио ажыратылды"</string>
diff --git a/android/app/res/values/config.xml b/android/app/res/values/config.xml
index 913c77f..1e062b5 100644
--- a/android/app/res/values/config.xml
+++ b/android/app/res/values/config.xml
@@ -33,6 +33,34 @@
     <integer name="gatt_low_power_min_interval">80</integer>
     <integer name="gatt_low_power_max_interval">100</integer>
 
+    <!-- min/max connection intervals/latencies for companion devices -->
+    <!-- Primary companion -->
+    <integer name="gatt_high_priority_min_interval_primary">6</integer>
+    <integer name="gatt_high_priority_max_interval_primary">8</integer>
+    <integer name="gatt_high_priority_latency_primary">45</integer>
+
+    <integer name="gatt_balanced_priority_min_interval_primary">6</integer>
+    <integer name="gatt_balanced_priority_max_interval_primary">10</integer>
+    <integer name="gatt_balanced_priority_latency_primary">120</integer>
+
+    <integer name="gatt_low_power_min_interval_primary">8</integer>
+    <integer name="gatt_low_power_max_interval_primary">10</integer>
+    <integer name="gatt_low_power_latency_primary">150</integer>
+
+    <!-- Secondary companion -->
+    <integer name="gatt_high_priority_min_interval_secondary">6</integer>
+    <integer name="gatt_high_priority_max_interval_secondary">6</integer>
+    <integer name="gatt_high_priority_latency_secondary">0</integer>
+
+    <integer name="gatt_balanced_priority_min_interval_secondary">12</integer>
+    <integer name="gatt_balanced_priority_max_interval_secondary">12</integer>
+    <integer name="gatt_balanced_priority_latency_secondary">30</integer>
+
+    <integer name="gatt_low_power_min_interval_secondary">80</integer>
+    <integer name="gatt_low_power_max_interval_secondary">100</integer>
+    <integer name="gatt_low_power_latency_secondary">15</integer>
+    <!-- ============================================================ -->
+
     <!-- Specifies latency parameters for high priority, balanced and low power
          GATT configurations. These values represents the number of packets a
          peripheral device is allowed to skip. -->
diff --git a/android/app/res/values/styles.xml b/android/app/res/values/styles.xml
index 91f402d..ea94695 100644
--- a/android/app/res/values/styles.xml
+++ b/android/app/res/values/styles.xml
@@ -31,7 +31,7 @@
         <item name="android:paddingTop">10dip</item>
         <item name="android:textAlignment">viewStart</item>
         <item name="android:textAppearance">@android:style/TextAppearance.Material.Body1</item>
-        <item name="android:textColor">@*android:color/secondary_text_default_material_light</item>
+        <item name="android:textColor">?android:attr/textColorSecondary</item>
     </style>
 
     <style name="file_transfer_item_content">
@@ -42,7 +42,7 @@
         <item name="android:paddingBottom">10dip</item>
         <item name="android:textAlignment">viewStart</item>
         <item name="android:textAppearance">@android:style/TextAppearance.Material.Subhead</item>
-        <item name="android:textColor">@*android:color/primary_text_default_material_light</item>
+        <item name="android:textColor">?android:attr/textColorPrimary</item>
     </style>
 
     <style name="dialog" parent="android:style/Theme.Material.Light.Dialog.Alert" />
diff --git a/android/app/src/com/android/bluetooth/BluetoothMethodProxy.java b/android/app/src/com/android/bluetooth/BluetoothMethodProxy.java
index d1ea7d4..00cf686 100644
--- a/android/app/src/com/android/bluetooth/BluetoothMethodProxy.java
+++ b/android/app/src/com/android/bluetooth/BluetoothMethodProxy.java
@@ -179,6 +179,13 @@
     }
 
     /**
+     * Proxies {@link Handler#sendEmptyMessage(int)}}.
+     */
+    public boolean handlerSendEmptyMessage(Handler handler, final int what) {
+        return handler.sendEmptyMessage(what);
+    }
+
+    /**
      * Proxies {@link HeaderSet#getHeader}.
      */
     public Object getHeader(HeaderSet headerSet, int headerId) throws IOException {
diff --git a/android/app/src/com/android/bluetooth/Utils.java b/android/app/src/com/android/bluetooth/Utils.java
index dc2c79a..b22b3aa 100644
--- a/android/app/src/com/android/bluetooth/Utils.java
+++ b/android/app/src/com/android/bluetooth/Utils.java
@@ -1008,7 +1008,8 @@
         }
         values.put(Telephony.Sms.ERROR_CODE, 0);
 
-        return 1 == context.getContentResolver().update(uri, values, null, null);
+        return 1 == BluetoothMethodProxy.getInstance().contentResolverUpdate(
+                context.getContentResolver(), uri, values, null, null);
     }
 
     /**
diff --git a/android/app/src/com/android/bluetooth/a2dp/A2dpService.java b/android/app/src/com/android/bluetooth/a2dp/A2dpService.java
index 9bc4fad..7ca6166 100644
--- a/android/app/src/com/android/bluetooth/a2dp/A2dpService.java
+++ b/android/app/src/com/android/bluetooth/a2dp/A2dpService.java
@@ -106,7 +106,6 @@
     boolean mA2dpOffloadEnabled = false;
 
     private BroadcastReceiver mBondStateChangedReceiver;
-    private BroadcastReceiver mConnectionStateChangedReceiver;
 
     public static boolean isEnabled() {
         return BluetoothProperties.isProfileA2dpSourceEnabled().orElse(false);
@@ -169,10 +168,6 @@
         filter.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED);
         mBondStateChangedReceiver = new BondStateChangedReceiver();
         registerReceiver(mBondStateChangedReceiver, filter);
-        filter = new IntentFilter();
-        filter.addAction(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED);
-        mConnectionStateChangedReceiver = new ConnectionStateChangedReceiver();
-        registerReceiver(mConnectionStateChangedReceiver, filter);
 
         // Step 8: Mark service as started
         setA2dpService(this);
@@ -208,8 +203,6 @@
         setA2dpService(null);
 
         // Step 7: Unregister broadcast receivers
-        unregisterReceiver(mConnectionStateChangedReceiver);
-        mConnectionStateChangedReceiver = null;
         unregisterReceiver(mBondStateChangedReceiver);
         mBondStateChangedReceiver = null;
 
@@ -1242,7 +1235,7 @@
         }
     }
 
-    private void connectionStateChanged(BluetoothDevice device, int fromState, int toState) {
+    void connectionStateChanged(BluetoothDevice device, int fromState, int toState) {
         if ((device == null) || (fromState == toState)) {
             return;
         }
@@ -1269,27 +1262,6 @@
     }
 
     /**
-     * Receiver for processing device connection state changes.
-     *
-     * <ul>
-     * <li> Update codec support per device when device is (re)connected
-     * <li> Delete the state machine instance if the device is disconnected and unbond
-     * </ul>
-     */
-    private class ConnectionStateChangedReceiver extends BroadcastReceiver {
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            if (!BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED.equals(intent.getAction())) {
-                return;
-            }
-            BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
-            int toState = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, -1);
-            int fromState = intent.getIntExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, -1);
-            connectionStateChanged(device, fromState, toState);
-        }
-    }
-
-    /**
      * Retrieves the most recently connected device in the A2DP connected devices list.
      */
     public BluetoothDevice getFallbackDevice() {
diff --git a/android/app/src/com/android/bluetooth/a2dp/A2dpStateMachine.java b/android/app/src/com/android/bluetooth/a2dp/A2dpStateMachine.java
index 9e7323f..11d33ad 100644
--- a/android/app/src/com/android/bluetooth/a2dp/A2dpStateMachine.java
+++ b/android/app/src/com/android/bluetooth/a2dp/A2dpStateMachine.java
@@ -701,6 +701,7 @@
         intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mDevice);
         intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT
                         | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
+        mA2dpService.connectionStateChanged(mDevice, prevState, newState);
         mA2dpService.sendBroadcast(intent, BLUETOOTH_CONNECT,
                 Utils.getTempAllowlistBroadcastOptions());
     }
diff --git a/android/app/src/com/android/bluetooth/audio_util/MediaPlayerList.java b/android/app/src/com/android/bluetooth/audio_util/MediaPlayerList.java
index 1eebe99..97df3ce 100644
--- a/android/app/src/com/android/bluetooth/audio_util/MediaPlayerList.java
+++ b/android/app/src/com/android/bluetooth/audio_util/MediaPlayerList.java
@@ -31,6 +31,7 @@
 import android.media.session.PlaybackState;
 import android.os.Handler;
 import android.os.Looper;
+import android.os.SystemProperties;
 import android.text.TextUtils;
 import android.util.Log;
 import android.view.KeyEvent;
@@ -146,10 +147,45 @@
                 mContext.getMainExecutor(), mMediaKeyEventSessionChangedListener);
     }
 
+    private void constructCurrentPlayers() {
+        // Construct the list of current players
+        d("Initializing list of current media players");
+        List<android.media.session.MediaController> controllers =
+                mMediaSessionManager.getActiveSessions(null);
+
+        for (android.media.session.MediaController controller : controllers) {
+            addMediaPlayer(controller);
+        }
+
+        // If there were any active players and we don't already have one due to the Media
+        // Framework Callbacks then set the highest priority one to active
+        if (mActivePlayerId == 0 && mMediaPlayers.size() > 0) {
+            String packageName = mMediaSessionManager.getMediaKeyEventSessionPackageName();
+            if (!TextUtils.isEmpty(packageName) && haveMediaPlayer(packageName)) {
+                Log.i(TAG, "Set active player to MediaKeyEvent session = " + packageName);
+                setActivePlayer(mMediaPlayerIds.get(packageName));
+            } else {
+                Log.i(TAG, "Set active player to first default");
+                setActivePlayer(1);
+            }
+        }
+    }
+
     public void init(MediaUpdateCallback callback) {
         Log.v(TAG, "Initializing MediaPlayerList");
         mCallback = callback;
 
+        if (!SystemProperties.getBoolean("bluetooth.avrcp.browsable_media_player.enabled", true)) {
+            // Allow to disable BrowsablePlayerConnector with systemproperties.
+            // This is useful when for watches because:
+            //   1. It is not a regular use case
+            //   2. Registering to all players is a very loading task
+
+            Log.i(TAG, "init: without Browsable Player");
+            constructCurrentPlayers();
+            return;
+        }
+
         // Build the list of browsable players and afterwards, build the list of media players
         Intent intent = new Intent(android.service.media.MediaBrowserService.SERVICE_INTERFACE);
         List<ResolveInfo> playerList =
@@ -185,27 +221,7 @@
                             });
                 }
 
-                // Construct the list of current players
-                d("Initializing list of current media players");
-                List<android.media.session.MediaController> controllers =
-                        mMediaSessionManager.getActiveSessions(null);
-
-                for (android.media.session.MediaController controller : controllers) {
-                    addMediaPlayer(controller);
-                }
-
-                // If there were any active players and we don't already have one due to the Media
-                // Framework Callbacks then set the highest priority one to active
-                if (mActivePlayerId == 0 && mMediaPlayers.size() > 0) {
-                    String packageName = mMediaSessionManager.getMediaKeyEventSessionPackageName();
-                    if (!TextUtils.isEmpty(packageName) && haveMediaPlayer(packageName)) {
-                        Log.i(TAG, "Set active player to MediaKeyEvent session = " + packageName);
-                        setActivePlayer(mMediaPlayerIds.get(packageName));
-                    } else {
-                        Log.i(TAG, "Set active player to first default");
-                        setActivePlayer(1);
-                    }
-                }
+                constructCurrentPlayers();
             });
     }
 
diff --git a/android/app/src/com/android/bluetooth/avrcp/AvrcpVolumeManager.java b/android/app/src/com/android/bluetooth/avrcp/AvrcpVolumeManager.java
index d0cfa58..5cf76bd 100644
--- a/android/app/src/com/android/bluetooth/avrcp/AvrcpVolumeManager.java
+++ b/android/app/src/com/android/bluetooth/avrcp/AvrcpVolumeManager.java
@@ -29,6 +29,7 @@
 import android.util.Log;
 
 import com.android.bluetooth.audio_util.BTAudioEventLogger;
+import com.android.internal.annotations.VisibleForTesting;
 
 import java.util.HashMap;
 import java.util.Map;
@@ -42,7 +43,9 @@
     private static final String VOLUME_MAP = "bluetooth_volume_map";
     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;
+
+    @VisibleForTesting
+    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;
diff --git a/android/app/src/com/android/bluetooth/btservice/ActiveDeviceManager.java b/android/app/src/com/android/bluetooth/btservice/ActiveDeviceManager.java
index f043394..03160d4 100644
--- a/android/app/src/com/android/bluetooth/btservice/ActiveDeviceManager.java
+++ b/android/app/src/com/android/bluetooth/btservice/ActiveDeviceManager.java
@@ -26,6 +26,7 @@
 import android.bluetooth.BluetoothHearingAid;
 import android.bluetooth.BluetoothLeAudio;
 import android.bluetooth.BluetoothProfile;
+import android.bluetooth.BluetoothSinkAudioPolicy;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
@@ -689,10 +690,14 @@
         if (headsetService == null) {
             return;
         }
-        if (!headsetService.setActiveDevice(device)) {
-            return;
+        BluetoothSinkAudioPolicy audioPolicy = headsetService.getHfpCallAudioPolicy(device);
+        if (audioPolicy == null || audioPolicy.getActiveDevicePolicyAfterConnection()
+                != BluetoothSinkAudioPolicy.POLICY_NOT_ALLOWED) {
+            if (!headsetService.setActiveDevice(device)) {
+                return;
+            }
+            mHfpActiveDevice = device;
         }
-        mHfpActiveDevice = device;
     }
 
     private void setHearingAidActiveDevice(BluetoothDevice device) {
diff --git a/android/app/src/com/android/bluetooth/btservice/AdapterProperties.java b/android/app/src/com/android/bluetooth/btservice/AdapterProperties.java
index ad7739c..77b2b4e 100644
--- a/android/app/src/com/android/bluetooth/btservice/AdapterProperties.java
+++ b/android/app/src/com/android/bluetooth/btservice/AdapterProperties.java
@@ -55,6 +55,8 @@
 import com.android.bluetooth.Utils;
 import com.android.bluetooth.btservice.RemoteDevices.DeviceProperties;
 
+import com.google.common.collect.EvictingQueue;
+
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.util.ArrayList;
@@ -91,6 +93,9 @@
     private CopyOnWriteArrayList<BluetoothDevice> mBondedDevices =
             new CopyOnWriteArrayList<BluetoothDevice>();
 
+    private static final int SCAN_MODE_CHANGES_MAX_SIZE = 10;
+    private EvictingQueue<String> mScanModeChanges;
+
     private int mProfilesConnecting, mProfilesConnected, mProfilesDisconnecting;
     private final HashMap<Integer, Pair<Integer, Integer>> mProfileConnectionState =
             new HashMap<>();
@@ -200,6 +205,7 @@
     AdapterProperties(AdapterService service) {
         mService = service;
         mAdapter = BluetoothAdapter.getDefaultAdapter();
+        mScanModeChanges = EvictingQueue.create(SCAN_MODE_CHANGES_MAX_SIZE);
         invalidateBluetoothCaches();
     }
 
@@ -254,6 +260,7 @@
         }
         mService = null;
         mBondedDevices.clear();
+        mScanModeChanges.clear();
         invalidateBluetoothCaches();
     }
 
@@ -389,15 +396,28 @@
     /**
      * Set the local adapter property - scanMode
      *
-     * @param scanMode the ScanMode to set
+     * @param scanMode the ScanMode to set, valid values are: {
+     *     BluetoothAdapter.SCAN_MODE_NONE,
+     *     BluetoothAdapter.SCAN_MODE_CONNECTABLE,
+     *     BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE,
+     *   }
      */
     boolean setScanMode(int scanMode) {
+        addScanChangeLog(scanMode);
         synchronized (mObject) {
             return mService.setAdapterPropertyNative(AbstractionLayer.BT_PROPERTY_ADAPTER_SCAN_MODE,
-                    Utils.intToByteArray(scanMode));
+                    Utils.intToByteArray(AdapterService.convertScanModeToHal(scanMode)));
         }
     }
 
+    private void addScanChangeLog(int scanMode) {
+        String time = Utils.getLocalTimeString();
+        String uidPid = Utils.getUidPidString();
+        String scanModeString = dumpScanMode(scanMode);
+
+        mScanModeChanges.add(time + " (" + uidPid + ") " + scanModeString);
+    }
+
     /**
      * @return the mUuids
      */
@@ -694,13 +714,14 @@
         if (state == BluetoothProfile.STATE_CONNECTING) {
             BluetoothStatsLog.write(BluetoothStatsLog.BLUETOOTH_DEVICE_NAME_REPORTED,
                     mService.getMetricId(device), device.getName());
+            MetricsLogger.getInstance().logSanitizedBluetoothDeviceName(device.getName());
         }
         Log.d(TAG,
                 "PROFILE_CONNECTION_STATE_CHANGE: profile=" + profile + ", device=" + device + ", "
                         + prevState + " -> " + state);
         BluetoothStatsLog.write(BluetoothStatsLog.BLUETOOTH_CONNECTION_STATE_CHANGED, state,
                 0 /* deprecated */, profile, mService.obfuscateAddress(device),
-                mService.getMetricId(device), 0);
+                mService.getMetricId(device), 0, -1);
 
         if (!isNormalStateTransition(prevState, state)) {
             Log.w(TAG,
@@ -1073,7 +1094,7 @@
             mProfilesConnecting = 0;
             mProfilesDisconnecting = 0;
             // adapterPropertyChangedCallback has already been received.  Set the scan mode.
-            setScanMode(AbstractionLayer.BT_SCAN_MODE_CONNECTABLE);
+            setScanMode(BluetoothAdapter.SCAN_MODE_CONNECTABLE);
             // This keeps NV up-to date on first-boot after flash.
             setDiscoverableTimeout(mDiscoverableTimeout);
         }
@@ -1083,7 +1104,7 @@
         // Sequence BLE_ON to STATE_OFF - that is _complete_ OFF state.
         debugLog("onBleDisable");
         // Set the scan_mode to NONE (no incoming connections).
-        setScanMode(AbstractionLayer.BT_SCAN_MODE_NONE);
+        setScanMode(BluetoothAdapter.SCAN_MODE_NONE);
     }
 
     void discoveryStateChangeCallback(int state) {
@@ -1136,6 +1157,12 @@
             }
         }
         writer.println(sb.toString());
+
+        writer.println("  " + "Scan Mode Changes:");
+        for (String log : mScanModeChanges) {
+            writer.println("    " + log);
+        }
+
     }
 
     private String dumpDeviceType(int deviceType) {
diff --git a/android/app/src/com/android/bluetooth/btservice/AdapterService.java b/android/app/src/com/android/bluetooth/btservice/AdapterService.java
index c5fb78b..7e36914 100644
--- a/android/app/src/com/android/bluetooth/btservice/AdapterService.java
+++ b/android/app/src/com/android/bluetooth/btservice/AdapterService.java
@@ -50,6 +50,7 @@
 import android.bluetooth.BluetoothProtoEnums;
 import android.bluetooth.BluetoothSap;
 import android.bluetooth.BluetoothServerSocket;
+import android.bluetooth.BluetoothSinkAudioPolicy;
 import android.bluetooth.BluetoothSocket;
 import android.bluetooth.BluetoothStatusCodes;
 import android.bluetooth.BluetoothUuid;
@@ -300,6 +301,7 @@
     private ActiveDeviceManager mActiveDeviceManager;
     private DatabaseManager mDatabaseManager;
     private SilenceDeviceManager mSilenceDeviceManager;
+    private CompanionManager mBtCompanionManager;
     private AppOpsManager mAppOps;
 
     private BluetoothSocketManagerBinder mBluetoothSocketManagerBinder;
@@ -441,6 +443,7 @@
                         getAdapterPropertyNative(AbstractionLayer.BT_PROPERTY_LOCAL_IO_CAPS_BLE);
                         getAdapterPropertyNative(AbstractionLayer.BT_PROPERTY_DYNAMIC_AUDIO_BUFFER);
                         mAdapterStateMachine.sendMessage(AdapterState.BREDR_STARTED);
+                        mBtCompanionManager.loadCompanionInfo();
                     }
                     break;
                 case BluetoothAdapter.STATE_OFF:
@@ -542,6 +545,8 @@
                 Looper.getMainLooper());
         mSilenceDeviceManager.start();
 
+        mBtCompanionManager = new CompanionManager(this, new ServiceFactory());
+
         mBluetoothSocketManagerBinder = new BluetoothSocketManagerBinder(this);
 
         mActivityAttributionService = new ActivityAttributionService();
@@ -712,7 +717,7 @@
     void stopProfileServices() {
         // Make sure to stop classic background tasks now
         cancelDiscoveryNative();
-        mAdapterProperties.setScanMode(AbstractionLayer.BT_SCAN_MODE_NONE);
+        mAdapterProperties.setScanMode(BluetoothAdapter.SCAN_MODE_NONE);
 
         Class[] supportedProfileServices = Config.getSupportedProfiles();
         // TODO(b/228875190): GATT is assumed supported. If we support no profiles then just move on
@@ -1557,6 +1562,32 @@
     }
 
     /**
+     *  Get an metadata of given device and key
+     *
+     *  @param device Bluetooth device
+     *  @param key Metadata key
+     *  @param value Metadata value
+     *  @return if metadata is set successfully
+     */
+    public boolean setMetadata(BluetoothDevice device, int key, byte[] value) {
+        if (value == null || value.length > BluetoothDevice.METADATA_MAX_LENGTH) {
+            return false;
+        }
+        return mDatabaseManager.setCustomMeta(device, key, value);
+    }
+
+    /**
+     *  Get an metadata of given device and key
+     *
+     *  @param device Bluetooth device
+     *  @param key Metadata key
+     *  @return value of given device and key combination
+     */
+    public byte[] getMetadata(BluetoothDevice device, int key) {
+        return mDatabaseManager.getCustomMeta(device, key);
+    }
+
+    /**
      * Handlers for incoming service calls
      */
     private AdapterServiceBinder mBinder;
@@ -1967,7 +1998,7 @@
             }
             enforceBluetoothPrivilegedPermission(service);
 
-            return service.mAdapterProperties.setScanMode(convertScanModeToHal(mode))
+            return service.mAdapterProperties.setScanMode(mode)
                     ? BluetoothStatusCodes.SUCCESS : BluetoothStatusCodes.ERROR_UNKNOWN;
         }
 
@@ -3171,6 +3202,10 @@
                 service.mBluetoothKeystoreService.factoryReset();
             }
 
+            if (service.mBtCompanionManager != null) {
+                service.mBtCompanionManager.factoryReset();
+            }
+
             return service.factoryResetNative();
         }
 
@@ -3636,6 +3671,76 @@
         }
 
         @Override
+        public void isRequestAudioPolicyAsSinkSupported(BluetoothDevice device,
+                AttributionSource source, SynchronousResultReceiver receiver) {
+            try {
+                receiver.send(isRequestAudioPolicyAsSinkSupported(device, source));
+            } catch (RuntimeException e) {
+                receiver.propagateException(e);
+            }
+        }
+        private int isRequestAudioPolicyAsSinkSupported(BluetoothDevice device,
+                AttributionSource source) {
+            AdapterService service = getService();
+            if (service == null
+                    || !callerIsSystemOrActiveOrManagedUser(service, TAG,
+                        "isRequestAudioPolicyAsSinkSupported")
+                    || !Utils.checkConnectPermissionForDataDelivery(service, source, TAG)) {
+                return BluetoothStatusCodes.FEATURE_NOT_CONFIGURED;
+            }
+            enforceBluetoothPrivilegedPermission(service);
+            return service.isRequestAudioPolicyAsSinkSupported(device);
+        }
+
+        @Override
+        public void requestAudioPolicyAsSink(BluetoothDevice device,
+                BluetoothSinkAudioPolicy policies, AttributionSource source,
+                SynchronousResultReceiver receiver) {
+            try {
+                receiver.send(requestAudioPolicyAsSink(device, policies, source));
+            } catch (RuntimeException e) {
+                receiver.propagateException(e);
+            }
+        }
+        private int requestAudioPolicyAsSink(BluetoothDevice device,
+                BluetoothSinkAudioPolicy policies, AttributionSource source) {
+            AdapterService service = getService();
+            if (service == null) {
+                return BluetoothStatusCodes.ERROR_BLUETOOTH_NOT_ENABLED;
+            } else if (!callerIsSystemOrActiveOrManagedUser(service,
+                    TAG, "requestAudioPolicyAsSink")) {
+                return BluetoothStatusCodes.ERROR_BLUETOOTH_NOT_ALLOWED;
+            } else if (!Utils.checkConnectPermissionForDataDelivery(
+                    service, source, TAG)) {
+                return BluetoothStatusCodes.ERROR_MISSING_BLUETOOTH_CONNECT_PERMISSION;
+            }
+            enforceBluetoothPrivilegedPermission(service);
+            return service.requestAudioPolicyAsSink(device, policies);
+        }
+
+        @Override
+        public void getRequestedAudioPolicyAsSink(BluetoothDevice device,
+                AttributionSource source, SynchronousResultReceiver receiver) {
+            try {
+                receiver.send(getRequestedAudioPolicyAsSink(device, source));
+            } catch (RuntimeException e) {
+                receiver.propagateException(e);
+            }
+        }
+        private BluetoothSinkAudioPolicy getRequestedAudioPolicyAsSink(BluetoothDevice device,
+                AttributionSource source) {
+            AdapterService service = getService();
+            if (service == null
+                    || !callerIsSystemOrActiveOrManagedUser(service,
+                            TAG, "getRequestedAudioPolicyAsSink")
+                    || !Utils.checkConnectPermissionForDataDelivery(service, source, TAG)) {
+                return null;
+            }
+            enforceBluetoothPrivilegedPermission(service);
+            return service.getRequestedAudioPolicyAsSink(device);
+        }
+
+        @Override
         public void requestActivityInfo(IBluetoothActivityEnergyInfoListener listener,
                     AttributionSource source) {
             BluetoothActivityEnergyInfo info = reportActivityInfo(source);
@@ -4863,7 +4968,7 @@
                 source.getUid(), source.getPackageName(), deviceAddress);
     }
 
-    private static int convertScanModeToHal(int mode) {
+    static int convertScanModeToHal(int mode) {
         switch (mode) {
             case BluetoothAdapter.SCAN_MODE_NONE:
                 return AbstractionLayer.BT_SCAN_MODE_NONE;
@@ -5458,6 +5563,84 @@
         return getMetricIdNative(Utils.getByteAddress(device));
     }
 
+    public CompanionManager getCompanionManager() {
+        return mBtCompanionManager;
+    }
+
+    /**
+     *  Call for the AdapterService receives bond state change
+     *
+     *  @param device Bluetooth device
+     *  @param state bond state
+     */
+    public void onBondStateChanged(BluetoothDevice device, int state) {
+        if (mBtCompanionManager != null) {
+            mBtCompanionManager.onBondStateChanged(device, state);
+        }
+    }
+
+    /**
+     * Get audio policy feature support status
+     *
+     * @param device Bluetooth device to be checked for audio policy support
+     * @return int status of the remote support for audio policy feature
+     */
+    public int isRequestAudioPolicyAsSinkSupported(BluetoothDevice device) {
+        if (mHeadsetClientService != null) {
+            return mHeadsetClientService.getAudioPolicyRemoteSupported(device);
+        } else {
+            Log.e(TAG, "No audio transport connected");
+            return BluetoothStatusCodes.FEATURE_NOT_CONFIGURED;
+        }
+    }
+
+    /**
+     * Set audio policy for remote device
+     *
+     * @param device Bluetooth device to be set policy for
+     * @return int result status for requestAudioPolicyAsSink API
+     */
+    public int requestAudioPolicyAsSink(BluetoothDevice device, BluetoothSinkAudioPolicy policies) {
+        DeviceProperties deviceProp = mRemoteDevices.getDeviceProperties(device);
+        if (deviceProp == null) {
+            return BluetoothStatusCodes.ERROR_DEVICE_NOT_BONDED;
+        }
+
+        if (mHeadsetClientService != null) {
+            if (isRequestAudioPolicyAsSinkSupported(device)
+                    != BluetoothStatusCodes.FEATURE_SUPPORTED) {
+                throw new UnsupportedOperationException(
+                        "Request Audio Policy As Sink not supported");
+            }
+            deviceProp.setHfAudioPolicyForRemoteAg(policies);
+            mHeadsetClientService.setAudioPolicy(device, policies);
+            return BluetoothStatusCodes.SUCCESS;
+        } else {
+            Log.e(TAG, "HeadsetClient not connected");
+            return BluetoothStatusCodes.ERROR_PROFILE_NOT_CONNECTED;
+        }
+    }
+
+    /**
+     * Get audio policy for remote device
+     *
+     * @param device Bluetooth device to be set policy for
+     * @return {@link BluetoothSinkAudioPolicy} policy stored for the device
+     */
+    public BluetoothSinkAudioPolicy getRequestedAudioPolicyAsSink(BluetoothDevice device) {
+        DeviceProperties deviceProp = mRemoteDevices.getDeviceProperties(device);
+        if (deviceProp == null) {
+            return null;
+        }
+
+        if (mHeadsetClientService != null) {
+            return deviceProp.getHfAudioPolicyForRemoteAg();
+        } else {
+            Log.e(TAG, "HeadsetClient not connected");
+            return null;
+        }
+    }
+
     /**
      *  Allow audio low latency
      *
diff --git a/android/app/src/com/android/bluetooth/btservice/BluetoothSocketManagerBinder.java b/android/app/src/com/android/bluetooth/btservice/BluetoothSocketManagerBinder.java
index 2187a91..13a9f4c 100644
--- a/android/app/src/com/android/bluetooth/btservice/BluetoothSocketManagerBinder.java
+++ b/android/app/src/com/android/bluetooth/btservice/BluetoothSocketManagerBinder.java
@@ -17,6 +17,7 @@
 package com.android.bluetooth.btservice;
 
 import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothSocket;
 import android.bluetooth.IBluetoothSocketManager;
 import android.os.Binder;
 import android.os.ParcelFileDescriptor;
@@ -49,13 +50,17 @@
             return null;
         }
 
-        return marshalFd(mService.connectSocketNative(
-            Utils.getBytesFromAddress(device.getAddress()),
-            type,
-            Utils.uuidToByteArray(uuid),
-            port,
-            flag,
-            Binder.getCallingUid()));
+        return marshalFd(
+                mService.connectSocketNative(
+                        Utils.getBytesFromAddress(
+                                type == BluetoothSocket.TYPE_L2CAP_LE
+                                        ? device.getAddress()
+                                        : mService.getIdentityAddress(device.getAddress())),
+                        type,
+                        Utils.uuidToByteArray(uuid),
+                        port,
+                        flag,
+                        Binder.getCallingUid()));
     }
 
     @Override
diff --git a/android/app/src/com/android/bluetooth/btservice/BondStateMachine.java b/android/app/src/com/android/bluetooth/btservice/BondStateMachine.java
index 713545f..c6633da 100644
--- a/android/app/src/com/android/bluetooth/btservice/BondStateMachine.java
+++ b/android/app/src/com/android/bluetooth/btservice/BondStateMachine.java
@@ -470,6 +470,7 @@
         if (newState == BluetoothDevice.BOND_NONE) {
             intent.putExtra(BluetoothDevice.EXTRA_UNBOND_REASON, reason);
         }
+        mAdapterService.onBondStateChanged(device, newState);
         mAdapterService.sendBroadcastAsUser(intent, UserHandle.ALL, BLUETOOTH_CONNECT,
                 Utils.getTempAllowlistBroadcastOptions());
         infoLog("Bond State Change Intent:" + device + " " + state2str(oldState) + " => "
diff --git a/android/app/src/com/android/bluetooth/btservice/CompanionManager.java b/android/app/src/com/android/bluetooth/btservice/CompanionManager.java
new file mode 100644
index 0000000..64aae78
--- /dev/null
+++ b/android/app/src/com/android/bluetooth/btservice/CompanionManager.java
@@ -0,0 +1,403 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.bluetooth.btservice;
+
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothGatt;
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.os.SystemProperties;
+import android.util.Log;
+
+import androidx.annotation.VisibleForTesting;
+
+import com.android.bluetooth.R;
+
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+  A CompanionManager to specify parameters between companion devices and regular devices.
+
+  1.  A paired device is recognized as a companion device if its METADATA_SOFTWARE_VERSION is
+      set to BluetoothDevice.COMPANION_TYPE_PRIMARY or BluetoothDevice.COMPANION_TYPE_SECONDARY.
+  2.  Only can have one companion device at a time.
+  3.  Remove bond does not remove the companion device record.
+  4.  Factory reset Bluetooth removes the companion device.
+  5.  Companion device has individual GATT connection parameters.
+*/
+
+public class CompanionManager {
+    private static final String TAG = "BluetoothCompanionManager";
+
+    private BluetoothDevice mCompanionDevice;
+    private int mCompanionType;
+
+    private final int[] mGattConnHighPrimary;
+    private final int[] mGattConnBalancePrimary;
+    private final int[] mGattConnLowPrimary;
+    private final int[] mGattConnHighSecondary;
+    private final int[] mGattConnBalanceSecondary;
+    private final int[] mGattConnLowSecondary;
+    private final int[] mGattConnHighDefault;
+    private final int[] mGattConnBalanceDefault;
+    private final int[] mGattConnLowDefault;
+
+    @VisibleForTesting static final int COMPANION_TYPE_NONE      = 0;
+    @VisibleForTesting static final int COMPANION_TYPE_PRIMARY   = 1;
+    @VisibleForTesting static final int COMPANION_TYPE_SECONDARY = 2;
+
+    public static final int GATT_CONN_INTERVAL_MIN = 0;
+    public static final int GATT_CONN_INTERVAL_MAX = 1;
+    public static final int GATT_CONN_LATENCY      = 2;
+
+    @VisibleForTesting static final String COMPANION_INFO = "bluetooth_companion_info";
+    @VisibleForTesting static final String COMPANION_DEVICE_KEY = "companion_device";
+    @VisibleForTesting static final String COMPANION_TYPE_KEY = "companion_type";
+
+    static final String PROPERTY_HIGH_MIN_INTERVAL = "bluetooth.gatt.high_priority.min_interval";
+    static final String PROPERTY_HIGH_MAX_INTERVAL = "bluetooth.gatt.high_priority.max_interval";
+    static final String PROPERTY_HIGH_LATENCY = "bluetooth.gatt.high_priority.latency";
+    static final String PROPERTY_BALANCED_MIN_INTERVAL =
+            "bluetooth.gatt.balanced_priority.min_interval";
+    static final String PROPERTY_BALANCED_MAX_INTERVAL =
+            "bluetooth.gatt.balanced_priority.max_interval";
+    static final String PROPERTY_BALANCED_LATENCY = "bluetooth.gatt.balanced_priority.latency";
+    static final String PROPERTY_LOW_MIN_INTERVAL = "bluetooth.gatt.low_priority_min.interval";
+    static final String PROPERTY_LOW_MAX_INTERVAL = "bluetooth.gatt.low_priority_max.interval";
+    static final String PROPERTY_LOW_LATENCY = "bluetooth.gatt.low_priority.latency";
+    static final String PROPERTY_SUFFIX_PRIMARY = ".primary";
+    static final String PROPERTY_SUFFIX_SECONDARY = ".secondary";
+
+    private final AdapterService mAdapterService;
+    private final BluetoothAdapter mAdapter = BluetoothAdapter.getDefaultAdapter();
+    private final Set<BluetoothDevice> mMetadataListeningDevices = new HashSet<>();
+
+    public CompanionManager(AdapterService service, ServiceFactory factory) {
+        mAdapterService = service;
+
+        mGattConnHighDefault = new int[] {
+                getGattConfig(PROPERTY_HIGH_MIN_INTERVAL,
+                        R.integer.gatt_high_priority_min_interval),
+                getGattConfig(PROPERTY_HIGH_MAX_INTERVAL,
+                        R.integer.gatt_high_priority_max_interval),
+                getGattConfig(PROPERTY_HIGH_LATENCY,
+                        R.integer.gatt_high_priority_latency)};
+        mGattConnBalanceDefault = new int[] {
+                getGattConfig(PROPERTY_BALANCED_MIN_INTERVAL,
+                        R.integer.gatt_balanced_priority_min_interval),
+                getGattConfig(PROPERTY_BALANCED_MAX_INTERVAL,
+                        R.integer.gatt_balanced_priority_max_interval),
+                getGattConfig(PROPERTY_BALANCED_LATENCY,
+                        R.integer.gatt_balanced_priority_latency)};
+        mGattConnLowDefault = new int[] {
+                getGattConfig(PROPERTY_LOW_MIN_INTERVAL, R.integer.gatt_low_power_min_interval),
+                getGattConfig(PROPERTY_LOW_MAX_INTERVAL, R.integer.gatt_low_power_max_interval),
+                getGattConfig(PROPERTY_LOW_LATENCY, R.integer.gatt_low_power_latency)};
+
+        mGattConnHighPrimary = new int[] {
+                getGattConfig(PROPERTY_HIGH_MIN_INTERVAL + PROPERTY_SUFFIX_PRIMARY,
+                        R.integer.gatt_high_priority_min_interval_primary),
+                getGattConfig(PROPERTY_HIGH_MAX_INTERVAL + PROPERTY_SUFFIX_PRIMARY,
+                        R.integer.gatt_high_priority_max_interval_primary),
+                getGattConfig(PROPERTY_HIGH_LATENCY + PROPERTY_SUFFIX_PRIMARY,
+                        R.integer.gatt_high_priority_latency_primary)};
+        mGattConnBalancePrimary = new int[] {
+                getGattConfig(PROPERTY_BALANCED_MIN_INTERVAL + PROPERTY_SUFFIX_PRIMARY,
+                        R.integer.gatt_balanced_priority_min_interval_primary),
+                getGattConfig(PROPERTY_BALANCED_MAX_INTERVAL + PROPERTY_SUFFIX_PRIMARY,
+                        R.integer.gatt_balanced_priority_max_interval_primary),
+                getGattConfig(PROPERTY_BALANCED_LATENCY + PROPERTY_SUFFIX_PRIMARY,
+                        R.integer.gatt_balanced_priority_latency_primary)};
+        mGattConnLowPrimary = new int[] {
+                getGattConfig(PROPERTY_LOW_MIN_INTERVAL + PROPERTY_SUFFIX_PRIMARY,
+                        R.integer.gatt_low_power_min_interval_primary),
+                getGattConfig(PROPERTY_LOW_MAX_INTERVAL + PROPERTY_SUFFIX_PRIMARY,
+                        R.integer.gatt_low_power_max_interval_primary),
+                getGattConfig(PROPERTY_LOW_LATENCY + PROPERTY_SUFFIX_PRIMARY,
+                        R.integer.gatt_low_power_latency_primary)};
+
+        mGattConnHighSecondary = new int[] {
+                getGattConfig(PROPERTY_HIGH_MIN_INTERVAL + PROPERTY_SUFFIX_SECONDARY,
+                        R.integer.gatt_high_priority_min_interval_secondary),
+                getGattConfig(PROPERTY_HIGH_MAX_INTERVAL + PROPERTY_SUFFIX_SECONDARY,
+                        R.integer.gatt_high_priority_max_interval_secondary),
+                getGattConfig(PROPERTY_HIGH_LATENCY + PROPERTY_SUFFIX_SECONDARY,
+                        R.integer.gatt_high_priority_latency_secondary)};
+        mGattConnBalanceSecondary = new int[] {
+                getGattConfig(PROPERTY_BALANCED_MIN_INTERVAL + PROPERTY_SUFFIX_SECONDARY,
+                        R.integer.gatt_balanced_priority_min_interval_secondary),
+                getGattConfig(PROPERTY_BALANCED_MAX_INTERVAL + PROPERTY_SUFFIX_SECONDARY,
+                        R.integer.gatt_balanced_priority_max_interval_secondary),
+                getGattConfig(PROPERTY_BALANCED_LATENCY + PROPERTY_SUFFIX_SECONDARY,
+                        R.integer.gatt_balanced_priority_latency_secondary)};
+        mGattConnLowSecondary = new int[] {
+                getGattConfig(PROPERTY_LOW_MIN_INTERVAL + PROPERTY_SUFFIX_SECONDARY,
+                        R.integer.gatt_low_power_min_interval_secondary),
+                getGattConfig(PROPERTY_LOW_MAX_INTERVAL + PROPERTY_SUFFIX_SECONDARY,
+                        R.integer.gatt_low_power_max_interval_secondary),
+                getGattConfig(PROPERTY_LOW_LATENCY + PROPERTY_SUFFIX_SECONDARY,
+                        R.integer.gatt_low_power_latency_secondary)};
+    }
+
+    private int getGattConfig(String property, int resId) {
+        return SystemProperties.getInt(property, mAdapterService.getResources().getInteger(resId));
+    }
+
+    void loadCompanionInfo() {
+        synchronized (mMetadataListeningDevices) {
+            String address = getCompanionPreferences().getString(COMPANION_DEVICE_KEY, "");
+
+            try {
+                mCompanionDevice = mAdapter.getRemoteDevice(address);
+                mCompanionType = getCompanionPreferences().getInt(
+                        COMPANION_TYPE_KEY, COMPANION_TYPE_NONE);
+            } catch (IllegalArgumentException e) {
+                mCompanionDevice = null;
+                mCompanionType = COMPANION_TYPE_NONE;
+            }
+        }
+
+        if (mCompanionDevice == null) {
+            // We don't have any companion phone registered, try look from the bonded devices
+            for (BluetoothDevice device : mAdapter.getBondedDevices()) {
+                byte[] metadata = mAdapterService.getMetadata(device,
+                        BluetoothDevice.METADATA_SOFTWARE_VERSION);
+                if (metadata == null) {
+                    continue;
+                }
+                String valueStr = new String(metadata);
+                if ((valueStr.equals(BluetoothDevice.COMPANION_TYPE_PRIMARY)
+                        || valueStr.equals(BluetoothDevice.COMPANION_TYPE_SECONDARY))) {
+                    // found the companion device, store and unregister all listeners
+                    Log.i(TAG, "Found companion device from the database!");
+                    setCompanionDevice(device, valueStr);
+                    break;
+                }
+                registerMetadataListener(device);
+            }
+        }
+        Log.i(TAG, "Companion device is " + mCompanionDevice + ", type=" + mCompanionType);
+    }
+
+    final BluetoothAdapter.OnMetadataChangedListener mMetadataListener =
+            new BluetoothAdapter.OnMetadataChangedListener() {
+                @Override
+                public void onMetadataChanged(BluetoothDevice device, int key, byte[] value) {
+                    String valueStr = new String(value);
+                    Log.d(TAG, String.format("Metadata updated in Device %s: %d = %s.", device,
+                            key, value == null ? null : valueStr));
+                    if (key == BluetoothDevice.METADATA_SOFTWARE_VERSION
+                            && (valueStr.equals(BluetoothDevice.COMPANION_TYPE_PRIMARY)
+                            || valueStr.equals(BluetoothDevice.COMPANION_TYPE_SECONDARY))) {
+                        setCompanionDevice(device, valueStr);
+                    }
+                }
+            };
+
+    private void setCompanionDevice(BluetoothDevice companionDevice, String type) {
+        synchronized (mMetadataListeningDevices) {
+            Log.i(TAG, "setCompanionDevice: " + companionDevice + ", type=" + type);
+            mCompanionDevice = companionDevice;
+            mCompanionType = type.equals(BluetoothDevice.COMPANION_TYPE_PRIMARY)
+                    ? COMPANION_TYPE_PRIMARY : COMPANION_TYPE_SECONDARY;
+
+            // unregister all metadata listeners
+            for (BluetoothDevice device : mMetadataListeningDevices) {
+                try {
+                    mAdapter.removeOnMetadataChangedListener(device, mMetadataListener);
+                } catch (IllegalArgumentException e) {
+                    Log.e(TAG, "failed to unregister metadata listener for "
+                            + device + " " + e);
+                }
+            }
+            mMetadataListeningDevices.clear();
+
+            SharedPreferences.Editor pref = getCompanionPreferences().edit();
+            pref.putString(COMPANION_DEVICE_KEY, mCompanionDevice.getAddress());
+            pref.putInt(COMPANION_TYPE_KEY, mCompanionType);
+            pref.apply();
+        }
+    }
+
+    private SharedPreferences getCompanionPreferences() {
+        return mAdapterService.getSharedPreferences(COMPANION_INFO, Context.MODE_PRIVATE);
+    }
+
+    /**
+     * Bond state change event from the AdapterService
+     *
+     * @param device the Bluetooth device
+     * @param state the new Bluetooth bond state of the device
+     */
+    public void onBondStateChanged(BluetoothDevice device, int state) {
+        synchronized (mMetadataListeningDevices) {
+            if (mCompanionDevice != null) {
+                // We already have the companion device, do not care bond state change any more.
+                return;
+            }
+            switch (state) {
+                case BluetoothDevice.BOND_BONDING:
+                    registerMetadataListener(device);
+                    break;
+                case BluetoothDevice.BOND_NONE:
+                    removeMetadataListener(device);
+                    break;
+                default:
+                    break;
+            }
+        }
+    }
+
+    private void registerMetadataListener(BluetoothDevice device) {
+        synchronized (mMetadataListeningDevices) {
+            Log.d(TAG, "register metadata listener: " + device);
+            try {
+                mAdapter.addOnMetadataChangedListener(
+                        device, mAdapterService.getMainExecutor(), mMetadataListener);
+            } catch (IllegalArgumentException e) {
+                Log.e(TAG, "failed to register metadata listener for "
+                        + device + " " + e);
+            }
+            mMetadataListeningDevices.add(device);
+        }
+    }
+
+    private void removeMetadataListener(BluetoothDevice device) {
+        synchronized (mMetadataListeningDevices) {
+            if (!mMetadataListeningDevices.contains(device)) return;
+
+            Log.d(TAG, "remove metadata listener: " + device);
+            try {
+                mAdapter.removeOnMetadataChangedListener(device, mMetadataListener);
+            } catch (IllegalArgumentException e) {
+                Log.e(TAG, "failed to unregister metadata listener for "
+                        + device + " " + e);
+            }
+            mMetadataListeningDevices.remove(device);
+        }
+    }
+
+
+    /**
+     * Method to get the stored companion device
+     *
+     * @return the companion Bluetooth device
+     */
+    public BluetoothDevice getCompanionDevice() {
+        return mCompanionDevice;
+    }
+
+    /**
+     * Method to check whether it is a companion device
+     *
+     * @param address the address of the device
+     * @return true if the address is a companion device, otherwise false
+     */
+    public boolean isCompanionDevice(String address) {
+        try {
+            return isCompanionDevice(mAdapter.getRemoteDevice(address));
+        } catch (IllegalArgumentException e) {
+            return false;
+        }
+    }
+
+    /**
+     * Method to check whether it is a companion device
+     *
+     * @param device the Bluetooth device
+     * @return true if the device is a companion device, otherwise false
+     */
+    public boolean isCompanionDevice(BluetoothDevice device) {
+        if (device == null) return false;
+        return device.equals(mCompanionDevice);
+    }
+
+    /**
+     * Method to reset the stored companion info
+     */
+    public void factoryReset() {
+        synchronized (mMetadataListeningDevices) {
+            mCompanionDevice = null;
+            mCompanionType = COMPANION_TYPE_NONE;
+
+            SharedPreferences.Editor pref = getCompanionPreferences().edit();
+            pref.remove(COMPANION_DEVICE_KEY);
+            pref.remove(COMPANION_TYPE_KEY);
+            pref.apply();
+        }
+    }
+
+    /**
+     * Gets the GATT connection parameters of the device
+     *
+     * @param address the address of the Bluetooth device
+     * @param type type of the parameter, can be GATT_CONN_INTERVAL_MIN, GATT_CONN_INTERVAL_MAX
+     * or GATT_CONN_LATENCY
+     * @param priority the priority of the connection, can be
+     * BluetoothGatt.CONNECTION_PRIORITY_HIGH, BluetoothGatt.CONNECTION_PRIORITY_LOW_POWER or
+     * BluetoothGatt.CONNECTION_PRIORITY_BALANCED
+     * @return the connection parameter in integer
+     */
+    public int getGattConnParameters(String address, int type, int priority) {
+        int companionType = isCompanionDevice(address) ? mCompanionType : COMPANION_TYPE_NONE;
+        int parameter;
+        switch (companionType) {
+            case COMPANION_TYPE_PRIMARY:
+                parameter = getGattConnParameterPrimary(type, priority);
+                break;
+            case COMPANION_TYPE_SECONDARY:
+                parameter = getGattConnParameterSecondary(type, priority);
+                break;
+            default:
+                parameter = getGattConnParameterDefault(type, priority);
+                break;
+        }
+        return parameter;
+    }
+
+    private int getGattConnParameterPrimary(int type, int priority) {
+        switch (priority) {
+            case BluetoothGatt.CONNECTION_PRIORITY_HIGH:
+                return mGattConnHighPrimary[type];
+            case BluetoothGatt.CONNECTION_PRIORITY_LOW_POWER:
+                return mGattConnLowPrimary[type];
+        }
+        return mGattConnBalancePrimary[type];
+    }
+
+    private int getGattConnParameterSecondary(int type, int priority) {
+        switch (priority) {
+            case BluetoothGatt.CONNECTION_PRIORITY_HIGH:
+                return mGattConnHighSecondary[type];
+            case BluetoothGatt.CONNECTION_PRIORITY_LOW_POWER:
+                return mGattConnLowSecondary[type];
+        }
+        return mGattConnBalanceSecondary[type];
+    }
+
+    private int getGattConnParameterDefault(int type, int mode) {
+        switch (mode) {
+            case BluetoothGatt.CONNECTION_PRIORITY_HIGH:
+                return mGattConnHighDefault[type];
+            case BluetoothGatt.CONNECTION_PRIORITY_LOW_POWER:
+                return mGattConnLowDefault[type];
+        }
+        return mGattConnBalanceDefault[type];
+    }
+}
diff --git a/android/app/src/com/android/bluetooth/btservice/DeviceBloomfilterGenerator.java b/android/app/src/com/android/bluetooth/btservice/DeviceBloomfilterGenerator.java
new file mode 100644
index 0000000..fcadc29
--- /dev/null
+++ b/android/app/src/com/android/bluetooth/btservice/DeviceBloomfilterGenerator.java
@@ -0,0 +1,203 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.bluetooth.btservice;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+
+/**
+ * Class to generate a default Device Bloomfilter
+ */
+public class DeviceBloomfilterGenerator {
+    public static final String BLOOM_FILTER_DEFAULT =
+            "01070000013b23cef3cd0e063e5dd15a"
+            + "1a3f14b8a2d6974ab2e5a2d37f2efa97"
+            + "10e526000ae8728c41445c9a1387c123"
+            + "dc63675c0b8da3d365cde65b9edf153d"
+            + "12d3a1ecdf9b78b3b2f86bc294ccf7ea"
+            + "f650e1fa767bcaad3b61520125d38364"
+            + "4cb480d820122ad455e7e422e9bc51fd"
+            + "c442628ed66154916130be24212e4f44"
+            + "efed5a6bc9b7064fa7b2efe86dd4e801"
+            + "72c65b972a7524b370a2bca955429385"
+            + "a405671d87ead027e7cb4080713dd0bf"
+            + "6b440048b14d3d55d41d1f4497143b98"
+            + "3b939c0bb686f026aa3c42df96bcab6a"
+            + "542f9b8b62cd30e76ac744b4a185c7aa"
+            + "3433dd714e95c0f268449c56e904a4d4"
+            + "8bf9d242f99fbbe3e259ee97cbafd1f7"
+            + "65306274f54f67b7dfbf2423b8ef8fd6"
+            + "ee3fca0e2217bb351bd3347c610fa3f7"
+            + "7e5ec2d7b931f657d61784fe59e2516c"
+            + "9c8f8f4bcffe0a247ed16d93347a818a"
+            + "a798320da96f05dbee4c5cf31661121f"
+            + "e0e6e6ce9b657df24a7b8e0b8a34e443"
+            + "79dcb270d856a5431a2b6416464e9327"
+            + "22bb423d6f4e620c33a5f2b1d02f9a8f"
+            + "521f7b49947284af61c0dfc4d0e64ccd"
+            + "1df147ae9999fdf9a3538c0ad83ee7d5"
+            + "d066f0f4759bcb5c46b7c3fd57697b88"
+            + "5a8f82a77d927617a6ea077ff352ff18"
+            + "1ab520f1cc0d73f688b55c37761e0be7"
+            + "e543e33accef44fa212f2b746a239bfd"
+            + "b139e39439ffa76919419b79e4505d17"
+            + "0b412ace6f21c6c34bff54eb2e16429a"
+            + "cbc691d3c17aa9e0590e3d4d3acc9349"
+            + "0d67e7cfbf4dfa9aeaadfead7770af8f"
+            + "fb827e2376d30d027cc949712dbce0f7"
+            + "3bb193dbf9201a59632ba6daf11b8a92"
+            + "6bea34175531df805afd72792c9ebada"
+            + "823a0b677c4ee75d745806a98a4dd754"
+            + "2f5e5f665e3280385a416e94ccd8eb88"
+            + "a949d2daaee0f11c238fed182e1c234c"
+            + "c4021288a0f7f31807b735ea96e3e4fe"
+            + "66d07484a2336971d6ac0e6a79967116"
+            + "cc9eac2921ea51ec822fcc5f90c0f6b4"
+            + "96845542dfe8fbd6299e7d2af66ce423"
+            + "a7346d1af0f5bca2f261e9a247e214a5"
+            + "aecf8d19f2e368d7f0ea9699bc313ccf"
+            + "ccdb8f759d9bf4ee42a49cda2021eba7"
+            + "71add727d5d8cf35143fd4ffc595ee83"
+            + "6d293113cd9ddcea69c009c6f94e4605"
+            + "f96efe314bbad0e5fa449b35e24d1121"
+            + "c1cbcfbacd3bad9759bb5028033ebfc4"
+            + "8ac390285e7b41195fa4c4512cb48bd7"
+            + "2787f52eb8d260e6a9e2b02d32d57c04"
+            + "fd236b933cb365d2ebc99c30fb972ac1"
+            + "fcd1afcf4087c4d612eb1fefc9a03e78"
+            + "de594bc828e3b1aaddb46b7f3d2e0916"
+            + "8c324e1059e2d6b8535c34e4ab05bc13"
+            + "adaf2d75db9d9c8f0891541b573f5782"
+            + "a543f214b34bfdad7373bd6703d4b1ae"
+            + "3793910ad3ccceebda27f714df06c63a"
+            + "94ac90a3044f9c9494ffdc7cb050a750"
+            + "0d647262b98a7f74378f525ba945ddc7"
+            + "a9926b67c553b37ca370ac9016e6b34d"
+            + "5966a6571bc62dfb0fea8906ce4e0739"
+            + "3ff747c356734343bcdc2362baa97e2a"
+            + "eb37244316ac6d0f91e0c6dada3f19d2"
+            + "21f4f309db772bcca9128ee94b11dca5"
+            + "58e678deabfd506f3acf269c0cdd4d66"
+            + "951567041afd88acfa5afff876f024bb"
+            + "7e72db189f9f9e77782aef5f565ceb12"
+            + "1b9cea8200c797bf46f9e086bb6c45c7"
+            + "2a8a7f521523158d005ba13f72866e4b"
+            + "281abb7c01bc16e666b3d9c49ac4ef8a"
+            + "d45b4a63a2d8318cfd6387fa59fc9c1e"
+            + "a7f753d4a2a12a9e802ecaf24ded9075"
+            + "4c476cb2d1f547ecabe06180471cf5b2"
+            + "18099f595df1f96eb9bb301da60853cb"
+            + "3db3ee16dc09f5c167632cb742f9b631"
+            + "35ebf72aaad9f8fbd44f15d9ba77b7c2"
+            + "9bc2873378bd433c0d27258dbf095c75"
+            + "dd7b4ed56b2db02331a5b3817473c6b5"
+            + "b3228749bf1fa16cd88903276b12ac9d"
+            + "949042a04c364725f27644fd082e8e1b"
+            + "2bcaa9ac54b170a67862fd3325e09896"
+            + "bdf499eb1a933d255bb7bd58011379a6"
+            + "20da77c55a7484c0aa19681a8fb71b8b"
+            + "5f10efa2cbaf518a071651b899961dcd"
+            + "953d695f8187a0a3249db6afc81492f1"
+            + "03de215ca8af5c62bda273e0c46f6d4e"
+            + "0f4f8025cc52532b7f4c3ef61769326e"
+            + "841c9c775294ddd2aeba8b7fcb7ce8c4"
+            + "66472f0551c905db5d6c7901e51ba435"
+            + "9a42ed96fb170e2b6e933440de8f4b7e"
+            + "7832696368f9c61c46840db11f5e411a"
+            + "d64e2aa300cfd0768fb919f9434f53b0"
+            + "02e9316c926fe1498ea8b8bfb1f87943"
+            + "6ad5633e004878d47f3102ec93f56737"
+            + "c7a4f0f723f402726f4419f1805b9bcb"
+            + "c25e5536a1356f30756580aa919dcc5f"
+            + "1491bdf6e4639eb56b246e6aa846f721"
+            + "59684a64b413264678df77a633c1c448"
+            + "0ceddd569bf36b61f9fb492ef7ad14dd"
+            + "6b5460e9b267ec1aecf078e3ad180e06"
+            + "35b86c65a0ae236c4cdaed5e48b33525"
+            + "856a70eac296a1744932ee9a91b45821"
+            + "fd7dcaa3e47ef274ed4d34ca440c71bf"
+            + "d9cee7e20b85993d61acb72acaeaf969"
+            + "4f6480d157c4a062a2abf5df87835df8"
+            + "cafbb79aa8f2f2b6b8eae630ff25bdc9"
+            + "5e4df395ce7626882cbb26de3a13d98a"
+            + "b5b7f3bde86c39ab85844cfabaaa9d5e"
+            + "8d6bb9f1c6d644f20c8bf59960efad58"
+            + "ec5071353c1fa7da4a681a650fcbeb8f"
+            + "9cc48389e5e8c0734d2d77126904addb"
+            + "6cf4166e1f4cb964d658a3bba2a2fb33"
+            + "0c16fc9b83f54774b826b38ca96019c8"
+            + "49705809b8656d61044ee19ade74e59f"
+            + "6bce4d414a11bdc1bb76cd096d88dd9d"
+            + "83ca5813bdfa7cc4cd6cefbd090c928d"
+            + "a944ca2012500c510f9462056ee6d99c"
+            + "a76467f9999f4ecf62f7ad1c98eb5914"
+            + "283c354c3fae5b527204983915648b2d"
+            + "4ccda53623e4e1c4eae633f5ed3f18d1"
+            + "3c25d41487014bcc72f3fb69cfe8cdc2"
+            + "d157f899b935ee1501bd8131cd2bdcc1"
+            + "b64c5425562fa6491d24a53047c8720a"
+            + "736be13878c14326035d4b45f319f249"
+            + "cab39e4332aa2e309d264be67c4fb376"
+            + "d3a9698df50497276792384787a9fd1e"
+            + "81c785a7491ec03b7b41625969898df5"
+            + "3456585ebe6db84fd70dcf6cb2914279"
+            + "ccfd1e7fc25d41f5d1020dce935a2eb6"
+            + "7d45641a180f47ab3e6b8cf8f507ef27"
+            + "c8c02c9fbd18519dcfd9adf0fdd4a50b"
+            + "e2c33bd38df85723e9c9763b6ac5a3da"
+            + "70d96dd329a42ca1e7bfed5f7f59e2ab"
+            + "830ba4f968bcf3b7dc2fe6e4d5851ab6"
+            + "360e5265525f153d9fd9ffc333cd946d"
+            + "6c7b035dbb9ee9d1d5a62b1c721481b5"
+            + "0a703f8e9ae4491a83d0fb5e2c72305d"
+            + "3d045f3c43d2db5168af4e1372d5a477"
+            + "ec76b55e3c734fdf9e17d4182ffd5c78"
+            + "0fcf25d709f331a2bd9bd991ab9acbd8"
+            + "50f701c039172dca18db78836de81f96"
+            + "7e75dfa622fe6bdaa7ea896eaab576f8"
+            + "3e9e39148bf5960dc4dc8f3b768415f3"
+            + "67f477cd34cd47ae7b7de6d3332d42d9"
+            + "cf87b883abbd016d668b5f389d72a219"
+            + "c82bdd4f2c2b6768779fe2d74bf01653"
+            + "1d5618d537029d86004bf48f4cc89d16"
+            + "7bdffccd73134c971cff61096877a799"
+            + "9d1bf238fb8c12aae9f02a08b9abdfa5"
+            + "c8d1101a3d1928a7bc63973cd84b62c2"
+            + "9f7c74668d3f203c84b165b5eee84881"
+            + "a8b7a86cf7edf7b2a060c56b75d55286"
+            + "fbf4468a573a7e77e24d32470b95680e"
+            + "7155eeeea7e9522814528e2c414bbf2d"
+            + "fcafa73fcbb3b7a42f19b5f057dd";
+
+    public static byte[] hexStringToByteArray(String s) {
+        int len = s.length();
+        byte[] data = new byte[len / 2];
+        for (int i = 0; i < len; i += 2) {
+            data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4)
+                    + Character.digit(s.charAt(i + 1), 16));
+        }
+        return data;
+    }
+
+    public static void generateDefaultBloomfilter(String filePath) throws IOException {
+        File outputFile = new File(filePath);
+        outputFile.createNewFile(); // if file already exists will do nothing
+        FileOutputStream fos = new FileOutputStream(filePath);
+        fos.write(hexStringToByteArray(BLOOM_FILTER_DEFAULT));
+        fos.close();
+    }
+}
diff --git a/android/app/src/com/android/bluetooth/btservice/MetricsLogger.java b/android/app/src/com/android/bluetooth/btservice/MetricsLogger.java
index ecbac60..0f3540d 100644
--- a/android/app/src/com/android/bluetooth/btservice/MetricsLogger.java
+++ b/android/app/src/com/android/bluetooth/btservice/MetricsLogger.java
@@ -25,6 +25,15 @@
 import com.android.bluetooth.BluetoothMetricsProto.ProfileId;
 import com.android.bluetooth.BluetoothStatsLog;
 
+import com.google.common.hash.BloomFilter;
+import com.google.common.hash.Funnels;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
 import java.util.HashMap;
 
 /**
@@ -32,11 +41,15 @@
  */
 public class MetricsLogger {
     private static final String TAG = "BluetoothMetricsLogger";
+    private static final String BLOOMFILTER_PATH = "/data/misc/bluetooth/metrics";
+    private static final String BLOOMFILTER_FILE = "/devices";
+    public static final String BLOOMFILTER_FULL_PATH = BLOOMFILTER_PATH + BLOOMFILTER_FILE;
 
     public static final boolean DEBUG = false;
 
     // 6 hours timeout for counter metrics
     private static final long BLUETOOTH_COUNTER_METRICS_ACTION_DURATION_MILLIS = 6L * 3600L * 1000L;
+    private static final int MAX_WORDS_ALLOWED_IN_DEVICE_NAME = 7;
 
     private static final HashMap<ProfileId, Integer> sProfileConnectionCounts = new HashMap<>();
 
@@ -46,6 +59,8 @@
     private AlarmManager mAlarmManager = null;
     private boolean mInitialized = false;
     static final private Object mLock = new Object();
+    private BloomFilter<byte[]> mBloomFilter = null;
+    protected boolean mBloomFilterInitialized = false;
 
     private AlarmManager.OnAlarmListener mOnAlarmListener = new AlarmManager.OnAlarmListener () {
         @Override
@@ -70,6 +85,28 @@
         return mInitialized;
     }
 
+    public boolean initBloomFilter(String path) {
+        try {
+            File file = new File(path);
+            if (!file.exists()) {
+                Log.w(TAG, "MetricsLogger is creating a new Bloomfilter file");
+                DeviceBloomfilterGenerator.generateDefaultBloomfilter(path);
+            }
+
+            FileInputStream in = new FileInputStream(new File(path));
+            mBloomFilter = BloomFilter.readFrom(in, Funnels.byteArrayFunnel());
+            mBloomFilterInitialized = true;
+        } catch (IOException e) {
+            Log.w(TAG, "MetricsLogger can't read the BloomFilter file");
+            return false;
+        }
+        return true;
+    }
+
+    protected void setBloomfilter(BloomFilter bloomfilter) {
+        mBloomFilter = bloomfilter;
+    }
+
     public boolean init(Context context) {
         if (mInitialized) {
             return false;
@@ -77,6 +114,12 @@
         mInitialized = true;
         mContext = context;
         scheduleDrains();
+        if (!initBloomFilter(BLOOMFILTER_FULL_PATH)) {
+            Log.w(TAG, "MetricsLogger can't initialize the bloomfilter");
+            // The class is for multiple metrics tasks.
+            // We still want to use this class even if the bloomfilter isn't initialized
+            // so still return true here.
+        }
         return true;
     }
 
@@ -186,9 +229,70 @@
         mAlarmManager = null;
         mContext = null;
         mInitialized = false;
+        mBloomFilterInitialized = false;
         return true;
     }
     protected void cancelPendingDrain() {
         mAlarmManager.cancel(mOnAlarmListener);
     }
+
+    protected boolean logSanitizedBluetoothDeviceName(String deviceName) {
+        if (!mBloomFilterInitialized) {
+            return false;
+        }
+
+        // remove more than one spaces in a row
+        deviceName = deviceName.trim().replaceAll(" +", " ");
+        // remove non alphanumeric characters and spaces, and transform to lower cases.
+        String[] words = deviceName.replaceAll(
+                "[^a-zA-Z0-9 ]", "").toLowerCase().split(" ");
+
+        if (words.length > MAX_WORDS_ALLOWED_IN_DEVICE_NAME) {
+            // Validity checking here to avoid excessively long sequences
+            return false;
+        }
+        // find the longest matched substring
+        String matchedString = "";
+        byte[] matchedSha256 = null;
+        for (int start = 0; start < words.length; start++) {
+
+            String toBeMatched = "";
+            for (int end = start; end < words.length; end++) {
+                toBeMatched += words[end];
+                byte[] sha256 = getSha256(toBeMatched);
+                if (sha256 == null) {
+                    continue;
+                }
+
+                if (mBloomFilter.mightContain(sha256)
+                        && toBeMatched.length() > matchedString.length()) {
+                    matchedString = toBeMatched;
+                    matchedSha256 = sha256;
+                }
+            }
+        }
+
+        // upload the sha256 of the longest matched string.
+        if (matchedSha256 == null) {
+            return false;
+        }
+        statslogBluetoothDeviceNames(matchedString, matchedSha256);
+        return true;
+    }
+
+    protected void statslogBluetoothDeviceNames(String matchedString, byte[] sha256) {
+        Log.w(TAG, "Uploading sha256 hash of matched bluetooth device name: "
+                + (new String(sha256, StandardCharsets.UTF_8)));
+    }
+
+    protected static byte[] getSha256(String name) {
+        MessageDigest digest = null;
+        try {
+            digest = MessageDigest.getInstance("SHA-256");
+        } catch (NoSuchAlgorithmException e) {
+            Log.w(TAG, "No SHA-256 in MessageDigest");
+            return null;
+        }
+        return digest.digest(name.getBytes(StandardCharsets.UTF_8));
+    }
 }
diff --git a/android/app/src/com/android/bluetooth/btservice/PhonePolicy.java b/android/app/src/com/android/bluetooth/btservice/PhonePolicy.java
index 163a3cd..6d2e838 100644
--- a/android/app/src/com/android/bluetooth/btservice/PhonePolicy.java
+++ b/android/app/src/com/android/bluetooth/btservice/PhonePolicy.java
@@ -355,10 +355,12 @@
                     BluetoothProfile.PAN, BluetoothProfile.CONNECTION_POLICY_ALLOWED);
         }
 
+        boolean isLeAudioProfileAllowed = false;
         if ((leAudioService != null) && Utils.arrayContains(uuids,
                 BluetoothUuid.LE_AUDIO) && (leAudioService.getConnectionPolicy(device)
                 == BluetoothProfile.CONNECTION_POLICY_UNKNOWN)) {
             debugLog("setting le audio profile priority for device " + device);
+            isLeAudioProfileAllowed = true;
             mAdapterService.getDatabase().setProfileConnectionPolicy(device,
                     BluetoothProfile.LE_AUDIO, BluetoothProfile.CONNECTION_POLICY_ALLOWED);
             if (mPreferLeAudioOnlyMode) {
@@ -384,9 +386,13 @@
         if ((hearingAidService != null) && Utils.arrayContains(uuids,
                 BluetoothUuid.HEARING_AID) && (hearingAidService.getConnectionPolicy(device)
                 == BluetoothProfile.CONNECTION_POLICY_UNKNOWN)) {
-            debugLog("setting hearing aid profile priority for device " + device);
-            mAdapterService.getDatabase().setProfileConnectionPolicy(device,
-                    BluetoothProfile.HEARING_AID, BluetoothProfile.CONNECTION_POLICY_ALLOWED);
+            if (isLeAudioProfileAllowed) {
+                debugLog("LE Audio preferred over ASHA for device " + device);
+            } else {
+                debugLog("setting hearing aid profile priority for device " + device);
+                mAdapterService.getDatabase().setProfileConnectionPolicy(device,
+                        BluetoothProfile.HEARING_AID, BluetoothProfile.CONNECTION_POLICY_ALLOWED);
+            }
         }
 
         if ((volumeControlService != null) && Utils.arrayContains(uuids,
diff --git a/android/app/src/com/android/bluetooth/btservice/RemoteDevices.java b/android/app/src/com/android/bluetooth/btservice/RemoteDevices.java
index 90d10ee..e748631 100644
--- a/android/app/src/com/android/bluetooth/btservice/RemoteDevices.java
+++ b/android/app/src/com/android/bluetooth/btservice/RemoteDevices.java
@@ -27,6 +27,7 @@
 import android.bluetooth.BluetoothHeadset;
 import android.bluetooth.BluetoothHeadsetClient;
 import android.bluetooth.BluetoothProfile;
+import android.bluetooth.BluetoothSinkAudioPolicy;
 import android.bluetooth.IBluetoothConnectionCallback;
 import android.content.BroadcastReceiver;
 import android.content.Context;
@@ -38,6 +39,7 @@
 import android.os.Message;
 import android.os.ParcelUuid;
 import android.os.RemoteException;
+import android.os.SystemProperties;
 import android.util.Log;
 
 import com.android.bluetooth.BluetoothStatsLog;
@@ -245,29 +247,28 @@
 
     DeviceProperties getDeviceProperties(BluetoothDevice device) {
         synchronized (mDevices) {
-            DeviceProperties prop = mDevices.get(device.getAddress());
-            if (prop == null) {
-                String mainAddress = mDualDevicesMap.get(device.getAddress());
-                if (mainAddress != null && mDevices.get(mainAddress) != null) {
-                    prop = mDevices.get(mainAddress);
-                }
+            String address = mDualDevicesMap.get(device.getAddress());
+            // If the device is not in the dual map, use its original address
+            if (address == null || mDevices.get(address) == null) {
+                address = device.getAddress();
             }
-            return prop;
+            return mDevices.get(address);
         }
     }
 
     BluetoothDevice getDevice(byte[] address) {
         String addressString = Utils.getAddressStringFromByte(address);
-        DeviceProperties prop = mDevices.get(addressString);
-        if (prop == null) {
-            String mainAddress = mDualDevicesMap.get(addressString);
-            if (mainAddress != null && mDevices.get(mainAddress) != null) {
-                prop = mDevices.get(mainAddress);
-                return prop.getDevice();
-            }
-            return null;
+        String deviceAddress = mDualDevicesMap.get(addressString);
+        // If the device is not in the dual map, use its original address
+        if (deviceAddress == null || mDevices.get(deviceAddress) == null) {
+            deviceAddress = addressString;
         }
-        return prop.getDevice();
+
+        DeviceProperties prop = mDevices.get(deviceAddress);
+        if (prop != null) {
+            return prop.getDevice();
+        }
+        return null;
     }
 
     @VisibleForTesting
@@ -311,6 +312,7 @@
         @VisibleForTesting int mBondState;
         @VisibleForTesting int mDeviceType;
         @VisibleForTesting ParcelUuid[] mUuids;
+        private BluetoothSinkAudioPolicy mAudioPolicy;
 
         DeviceProperties() {
             mBondState = BluetoothDevice.BOND_NONE;
@@ -498,6 +500,14 @@
                 return mIsCoordinatedSetMember;
             }
         }
+
+        public void setHfAudioPolicyForRemoteAg(BluetoothSinkAudioPolicy policies) {
+            mAudioPolicy = policies;
+        }
+
+        public BluetoothSinkAudioPolicy getHfAudioPolicyForRemoteAg() {
+            return mAudioPolicy;
+        }
     }
 
     private void sendUuidIntent(BluetoothDevice device, DeviceProperties prop) {
@@ -754,6 +764,12 @@
             errorLog("Device Properties is null for Device:" + device);
             return;
         }
+        boolean restrict_device_found =
+                SystemProperties.getBoolean("bluetooth.restrict_discovered_device.enabled", false);
+        if (restrict_device_found && (deviceProp.mName == null || deviceProp.mName.isEmpty())) {
+            debugLog("Device name is null or empty: " + device);
+            return;
+        }
 
         Intent intent = new Intent(BluetoothDevice.ACTION_FOUND);
         intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
diff --git a/android/app/src/com/android/bluetooth/btservice/storage/AudioPolicyEntity.java b/android/app/src/com/android/bluetooth/btservice/storage/AudioPolicyEntity.java
new file mode 100644
index 0000000..d5ec422
--- /dev/null
+++ b/android/app/src/com/android/bluetooth/btservice/storage/AudioPolicyEntity.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.bluetooth.btservice.storage;
+
+import android.bluetooth.BluetoothSinkAudioPolicy;
+
+import androidx.room.ColumnInfo;
+import androidx.room.Entity;
+
+@Entity
+class AudioPolicyEntity {
+    @ColumnInfo(name = "call_establish_audio_policy")
+    public int callEstablishAudioPolicy;
+    @ColumnInfo(name = "connecting_time_audio_policy")
+    public int connectingTimeAudioPolicy;
+    @ColumnInfo(name = "in_band_ringtone_audio_policy")
+    public int inBandRingtoneAudioPolicy;
+
+    AudioPolicyEntity() {
+        callEstablishAudioPolicy = BluetoothSinkAudioPolicy.POLICY_UNCONFIGURED;
+        connectingTimeAudioPolicy = BluetoothSinkAudioPolicy.POLICY_UNCONFIGURED;
+        inBandRingtoneAudioPolicy = BluetoothSinkAudioPolicy.POLICY_UNCONFIGURED;
+    }
+
+    AudioPolicyEntity(int callEstablishAudioPolicy, int connectingTimeAudioPolicy,
+            int inBandRingtoneAudioPolicy) {
+        this.callEstablishAudioPolicy = callEstablishAudioPolicy;
+        this.connectingTimeAudioPolicy = connectingTimeAudioPolicy;
+        this.inBandRingtoneAudioPolicy = inBandRingtoneAudioPolicy;
+    }
+
+    public String toString() {
+        StringBuilder builder = new StringBuilder();
+        builder.append("callEstablishAudioPolicy=")
+                .append(metadataToString(callEstablishAudioPolicy))
+                .append("|connectingTimeAudioPolicy=")
+                .append(metadataToString(connectingTimeAudioPolicy))
+                .append("|inBandRingtoneAudioPolicy=")
+                .append(metadataToString(inBandRingtoneAudioPolicy));
+
+        return builder.toString();
+    }
+
+    private String metadataToString(int metadata) {
+        return String.valueOf(metadata);
+    }
+}
diff --git a/android/app/src/com/android/bluetooth/btservice/storage/CustomizedMetadataEntity.java b/android/app/src/com/android/bluetooth/btservice/storage/CustomizedMetadataEntity.java
index b3746a2..e2f0765 100644
--- a/android/app/src/com/android/bluetooth/btservice/storage/CustomizedMetadataEntity.java
+++ b/android/app/src/com/android/bluetooth/btservice/storage/CustomizedMetadataEntity.java
@@ -47,6 +47,8 @@
     public byte[] spatial_audio;
     public byte[] fastpair_customized;
     public byte[] le_audio;
+    public byte[] gmcs_cccd;
+    public byte[] gtbs_cccd;
 
     public String toString() {
         StringBuilder builder = new StringBuilder();
@@ -103,7 +105,11 @@
                 .append("|fastpair_customized=")
                 .append(metadataToString(fastpair_customized))
                 .append("|le_audio=")
-                .append(metadataToString(le_audio));
+                .append(metadataToString(le_audio))
+                .append("|gmcs_cccd=")
+                .append(metadataToString(gmcs_cccd))
+                .append("|gtbs_cccd=")
+                .append(metadataToString(gtbs_cccd));
 
 
         return builder.toString();
diff --git a/android/app/src/com/android/bluetooth/btservice/storage/DatabaseManager.java b/android/app/src/com/android/bluetooth/btservice/storage/DatabaseManager.java
index fa85049..cd81c9d 100644
--- a/android/app/src/com/android/bluetooth/btservice/storage/DatabaseManager.java
+++ b/android/app/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.bluetooth.BluetoothSinkAudioPolicy;
 import android.content.BroadcastReceiver;
 import android.content.ContentResolver;
 import android.content.Context;
@@ -284,6 +285,60 @@
     }
 
     /**
+     * Set audio policy metadata to database with requested key
+     */
+    @VisibleForTesting
+    public boolean setAudioPolicyMetadata(BluetoothDevice device,
+            BluetoothSinkAudioPolicy policies) {
+        synchronized (mMetadataCache) {
+            if (device == null) {
+                Log.e(TAG, "setAudioPolicyMetadata: device is null");
+                return false;
+            }
+
+            String address = device.getAddress();
+            if (!mMetadataCache.containsKey(address)) {
+                createMetadata(address, false);
+            }
+            Metadata data = mMetadataCache.get(address);
+            AudioPolicyEntity entity = data.audioPolicyMetadata;
+            entity.callEstablishAudioPolicy = policies.getCallEstablishPolicy();
+            entity.connectingTimeAudioPolicy = policies.getActiveDevicePolicyAfterConnection();
+            entity.inBandRingtoneAudioPolicy = policies.getInBandRingtonePolicy();
+
+            updateDatabase(data);
+            return true;
+        }
+    }
+
+    /**
+     * Get audio policy metadata from database with requested key
+     */
+    @VisibleForTesting
+    public BluetoothSinkAudioPolicy getAudioPolicyMetadata(BluetoothDevice device) {
+        synchronized (mMetadataCache) {
+            if (device == null) {
+                Log.e(TAG, "getAudioPolicyMetadata: device is null");
+                return null;
+            }
+
+            String address = device.getAddress();
+
+            if (!mMetadataCache.containsKey(address)) {
+                Log.d(TAG, "getAudioPolicyMetadata: device " + address + " is not in cache");
+                return null;
+            }
+
+            AudioPolicyEntity entity = mMetadataCache.get(address).audioPolicyMetadata;
+            return new BluetoothSinkAudioPolicy.Builder()
+                    .setCallEstablishPolicy(entity.callEstablishAudioPolicy)
+                    .setActiveDevicePolicyAfterConnection(entity.connectingTimeAudioPolicy)
+                    .setInBandRingtonePolicy(entity.inBandRingtoneAudioPolicy)
+                    .build();
+        }
+    }
+
+    /**
      * Set the device profile connection policy
      *
      * @param device {@link BluetoothDevice} wish to set
diff --git a/android/app/src/com/android/bluetooth/btservice/storage/Metadata.java b/android/app/src/com/android/bluetooth/btservice/storage/Metadata.java
index f64f35e..756b6d7 100644
--- a/android/app/src/com/android/bluetooth/btservice/storage/Metadata.java
+++ b/android/app/src/com/android/bluetooth/btservice/storage/Metadata.java
@@ -58,6 +58,25 @@
     public long last_active_time;
     public boolean is_active_a2dp_device;
 
+    @Embedded
+    public AudioPolicyEntity audioPolicyMetadata;
+
+    /**
+     * The preferred profile to be used for {@link BluetoothDevice#AUDIO_MODE_OUTPUT_ONLY}. This can
+     * be either {@link BluetoothProfile#A2DP} or {@link BluetoothProfile#LE_AUDIO}. This value is
+     * only used if the remote device supports both A2DP and LE Audio and both transports are
+     * connected and active.
+     */
+    public int preferred_output_only_profile;
+
+    /**
+     * The preferred profile to be used for {@link BluetoothDevice#AUDIO_MODE_DUPLEX}. This can
+     * be either {@link BluetoothProfile#HEADSET} or {@link BluetoothProfile#LE_AUDIO}. This value
+     * is only used if the remote device supports both HFP and LE Audio and both transports are
+     * connected and active.
+     */
+    public int preferred_duplex_profile;
+
     Metadata(String address) {
         this.address = address;
         migrated = false;
@@ -67,6 +86,9 @@
         a2dpOptionalCodecsEnabled = BluetoothA2dp.OPTIONAL_CODECS_PREF_UNKNOWN;
         last_active_time = MetadataDatabase.sCurrentConnectionNumber++;
         is_active_a2dp_device = true;
+        audioPolicyMetadata = new AudioPolicyEntity();
+        preferred_output_only_profile = 0;
+        preferred_duplex_profile = 0;
     }
 
     /**
@@ -290,6 +312,12 @@
             case BluetoothDevice.METADATA_LE_AUDIO:
                 publicMetadata.le_audio = value;
                 break;
+            case BluetoothDevice.METADATA_GMCS_CCCD:
+                publicMetadata.gmcs_cccd = value;
+                break;
+            case BluetoothDevice.METADATA_GTBS_CCCD:
+                publicMetadata.gtbs_cccd = value;
+                break;
         }
     }
 
@@ -381,6 +409,12 @@
             case BluetoothDevice.METADATA_LE_AUDIO:
                 value = publicMetadata.le_audio;
                 break;
+            case BluetoothDevice.METADATA_GMCS_CCCD:
+                value = publicMetadata.gmcs_cccd;
+                break;
+            case BluetoothDevice.METADATA_GTBS_CCCD:
+                value = publicMetadata.gtbs_cccd;
+                break;
         }
         return value;
     }
@@ -407,6 +441,8 @@
             .append(a2dpOptionalCodecsEnabled)
             .append("), custom metadata(")
             .append(publicMetadata)
+            .append("), hfp client audio policy(")
+            .append(audioPolicyMetadata)
             .append(")}");
 
         return builder.toString();
diff --git a/android/app/src/com/android/bluetooth/btservice/storage/MetadataDatabase.java b/android/app/src/com/android/bluetooth/btservice/storage/MetadataDatabase.java
index a71a548..2776e65 100644
--- a/android/app/src/com/android/bluetooth/btservice/storage/MetadataDatabase.java
+++ b/android/app/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 = 114)
+@Database(entities = {Metadata.class}, version = 117)
 public abstract class MetadataDatabase extends RoomDatabase {
     /**
      * The metadata database file name
@@ -67,6 +67,9 @@
                 .addMigrations(MIGRATION_111_112)
                 .addMigrations(MIGRATION_112_113)
                 .addMigrations(MIGRATION_113_114)
+                .addMigrations(MIGRATION_114_115)
+                .addMigrations(MIGRATION_115_116)
+                .addMigrations(MIGRATION_116_117)
                 .allowMainThreadQueries()
                 .build();
     }
@@ -500,4 +503,67 @@
             }
         }
     };
+
+    @VisibleForTesting
+    static final Migration MIGRATION_114_115 = new Migration(114, 115) {
+        @Override
+        public void migrate(SupportSQLiteDatabase database) {
+            try {
+                database.execSQL(
+                        "ALTER TABLE metadata ADD COLUMN `call_establish_audio_policy` "
+                        + "INTEGER DEFAULT 0");
+                database.execSQL(
+                        "ALTER TABLE metadata ADD COLUMN `connecting_time_audio_policy` "
+                        + "INTEGER DEFAULT 0");
+                database.execSQL(
+                        "ALTER TABLE metadata ADD COLUMN `in_band_ringtone_audio_policy` "
+                        + "INTEGER DEFAULT 0");
+            } catch (SQLException ex) {
+                // Check if user has new schema, but is just missing the version update
+                Cursor cursor = database.query("SELECT * FROM metadata");
+                if (cursor == null
+                        || cursor.getColumnIndex("call_establish_audio_policy") == -1) {
+                    throw ex;
+                }
+            }
+        }
+    };
+
+    @VisibleForTesting
+    static final Migration MIGRATION_115_116 = new Migration(115, 116) {
+        @Override
+        public void migrate(SupportSQLiteDatabase database) {
+            try {
+                database.execSQL("ALTER TABLE metadata ADD COLUMN `preferred_output_only_profile` "
+                        + "INTEGER NOT NULL DEFAULT 0");
+                database.execSQL("ALTER TABLE metadata ADD COLUMN `preferred_duplex_profile` "
+                        + "INTEGER NOT NULL DEFAULT 0");
+            } catch (SQLException ex) {
+                // Check if user has new schema, but is just missing the version update
+                Cursor cursor = database.query("SELECT * FROM metadata");
+                if (cursor == null
+                        || cursor.getColumnIndex("preferred_output_only_profile") == -1
+                        || cursor.getColumnIndex("preferred_duplex_profile") == -1) {
+                    throw ex;
+                }
+            }
+        }
+    };
+
+    @VisibleForTesting
+    static final Migration MIGRATION_116_117 = new Migration(116, 117) {
+        @Override
+        public void migrate(SupportSQLiteDatabase database) {
+            try {
+                database.execSQL("ALTER TABLE metadata ADD COLUMN `gmcs_cccd` BLOB");
+                database.execSQL("ALTER TABLE metadata ADD COLUMN `gtbs_cccd` 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("gmcs_cccd") == -1) {
+                    throw ex;
+                }
+            }
+        }
+    };
 }
diff --git a/android/app/src/com/android/bluetooth/csip/CsipSetCoordinatorStateMachine.java b/android/app/src/com/android/bluetooth/csip/CsipSetCoordinatorStateMachine.java
index 5945180..062b004 100644
--- a/android/app/src/com/android/bluetooth/csip/CsipSetCoordinatorStateMachine.java
+++ b/android/app/src/com/android/bluetooth/csip/CsipSetCoordinatorStateMachine.java
@@ -47,7 +47,7 @@
     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 final int CONNECT_TIMEOUT = 201;
 
     // NOTE: the value is not "final" - it is modified in the unit tests
     @VisibleForTesting static int sConnectTimeoutMs = 30000; // 30s
diff --git a/android/app/src/com/android/bluetooth/gatt/AdvertiseManager.java b/android/app/src/com/android/bluetooth/gatt/AdvertiseManager.java
index 4ba889d..f1c83b3 100644
--- a/android/app/src/com/android/bluetooth/gatt/AdvertiseManager.java
+++ b/android/app/src/com/android/bluetooth/gatt/AdvertiseManager.java
@@ -31,6 +31,7 @@
 
 import com.android.bluetooth.btservice.AdapterService;
 import com.android.bluetooth.gatt.GattService.AdvertiserMap;
+import com.android.internal.annotations.VisibleForTesting;
 
 import java.util.Collections;
 import java.util.HashMap;
@@ -41,7 +42,8 @@
  *
  * @hide
  */
-class AdvertiseManager {
+@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+public class AdvertiseManager {
     private static final boolean DBG = GattServiceConfig.DBG;
     private static final String TAG = GattServiceConfig.TAG_PREFIX + "AdvertiseManager";
 
diff --git a/android/app/src/com/android/bluetooth/gatt/GattService.java b/android/app/src/com/android/bluetooth/gatt/GattService.java
index 2bcedd7..c8f589b 100644
--- a/android/app/src/com/android/bluetooth/gatt/GattService.java
+++ b/android/app/src/com/android/bluetooth/gatt/GattService.java
@@ -79,6 +79,7 @@
 import com.android.bluetooth.btservice.AbstractionLayer;
 import com.android.bluetooth.btservice.AdapterService;
 import com.android.bluetooth.btservice.BluetoothAdapterProxy;
+import com.android.bluetooth.btservice.CompanionManager;
 import com.android.bluetooth.btservice.ProfileService;
 import com.android.bluetooth.util.NumberUtils;
 import com.android.internal.annotations.VisibleForTesting;
@@ -145,7 +146,8 @@
     /**
      * The default floor value for LE batch scan report delays greater than 0
      */
-    private static final long DEFAULT_REPORT_DELAY_FLOOR = 5000;
+    @VisibleForTesting
+    static final long DEFAULT_REPORT_DELAY_FLOOR = 5000;
 
     // onFoundLost related constants
     private static final int ADVT_STATE_ONFOUND = 0;
@@ -274,9 +276,12 @@
 
     private AdapterService mAdapterService;
     private BluetoothAdapterProxy mBluetoothAdapterProxy;
-    private AdvertiseManager mAdvertiseManager;
-    private PeriodicScanManager mPeriodicScanManager;
-    private ScanManager mScanManager;
+    @VisibleForTesting
+    AdvertiseManager mAdvertiseManager;
+    @VisibleForTesting
+    PeriodicScanManager mPeriodicScanManager;
+    @VisibleForTesting
+    ScanManager mScanManager;
     private AppOpsManager mAppOps;
     private CompanionDeviceManager mCompanionManager;
     private String mExposureNotificationPackage;
@@ -309,7 +314,8 @@
     /**
      * Reliable write queue
      */
-    private Set<String> mReliableQueue = new HashSet<String>();
+    @VisibleForTesting
+    Set<String> mReliableQueue = new HashSet<String>();
 
     static {
         classInitNative();
@@ -2048,7 +2054,7 @@
                     (status == BluetoothGatt.GATT_SUCCESS), address);
         }
         statsLogGattConnectionStateChange(
-                BluetoothProfile.GATT, address, clientIf, connectionState);
+                BluetoothProfile.GATT, address, clientIf, connectionState, status);
     }
 
     void onDisconnected(int clientIf, int connId, int status, String address)
@@ -2083,7 +2089,7 @@
         }
         statsLogGattConnectionStateChange(
                 BluetoothProfile.GATT, address, clientIf,
-                BluetoothProtoEnums.CONNECTION_STATE_DISCONNECTED);
+                BluetoothProtoEnums.CONNECTION_STATE_DISCONNECTED, status);
     }
 
     void onClientPhyUpdate(int connId, int txPhy, int rxPhy, int status) throws RemoteException {
@@ -3431,7 +3437,7 @@
         statsLogAppPackage(address, attributionSource.getUid(), clientIf);
         statsLogGattConnectionStateChange(
                 BluetoothProfile.GATT, address, clientIf,
-                BluetoothProtoEnums.CONNECTION_STATE_CONNECTING);
+                BluetoothProtoEnums.CONNECTION_STATE_CONNECTING, -1);
         gattClientConnectNative(clientIf, address, isDirect, transport, opportunistic, phy);
     }
 
@@ -3448,7 +3454,7 @@
         }
         statsLogGattConnectionStateChange(
                 BluetoothProfile.GATT, address, clientIf,
-                BluetoothProtoEnums.CONNECTION_STATE_DISCONNECTING);
+                BluetoothProtoEnums.CONNECTION_STATE_DISCONNECTING, -1);
         gattClientDisconnectNative(clientIf, address, connId != null ? connId : 0);
     }
 
@@ -3860,33 +3866,21 @@
         // Link supervision timeout is measured in N * 10ms
         int timeout = 500; // 5s
 
-        switch (connectionPriority) {
-            case BluetoothGatt.CONNECTION_PRIORITY_HIGH:
-                minInterval = getResources().getInteger(R.integer.gatt_high_priority_min_interval);
-                maxInterval = getResources().getInteger(R.integer.gatt_high_priority_max_interval);
-                latency = getResources().getInteger(R.integer.gatt_high_priority_latency);
-                break;
 
-            case BluetoothGatt.CONNECTION_PRIORITY_LOW_POWER:
-                minInterval = getResources().getInteger(R.integer.gatt_low_power_min_interval);
-                maxInterval = getResources().getInteger(R.integer.gatt_low_power_max_interval);
-                latency = getResources().getInteger(R.integer.gatt_low_power_latency);
-                break;
+        CompanionManager manager =
+                AdapterService.getAdapterService().getCompanionManager();
 
-            default:
-                // Using the values for CONNECTION_PRIORITY_BALANCED.
-                minInterval =
-                        getResources().getInteger(R.integer.gatt_balanced_priority_min_interval);
-                maxInterval =
-                        getResources().getInteger(R.integer.gatt_balanced_priority_max_interval);
-                latency = getResources().getInteger(R.integer.gatt_balanced_priority_latency);
-                break;
-        }
+        minInterval = manager.getGattConnParameters(
+                address, CompanionManager.GATT_CONN_INTERVAL_MIN, connectionPriority);
+        maxInterval = manager.getGattConnParameters(
+                address, CompanionManager.GATT_CONN_INTERVAL_MAX, connectionPriority);
+        latency = manager.getGattConnParameters(
+                address, CompanionManager.GATT_CONN_LATENCY, connectionPriority);
 
-        if (DBG) {
-            Log.d(TAG, "connectionParameterUpdate() - address=" + address + "params="
-                    + connectionPriority + " interval=" + minInterval + "/" + maxInterval);
-        }
+        Log.d(TAG, "connectionParameterUpdate() - address=" + address + " params="
+                + connectionPriority + " interval=" + minInterval + "/" + maxInterval
+                + " timeout=" + timeout);
+
         gattConnectionParameterUpdateNative(clientIf, address, minInterval, maxInterval, latency,
                 timeout, 0, 0);
     }
@@ -3901,14 +3895,11 @@
             return;
         }
 
-        if (DBG) {
-            Log.d(TAG, "leConnectionUpdate() - address=" + address + ", intervals="
-                        + minInterval + "/" + maxInterval + ", latency=" + peripheralLatency
-                        + ", timeout=" + supervisionTimeout + "msec" + ", min_ce="
-                        + minConnectionEventLen + ", max_ce=" + maxConnectionEventLen);
+        Log.d(TAG, "leConnectionUpdate() - address=" + address + ", intervals="
+                    + minInterval + "/" + maxInterval + ", latency=" + peripheralLatency
+                    + ", timeout=" + supervisionTimeout + "msec" + ", min_ce="
+                    + minConnectionEventLen + ", max_ce=" + maxConnectionEventLen);
 
-
-        }
         gattConnectionParameterUpdateNative(clientIf, address, minInterval, maxInterval,
                                             peripheralLatency, supervisionTimeout,
                                             minConnectionEventLen, maxConnectionEventLen);
@@ -4031,7 +4022,7 @@
         app.callback.onServerConnectionState((byte) 0, serverIf, connected, address);
         statsLogAppPackage(address, applicationUid, serverIf);
         statsLogGattConnectionStateChange(
-                BluetoothProfile.GATT_SERVER, address, serverIf, connectionState);
+                BluetoothProfile.GATT_SERVER, address, serverIf, connectionState, -1);
     }
 
     void onServerReadCharacteristic(String address, int connId, int transId, int handle, int offset,
@@ -4581,7 +4572,8 @@
      *         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) {
+    @VisibleForTesting
+    ScanSettings enforceReportDelayFloor(ScanSettings settings) {
         if (settings.getReportDelayMillis() == 0) {
             return settings;
         }
@@ -4720,16 +4712,18 @@
     }
 
     private void statsLogGattConnectionStateChange(
-            int profile, String address, int sessionIndex, int connectionState) {
+            int profile, String address, int sessionIndex, int connectionState,
+            int connectionStatus) {
         BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address);
         BluetoothStatsLog.write(
                 BluetoothStatsLog.BLUETOOTH_CONNECTION_STATE_CHANGED, connectionState,
                 0 /* deprecated */, profile, new byte[0],
-                mAdapterService.getMetricId(device), sessionIndex);
+                mAdapterService.getMetricId(device), sessionIndex, connectionStatus);
         if (DBG) {
             Log.d(TAG, "Gatt Logging: metric_id=" + mAdapterService.getMetricId(device)
                     + ", session_index=" + sessionIndex
-                    + ", connection state=" + connectionState);
+                    + ", connection state=" + connectionState
+                    + ", connection status=" + connectionStatus);
         }
     }
 
diff --git a/android/app/src/com/android/bluetooth/gatt/PeriodicScanManager.java b/android/app/src/com/android/bluetooth/gatt/PeriodicScanManager.java
index f4fdb95..e165f0b 100644
--- a/android/app/src/com/android/bluetooth/gatt/PeriodicScanManager.java
+++ b/android/app/src/com/android/bluetooth/gatt/PeriodicScanManager.java
@@ -28,6 +28,7 @@
 import android.util.Log;
 
 import com.android.bluetooth.btservice.AdapterService;
+import com.android.internal.annotations.VisibleForTesting;
 
 import java.util.Collections;
 import java.util.HashMap;
@@ -39,7 +40,8 @@
  *
  * @hide
  */
-class PeriodicScanManager {
+@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+public class PeriodicScanManager {
     private static final boolean DBG = GattServiceConfig.DBG;
     private static final String TAG = GattServiceConfig.TAG_PREFIX + "SyncManager";
 
diff --git a/android/app/src/com/android/bluetooth/gatt/ScanFilterQueue.java b/android/app/src/com/android/bluetooth/gatt/ScanFilterQueue.java
index 9919732..d223967 100644
--- a/android/app/src/com/android/bluetooth/gatt/ScanFilterQueue.java
+++ b/android/app/src/com/android/bluetooth/gatt/ScanFilterQueue.java
@@ -39,7 +39,7 @@
     public static final int TYPE_LOCAL_NAME = 4;
     public static final int TYPE_MANUFACTURER_DATA = 5;
     public static final int TYPE_SERVICE_DATA = 6;
-    public static final int TYPE_ADVERTISING_DATA_TYPE = 7;
+    public static final int TYPE_ADVERTISING_DATA_TYPE = 8;
 
     // Max length is 31 - 3(flags) - 2 (one byte for length and one byte for type).
     private static final int MAX_LEN_PER_FIELD = 26;
diff --git a/android/app/src/com/android/bluetooth/hfp/HeadsetService.java b/android/app/src/com/android/bluetooth/hfp/HeadsetService.java
index 19bffa0..856f7dd 100644
--- a/android/app/src/com/android/bluetooth/hfp/HeadsetService.java
+++ b/android/app/src/com/android/bluetooth/hfp/HeadsetService.java
@@ -27,6 +27,7 @@
 import android.bluetooth.BluetoothDevice;
 import android.bluetooth.BluetoothHeadset;
 import android.bluetooth.BluetoothProfile;
+import android.bluetooth.BluetoothSinkAudioPolicy;
 import android.bluetooth.BluetoothStatusCodes;
 import android.bluetooth.BluetoothUuid;
 import android.bluetooth.IBluetoothHeadset;
@@ -57,6 +58,7 @@
 import com.android.bluetooth.btservice.ProfileService;
 import com.android.bluetooth.btservice.ServiceFactory;
 import com.android.bluetooth.btservice.storage.DatabaseManager;
+import com.android.bluetooth.hfpclient.HeadsetClientService;
 import com.android.bluetooth.le_audio.LeAudioService;
 import com.android.bluetooth.telephony.BluetoothInCallService;
 import com.android.internal.annotations.VisibleForTesting;
@@ -1364,6 +1366,24 @@
     }
 
     /**
+     * Get the Bluetooth Audio Policy stored in the state machine
+     *
+     * @param device the device to change silence mode
+     * @return a {@link BluetoothSinkAudioPolicy} object
+     */
+    public BluetoothSinkAudioPolicy getHfpCallAudioPolicy(BluetoothDevice device) {
+        synchronized (mStateMachines) {
+            final HeadsetStateMachine stateMachine = mStateMachines.get(device);
+            if (stateMachine == null) {
+                Log.e(TAG, "getHfpCallAudioPolicy(), " + device
+                        + " does not have a state machine");
+                return null;
+            }
+            return stateMachine.getHfpCallAudioPolicy();
+        }
+    }
+
+    /**
      * Remove the active device
      */
     private void removeActiveDevice() {
@@ -1891,7 +1911,24 @@
                 mSystemInterface.getAudioManager().setA2dpSuspended(false);
             }
         });
-
+        if (callState == HeadsetHalConstants.CALL_STATE_IDLE) {
+            final HeadsetStateMachine stateMachine = mStateMachines.get(mActiveDevice);
+            if (stateMachine == null) {
+                Log.d(TAG, "phoneStateChanged: CALL_STATE_IDLE, mActiveDevice is Null");
+            } else {
+                BluetoothSinkAudioPolicy currentPolicy = stateMachine.getHfpCallAudioPolicy();
+                if (currentPolicy != null && currentPolicy.getActiveDevicePolicyAfterConnection()
+                        == BluetoothSinkAudioPolicy.POLICY_NOT_ALLOWED) {
+                    /**
+                     * If the active device was set because of the pick up audio policy
+                     * and the connecting policy is NOT_ALLOWED, then after the call is
+                     * terminated, we must de-activate this device.
+                     * If there is a fallback mechanism, we should follow it.
+                     */
+                    removeActiveDevice();
+                }
+            }
+        }
     }
 
     @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
@@ -1940,8 +1977,32 @@
     public boolean isInbandRingingEnabled() {
         boolean isInbandRingingSupported = getResources().getBoolean(
                 com.android.bluetooth.R.bool.config_bluetooth_hfp_inband_ringing_support);
+
+        boolean inbandRingtoneAllowedByPolicy = true;
+        List<BluetoothDevice> audioConnectableDevices = getConnectedDevices();
+        if (audioConnectableDevices.size() == 1) {
+            BluetoothDevice connectedDevice = audioConnectableDevices.get(0);
+            BluetoothSinkAudioPolicy callAudioPolicy =
+                    getHfpCallAudioPolicy(connectedDevice);
+            if (callAudioPolicy != null && callAudioPolicy.getInBandRingtonePolicy()
+                    == BluetoothSinkAudioPolicy.POLICY_NOT_ALLOWED) {
+                inbandRingtoneAllowedByPolicy = false;
+            }
+        }
+
         return isInbandRingingSupported && !SystemProperties.getBoolean(
-                DISABLE_INBAND_RINGING_PROPERTY, false) && !mInbandRingingRuntimeDisable;
+                DISABLE_INBAND_RINGING_PROPERTY, false)
+                && !mInbandRingingRuntimeDisable
+                && inbandRingtoneAllowedByPolicy
+                && !isHeadsetClientConnected();
+    }
+
+    private boolean isHeadsetClientConnected() {
+        HeadsetClientService headsetClientService = HeadsetClientService.getHeadsetClientService();
+        if (headsetClientService == null) {
+            return false;
+        }
+        return !(headsetClientService.getConnectedDevices().isEmpty());
     }
 
     /**
@@ -1956,30 +2017,53 @@
     @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
     public void onConnectionStateChangedFromStateMachine(BluetoothDevice device, int fromState,
             int toState) {
-        synchronized (mStateMachines) {
-            List<BluetoothDevice> audioConnectableDevices =
-                    getDevicesMatchingConnectionStates(CONNECTING_CONNECTED_STATES);
-            if (fromState != BluetoothProfile.STATE_CONNECTED
-                    && toState == BluetoothProfile.STATE_CONNECTED) {
-                if (audioConnectableDevices.size() > 1) {
-                    mInbandRingingRuntimeDisable = true;
-                    doForEachConnectedStateMachine(
-                            stateMachine -> stateMachine.sendMessage(HeadsetStateMachine.SEND_BSIR,
-                                    0));
-                }
-                MetricsLogger.logProfileConnectionEvent(BluetoothMetricsProto.ProfileId.HEADSET);
+        if (fromState != BluetoothProfile.STATE_CONNECTED
+                && toState == BluetoothProfile.STATE_CONNECTED) {
+            updateInbandRinging(device, true);
+            MetricsLogger.logProfileConnectionEvent(BluetoothMetricsProto.ProfileId.HEADSET);
+        }
+        if (fromState != BluetoothProfile.STATE_DISCONNECTED
+                && toState == BluetoothProfile.STATE_DISCONNECTED) {
+            updateInbandRinging(device, false);
+            if (device.equals(mActiveDevice)) {
+                setActiveDevice(null);
             }
-            if (fromState != BluetoothProfile.STATE_DISCONNECTED
-                    && toState == BluetoothProfile.STATE_DISCONNECTED) {
-                if (audioConnectableDevices.size() <= 1) {
-                    mInbandRingingRuntimeDisable = false;
-                    doForEachConnectedStateMachine(
-                            stateMachine -> stateMachine.sendMessage(HeadsetStateMachine.SEND_BSIR,
-                                    1));
-                }
-                if (device.equals(mActiveDevice)) {
-                    setActiveDevice(null);
-                }
+        }
+    }
+
+    /**
+     * Called from {@link HeadsetClientStateMachine} to update inband ringing status.
+     */
+    public void updateInbandRinging(BluetoothDevice device, boolean connected) {
+        synchronized (mStateMachines) {
+            List<BluetoothDevice> audioConnectableDevices = getConnectedDevices();
+            final int enabled;
+            final boolean inbandRingingRuntimeDisable = mInbandRingingRuntimeDisable;
+
+            if (audioConnectableDevices.size() > 1 || isHeadsetClientConnected()) {
+                mInbandRingingRuntimeDisable = true;
+                enabled = 0;
+            } else {
+                mInbandRingingRuntimeDisable = false;
+                enabled = 1;
+            }
+
+            final boolean updateAll = inbandRingingRuntimeDisable != mInbandRingingRuntimeDisable;
+
+            Log.i(TAG, "updateInbandRinging():"
+                    + " Device=" + device
+                    + " enabled=" + enabled
+                    + " connected=" + connected
+                    + " Update all=" + updateAll);
+
+            StateMachineTask sendBsirTask = stateMachine -> stateMachine
+                            .sendMessage(HeadsetStateMachine.SEND_BSIR, enabled);
+
+            if (updateAll) {
+                doForEachConnectedStateMachine(sendBsirTask);
+            } else if (connected) {
+                // Same Inband ringing status, send +BSIR only to the new connected device
+                doForStateMachine(device, sendBsirTask);
             }
         }
     }
diff --git a/android/app/src/com/android/bluetooth/hfp/HeadsetStateMachine.java b/android/app/src/com/android/bluetooth/hfp/HeadsetStateMachine.java
index b237e7d..03ea3fa 100644
--- a/android/app/src/com/android/bluetooth/hfp/HeadsetStateMachine.java
+++ b/android/app/src/com/android/bluetooth/hfp/HeadsetStateMachine.java
@@ -25,6 +25,7 @@
 import android.bluetooth.BluetoothHeadset;
 import android.bluetooth.BluetoothProfile;
 import android.bluetooth.BluetoothProtoEnums;
+import android.bluetooth.BluetoothSinkAudioPolicy;
 import android.bluetooth.BluetoothStatusCodes;
 import android.bluetooth.hfp.BluetoothHfpProtoEnums;
 import android.content.Intent;
@@ -43,6 +44,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 com.android.internal.util.State;
 import com.android.internal.util.StateMachine;
@@ -134,6 +136,7 @@
     private final AdapterService mAdapterService;
     private final HeadsetNativeInterface mNativeInterface;
     private final HeadsetSystemInterface mSystemInterface;
+    private DatabaseManager mDatabaseManager;
 
     // Runtime states
     @VisibleForTesting
@@ -155,6 +158,10 @@
     // Audio disconnect timeout retry count
     private int mAudioDisconnectRetry = 0;
 
+    static final int HFP_SET_AUDIO_POLICY = 1;
+
+    private BluetoothSinkAudioPolicy mHsClientAudioPolicy;
+
     // Keys are AT commands, and values are the company IDs.
     private static final Map<String, Integer> VENDOR_SPECIFIC_AT_COMMAND_COMPANY_ID;
 
@@ -187,7 +194,22 @@
         mSystemInterface =
                 Objects.requireNonNull(systemInterface, "systemInterface cannot be null");
         mAdapterService = Objects.requireNonNull(adapterService, "AdapterService cannot be null");
+        mDatabaseManager = Objects.requireNonNull(
+            AdapterService.getAdapterService().getDatabase(),
+            "DatabaseManager cannot be null when HeadsetClientStateMachine is created");
         mDeviceSilenced = false;
+
+        BluetoothSinkAudioPolicy storedAudioPolicy =
+                mDatabaseManager.getAudioPolicyMetadata(device);
+        if (storedAudioPolicy == null) {
+            Log.w(TAG, "Audio Policy not created in database! Creating...");
+            mHsClientAudioPolicy = new BluetoothSinkAudioPolicy.Builder().build();
+            mDatabaseManager.setAudioPolicyMetadata(device, mHsClientAudioPolicy);
+        } else {
+            Log.i(TAG, "Audio Policy found in database!");
+            mHsClientAudioPolicy = storedAudioPolicy;
+        }
+
         // Create phonebook helper
         mPhonebook = new AtPhonebook(mHeadsetService, mNativeInterface);
         // Initialize state machine
@@ -241,6 +263,8 @@
         ProfileService.println(sb, "  mMicVolume: " + mMicVolume);
         ProfileService.println(sb,
                 "  mConnectingTimestampMs(uptimeMillis): " + mConnectingTimestampMs);
+        ProfileService.println(sb, "  mHsClientAudioPolicy: " + mHsClientAudioPolicy.toString());
+
         ProfileService.println(sb, "  StateMachine: " + this);
         // Dump the state machine logs
         StringWriter stringWriter = new StringWriter();
@@ -1909,6 +1933,119 @@
     }
 
     /**
+     * Process Android specific AT commands.
+     *
+     * @param atString AT command after the "AT+" prefix. Starts with "ANDROID"
+     * @param device Remote device that has sent this command
+     */
+    private void processAndroidAt(String atString, BluetoothDevice device) {
+        log("processAndroidSpecificAt - atString = " + atString);
+
+        if (atString.equals("+ANDROID=?")) {
+            // feature request type command
+            processAndroidAtFeatureRequest(device);
+        } else if (atString.startsWith("+ANDROID=")) {
+            // set type command
+            int equalIndex = atString.indexOf("=");
+            String arg = atString.substring(equalIndex + 1);
+
+            if (arg.isEmpty()) {
+                Log.e(TAG, "Command Invalid!");
+                mNativeInterface.atResponseCode(device, HeadsetHalConstants.AT_RESPONSE_ERROR, 0);
+                return;
+            }
+
+            Object[] args = generateArgs(arg);
+
+            if (!(args[0] instanceof Integer)) {
+                Log.e(TAG, "Type ID is invalid");
+                mNativeInterface.atResponseCode(device, HeadsetHalConstants.AT_RESPONSE_ERROR, 0);
+                return;
+            }
+
+            int type = (Integer) args[0];
+
+            if (type == HFP_SET_AUDIO_POLICY) {
+                processAndroidAtSetAudioPolicy(args, device);
+            } else {
+                Log.w(TAG, "Undefined AT+ANDROID command");
+                mNativeInterface.atResponseCode(device, HeadsetHalConstants.AT_RESPONSE_ERROR, 0);
+                return;
+            }
+        } else {
+            Log.e(TAG, "Undefined AT+ANDROID command");
+            mNativeInterface.atResponseCode(device, HeadsetHalConstants.AT_RESPONSE_ERROR, 0);
+            return;
+        }
+        mNativeInterface.atResponseCode(device, HeadsetHalConstants.AT_RESPONSE_OK, 0);
+    }
+
+    private void processAndroidAtFeatureRequest(BluetoothDevice device) {
+        /*
+            replying with +ANDROID=1
+            here, 1 is the feature id for audio policy
+
+            currently we only support one type of feature
+        */
+        mNativeInterface.atResponseString(device,
+                BluetoothHeadset.VENDOR_RESULT_CODE_COMMAND_ANDROID
+                + ": " + HFP_SET_AUDIO_POLICY);
+    }
+
+    /**
+     * Process AT+ANDROID AT command
+     *
+     * @param args command arguments after the equal sign
+     * @param device Remote device that has sent this command
+     */
+    private void processAndroidAtSetAudioPolicy(Object[] args, BluetoothDevice device) {
+        if (args.length != 4) {
+            Log.e(TAG, "processAndroidAtSetAudioPolicy() args length must be 4: "
+                    + String.valueOf(args.length));
+            return;
+        }
+        if (!(args[1] instanceof Integer) || !(args[2] instanceof Integer)
+                || !(args[3] instanceof Integer)) {
+            Log.e(TAG, "processAndroidAtSetAudioPolicy() argument types not matched");
+            return;
+        }
+
+        if (!mDevice.equals(device)) {
+            Log.e(TAG, "processAndroidAtSetAudioPolicy(): argument device " + device
+                    + " doesn't match mDevice " + mDevice);
+            return;
+        }
+
+        int callEstablishPolicy = (Integer) args[1];
+        int connectingTimePolicy = (Integer) args[2];
+        int inbandPolicy = (Integer) args[3];
+
+        setHfpCallAudioPolicy(new BluetoothSinkAudioPolicy.Builder()
+                .setCallEstablishPolicy(callEstablishPolicy)
+                .setActiveDevicePolicyAfterConnection(connectingTimePolicy)
+                .setInBandRingtonePolicy(inbandPolicy)
+                .build());
+    }
+
+    /**
+     * sets the audio policy of the client device and stores in the database
+     *
+     * @param policies policies to be set and stored
+     */
+    public void setHfpCallAudioPolicy(BluetoothSinkAudioPolicy policies) {
+        mHsClientAudioPolicy = policies;
+        mDatabaseManager.setAudioPolicyMetadata(mDevice, policies);
+    }
+
+    /**
+     * get the audio policy of the client device
+     *
+     */
+    public BluetoothSinkAudioPolicy getHfpCallAudioPolicy() {
+        return mHsClientAudioPolicy;
+    }
+
+    /**
      * Process AT+XAPL AT command
      *
      * @param args command arguments after the equal sign
@@ -1959,6 +2096,8 @@
             processAtCpbs(atCommand.substring(5), commandType, device);
         } else if (atCommand.startsWith("+CPBR")) {
             processAtCpbr(atCommand.substring(5), commandType, device);
+        } else if (atCommand.startsWith("+ANDROID")) {
+            processAndroidAt(atCommand, device);
         } else {
             processVendorSpecificAt(atCommand, device);
         }
diff --git a/android/app/src/com/android/bluetooth/hfp/HeadsetSystemInterface.java b/android/app/src/com/android/bluetooth/hfp/HeadsetSystemInterface.java
index 08f4bdb..bbf4e36 100644
--- a/android/app/src/com/android/bluetooth/hfp/HeadsetSystemInterface.java
+++ b/android/app/src/com/android/bluetooth/hfp/HeadsetSystemInterface.java
@@ -21,6 +21,7 @@
 import android.annotation.RequiresPermission;
 import android.bluetooth.BluetoothDevice;
 import android.bluetooth.BluetoothHeadset;
+import android.bluetooth.BluetoothSinkAudioPolicy;
 import android.content.ActivityNotFoundException;
 import android.content.ComponentName;
 import android.content.Intent;
@@ -155,13 +156,19 @@
     @VisibleForTesting
     @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
     public void answerCall(BluetoothDevice device) {
+        Log.d(TAG, "answerCall");
         if (device == null) {
             Log.w(TAG, "answerCall device is null");
             return;
         }
         BluetoothInCallService bluetoothInCallService = getBluetoothInCallServiceInstance();
         if (bluetoothInCallService != null) {
-            mHeadsetService.setActiveDevice(device);
+            BluetoothSinkAudioPolicy callAudioPolicy =
+                    mHeadsetService.getHfpCallAudioPolicy(device);
+            if (callAudioPolicy == null || callAudioPolicy.getCallEstablishPolicy()
+                    != BluetoothSinkAudioPolicy.POLICY_NOT_ALLOWED) {
+                mHeadsetService.setActiveDevice(device);
+            }
             bluetoothInCallService.answerCall();
         } else {
             Log.e(TAG, "Handsfree phone proxy null for answering call");
diff --git a/android/app/src/com/android/bluetooth/hfpclient/HeadsetClientService.java b/android/app/src/com/android/bluetooth/hfpclient/HeadsetClientService.java
index db43df5..9221d98 100644
--- a/android/app/src/com/android/bluetooth/hfpclient/HeadsetClientService.java
+++ b/android/app/src/com/android/bluetooth/hfpclient/HeadsetClientService.java
@@ -21,6 +21,8 @@
 import android.bluetooth.BluetoothHeadsetClient;
 import android.bluetooth.BluetoothHeadsetClientCall;
 import android.bluetooth.BluetoothProfile;
+import android.bluetooth.BluetoothSinkAudioPolicy;
+import android.bluetooth.BluetoothStatusCodes;
 import android.bluetooth.IBluetoothHeadsetClient;
 import android.content.AttributionSource;
 import android.content.BroadcastReceiver;
@@ -32,6 +34,7 @@
 import android.os.Bundle;
 import android.os.HandlerThread;
 import android.os.Message;
+import android.os.SystemProperties;
 import android.sysprop.BluetoothProperties;
 import android.util.Log;
 
@@ -59,7 +62,7 @@
  * @hide
  */
 public class HeadsetClientService extends ProfileService {
-    private static final boolean DBG = false;
+    private static final boolean DBG = true;
     private static final String TAG = "HeadsetClientService";
 
     // This is also used as a lock for shared data in {@link HeadsetClientService}
@@ -612,8 +615,10 @@
                 List<BluetoothHeadsetClientCall> defaultValue = new ArrayList<>();
                 if (service != null) {
                     List<HfpClientCall> calls = service.getCurrentCalls(device);
-                    for (HfpClientCall call : calls) {
-                        defaultValue.add(toLegacyCall(call));
+                    if (calls != null) {
+                        for (HfpClientCall call : calls) {
+                            defaultValue.add(toLegacyCall(call));
+                        }
                     }
                 }
                 receiver.send(defaultValue);
@@ -923,6 +928,53 @@
         return false;
     }
 
+    /**
+     * sends the {@link BluetoothSinkAudioPolicy} object to the state machine of the corresponding
+     * device to store and send to the remote device using Android specific AT commands.
+     *
+     * @param device for whom the policies to be set
+     * @param policies to be set policies
+     */
+    public void setAudioPolicy(BluetoothDevice device, BluetoothSinkAudioPolicy policies) {
+        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+        Log.i(TAG, "setAudioPolicy: device=" + device + ", " + policies.toString() + ", "
+                + Utils.getUidPidString());
+        HeadsetClientStateMachine sm = getStateMachine(device);
+        if (sm != null) {
+            sm.setAudioPolicy(policies);
+        }
+    }
+
+    /**
+     * sets the audio policy feature support status for the corresponding device.
+     *
+     * @param device for whom the policies to be set
+     * @param supported support status
+     */
+    public void setAudioPolicyRemoteSupported(BluetoothDevice device, boolean supported) {
+        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+        Log.i(TAG, "setAudioPolicyRemoteSupported: " + supported);
+        HeadsetClientStateMachine sm = getStateMachine(device);
+        if (sm != null) {
+            sm.setAudioPolicyRemoteSupported(supported);
+        }
+    }
+
+    /**
+     * gets the audio policy feature support status for the corresponding device.
+     *
+     * @param device for whom the policies to be set
+     * @return int support status
+     */
+    public int getAudioPolicyRemoteSupported(BluetoothDevice device) {
+        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+        HeadsetClientStateMachine sm = getStateMachine(device);
+        if (sm != null) {
+            return sm.getAudioPolicyRemoteSupported();
+        }
+        return BluetoothStatusCodes.FEATURE_NOT_CONFIGURED;
+    }
+
     public boolean connectAudio(BluetoothDevice device) {
         Log.i(TAG, "connectAudio: device=" + device + ", " + Utils.getUidPidString());
         HeadsetClientStateMachine sm = getStateMachine(device);
@@ -1078,6 +1130,14 @@
             return null;
         }
 
+        // Some platform does not support three way calling (ex: watch)
+        final boolean support_three_way_calling = SystemProperties
+                .getBoolean("bluetooth.headset_client.three_way_calling.enabled", true);
+        if (!support_three_way_calling && !getCurrentCalls(device).isEmpty()) {
+            Log.e(TAG, String.format("dial(%s): Line is busy, reject dialing", device));
+            return null;
+        }
+
         HfpClientCall call = new HfpClientCall(device,
                 HeadsetClientStateMachine.HF_ORIGINATED_CALL_ID,
                 HfpClientCall.CALL_STATE_DIALING, number, false  /* multiparty */,
diff --git a/android/app/src/com/android/bluetooth/hfpclient/HeadsetClientStateMachine.java b/android/app/src/com/android/bluetooth/hfpclient/HeadsetClientStateMachine.java
index 557d74b..804dadd 100644
--- a/android/app/src/com/android/bluetooth/hfpclient/HeadsetClientStateMachine.java
+++ b/android/app/src/com/android/bluetooth/hfpclient/HeadsetClientStateMachine.java
@@ -41,6 +41,8 @@
 import android.bluetooth.BluetoothHeadsetClient;
 import android.bluetooth.BluetoothHeadsetClient.NetworkServiceState;
 import android.bluetooth.BluetoothProfile;
+import android.bluetooth.BluetoothSinkAudioPolicy;
+import android.bluetooth.BluetoothStatusCodes;
 import android.bluetooth.BluetoothUuid;
 import android.bluetooth.hfp.BluetoothHfpProtoEnums;
 import android.content.Intent;
@@ -52,6 +54,7 @@
 import android.os.Message;
 import android.os.ParcelUuid;
 import android.os.SystemClock;
+import android.os.SystemProperties;
 import android.util.Log;
 import android.util.Pair;
 
@@ -62,6 +65,7 @@
 import com.android.bluetooth.btservice.AdapterService;
 import com.android.bluetooth.btservice.MetricsLogger;
 import com.android.bluetooth.btservice.ProfileService;
+import com.android.bluetooth.hfp.HeadsetService;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.IState;
 import com.android.internal.util.State;
@@ -108,6 +112,7 @@
     public static final int DISABLE_NREC = 20;
     public static final int SEND_VENDOR_AT_COMMAND = 21;
     public static final int SEND_BIEV = 22;
+    public static final int SEND_ANDROID_AT_COMMAND = 23;
 
     // internal actions
     @VisibleForTesting
@@ -145,6 +150,7 @@
     private long mClccTimer = 0;
 
     private final HeadsetClientService mService;
+    private final HeadsetService mHeadsetService;
 
     // Set of calls that represent the accurate state of calls that exists on AG and the calls that
     // are currently in process of being notified to the AG from HF.
@@ -179,6 +185,12 @@
     int mAudioState;
     // Indicates whether audio can be routed to the device
     private boolean mAudioRouteAllowed;
+
+    private static final int CALL_AUDIO_POLICY_FEATURE_ID = 1;
+
+    public int mAudioPolicyRemoteSupported;
+    private BluetoothSinkAudioPolicy mHsClientAudioPolicy;
+
     private boolean mAudioWbs;
     private int mVoiceRecognitionActive;
     private final BluetoothAdapter mAdapter;
@@ -226,6 +238,8 @@
         ProfileService.println(sb, "  mOperatorName: " + mOperatorName);
         ProfileService.println(sb, "  mSubscriberInfo: " + mSubscriberInfo);
         ProfileService.println(sb, "  mAudioRouteAllowed: " + mAudioRouteAllowed);
+        ProfileService.println(sb, "  mAudioPolicyRemoteSupported: " + mAudioPolicyRemoteSupported);
+        ProfileService.println(sb, "  mHsClientAudioPolicy: " + mHsClientAudioPolicy);
 
         ProfileService.println(sb, "  mCalls:");
         if (mCalls != null) {
@@ -529,7 +543,12 @@
         }
 
         if (mCalls.size() > 0) {
-            if (mService.getResources().getBoolean(R.bool.hfp_clcc_poll_during_call)) {
+            // Continue polling even if not enabled until the new outgoing call is associated with
+            // a valid call on the phone. The polling would at most continue until
+            // OUTGOING_TIMEOUT_MILLI. This handles the potential scenario where the phone creates
+            // and terminates a call before the first QUERY_CURRENT_CALLS completes.
+            if (mService.getResources().getBoolean(R.bool.hfp_clcc_poll_during_call)
+                    || (mCalls.containsKey(HF_ORIGINATED_CALL_ID))) {
                 sendMessageDelayed(QUERY_CURRENT_CALLS,
                         mService.getResources().getInteger(
                         R.integer.hfp_clcc_poll_interval_during_call));
@@ -844,12 +863,13 @@
         return (bitfield & mask) == mask;
     }
 
-    HeadsetClientStateMachine(HeadsetClientService context, Looper looper,
-                              NativeInterface nativeInterface) {
+    HeadsetClientStateMachine(HeadsetClientService context, HeadsetService headsetService,
+                              Looper looper, NativeInterface nativeInterface) {
         super(TAG, looper);
         mService = context;
         mNativeInterface = nativeInterface;
         mAudioManager = mService.getAudioManager();
+        mHeadsetService = headsetService;
 
         mVendorProcessor = new VendorCommandResponseProcessor(mService, mNativeInterface);
 
@@ -861,6 +881,8 @@
         mAudioRouteAllowed = context.getResources().getBoolean(
             R.bool.headset_client_initial_audio_route_allowed);
 
+        mHsClientAudioPolicy = new BluetoothSinkAudioPolicy.Builder().build();
+
         mIndicatorNetworkState = HeadsetClientHalConstants.NETWORK_STATE_NOT_AVAILABLE;
         mIndicatorNetworkType = HeadsetClientHalConstants.SERVICE_TYPE_HOME;
         mIndicatorNetworkSignal = 0;
@@ -891,11 +913,12 @@
         setInitialState(mDisconnected);
     }
 
-    static HeadsetClientStateMachine make(HeadsetClientService context, Looper looper,
-                                          NativeInterface nativeInterface) {
+    static HeadsetClientStateMachine make(HeadsetClientService context,
+                                          HeadsetService headsetService,
+                                          Looper looper, NativeInterface nativeInterface) {
         logD("make");
-        HeadsetClientStateMachine hfcsm = new HeadsetClientStateMachine(context, looper,
-                                                                        nativeInterface);
+        HeadsetClientStateMachine hfcsm = new HeadsetClientStateMachine(context, headsetService,
+                                                                        looper, nativeInterface);
         hfcsm.start();
         return hfcsm;
     }
@@ -1004,6 +1027,9 @@
                 Log.e(TAG, "Disconnected: Illegal state transition from " + mPrevState.getName()
                         + " to Disconnected, mCurrentDevice=" + mCurrentDevice);
             }
+            if (mHeadsetService != null && mCurrentDevice != null) {
+                mHeadsetService.updateInbandRinging(mCurrentDevice, false);
+            }
             mCurrentDevice = null;
         }
 
@@ -1143,6 +1169,40 @@
                             deferMessage(message);
                             break;
                         case StackEvent.EVENT_TYPE_CMD_RESULT:
+                            logD("Connecting: CMD_RESULT valueInt:" + event.valueInt
+                                    + " mQueuedActions.size=" + mQueuedActions.size());
+                            if (!mQueuedActions.isEmpty()) {
+                                logD("queuedAction:" + mQueuedActions.peek().first);
+                            }
+                            Pair<Integer, Object> queuedAction = mQueuedActions.poll();
+                            if (queuedAction == null || queuedAction.first == NO_ACTION) {
+                                break;
+                            }
+                            switch (queuedAction.first) {
+                                case SEND_ANDROID_AT_COMMAND:
+                                    if (event.valueInt == StackEvent.CMD_RESULT_TYPE_OK) {
+                                        Log.w(TAG, "Received OK instead of +ANDROID");
+                                    } else {
+                                        Log.w(TAG, "Received ERROR instead of +ANDROID");
+                                    }
+                                    setAudioPolicyRemoteSupported(false);
+                                    transitionTo(mConnected);
+                                    break;
+                                default:
+                                    Log.w(TAG, "Ignored CMD Result");
+                                    break;
+                            }
+                            break;
+
+                        case StackEvent.EVENT_TYPE_UNKNOWN_EVENT:
+                            if (mVendorProcessor.processEvent(event.valueString, event.device)) {
+                                mQueuedActions.poll();
+                                transitionTo(mConnected);
+                            } else {
+                                Log.e(TAG, "Unknown event :" + event.valueString
+                                        + " for device " + event.device);
+                            }
+                            break;
                         case StackEvent.EVENT_TYPE_SUBSCRIBER_INFO:
                         case StackEvent.EVENT_TYPE_CURRENT_CALLS:
                         case StackEvent.EVENT_TYPE_OPERATOR_NAME:
@@ -1206,7 +1266,11 @@
                             mAudioManager.isMicrophoneMute() ? 0 : 15, 0));
                     // query subscriber info
                     deferMessage(obtainMessage(HeadsetClientStateMachine.SUBSCRIBER_INFO));
-                    transitionTo(mConnected);
+
+                    if (!queryRemoteSupportedFeatures()) {
+                        Log.w(TAG, "Couldn't query Android AT remote supported!");
+                        transitionTo(mConnected);
+                    }
                     break;
 
                 case HeadsetClientHalConstants.CONNECTION_STATE_CONNECTED:
@@ -1253,6 +1317,9 @@
             if (mPrevState == mConnecting) {
                 broadcastConnectionState(mCurrentDevice, BluetoothProfile.STATE_CONNECTED,
                         BluetoothProfile.STATE_CONNECTING);
+                if (mHeadsetService != null) {
+                    mHeadsetService.updateInbandRinging(mCurrentDevice, true);
+                }
                 MetricsLogger.logProfileConnectionEvent(
                         BluetoothMetricsProto.ProfileId.HEADSET_CLIENT);
             } else if (mPrevState != mAudioOn) {
@@ -1547,8 +1614,11 @@
                             if (event.valueInt == HeadsetClientHalConstants.VOLUME_TYPE_SPK) {
                                 mCommandedSpeakerVolume = hfToAmVol(event.valueInt2);
                                 logD("AM volume set to " + mCommandedSpeakerVolume);
+                                boolean show_volume = SystemProperties
+                                        .getBoolean("bluetooth.hfp_volume_control.enabled", true);
                                 mAudioManager.setStreamVolume(AudioManager.STREAM_VOICE_CALL,
-                                        +mCommandedSpeakerVolume, AudioManager.FLAG_SHOW_UI);
+                                        +mCommandedSpeakerVolume,
+                                        show_volume ? AudioManager.FLAG_SHOW_UI : 0);
                             } else if (event.valueInt
                                     == HeadsetClientHalConstants.VOLUME_TYPE_MIC) {
                                 mAudioManager.setMicrophoneMute(event.valueInt2 == 0);
@@ -1588,6 +1658,8 @@
                                                 oldState, mVoiceRecognitionActive);
                                     }
                                     break;
+                                case SEND_ANDROID_AT_COMMAND:
+                                    logD("Connected: Received OK for AT+ANDROID");
                                 default:
                                     Log.w(TAG, "Unhandled AT OK " + event);
                                     break;
@@ -2089,9 +2161,81 @@
 
     public void setAudioRouteAllowed(boolean allowed) {
         mAudioRouteAllowed = allowed;
+
+        int establishPolicy = allowed
+                ? BluetoothSinkAudioPolicy.POLICY_ALLOWED :
+                BluetoothSinkAudioPolicy.POLICY_NOT_ALLOWED;
+
+        /*
+         * Backward compatibility for mAudioRouteAllowed
+         */
+        setAudioPolicy(new BluetoothSinkAudioPolicy.Builder(mHsClientAudioPolicy)
+                .setCallEstablishPolicy(establishPolicy).build());
     }
 
     public boolean getAudioRouteAllowed() {
         return mAudioRouteAllowed;
     }
+
+    private String createMaskString(BluetoothSinkAudioPolicy policies) {
+        StringBuilder mask = new StringBuilder();
+        mask.append(Integer.toString(CALL_AUDIO_POLICY_FEATURE_ID));
+        mask.append("," + policies.getCallEstablishPolicy());
+        mask.append("," + policies.getActiveDevicePolicyAfterConnection());
+        mask.append("," + policies.getInBandRingtonePolicy());
+        return mask.toString();
+    }
+
+    /**
+     * sets the {@link BluetoothSinkAudioPolicy} object device and send to the remote
+     * device using Android specific AT commands.
+     *
+     * @param policies to be set policies
+     */
+    public void setAudioPolicy(BluetoothSinkAudioPolicy policies) {
+        logD("setAudioPolicy: " + policies);
+        mHsClientAudioPolicy = policies;
+
+        if (mAudioPolicyRemoteSupported != BluetoothStatusCodes.FEATURE_SUPPORTED) {
+            Log.e(TAG, "Audio Policy feature not supported!");
+            return;
+        }
+
+        if (!mNativeInterface.sendAndroidAt(mCurrentDevice,
+                "+ANDROID=" + createMaskString(policies))) {
+            Log.e(TAG, "ERROR: Couldn't send call audio policies");
+        }
+    }
+
+    private boolean queryRemoteSupportedFeatures() {
+        Log.i(TAG, "queryRemoteSupportedFeatures");
+        if (!mNativeInterface.sendAndroidAt(mCurrentDevice, "+ANDROID=?")) {
+            Log.e(TAG, "ERROR: Couldn't send audio policy feature query");
+            return false;
+        }
+        addQueuedAction(SEND_ANDROID_AT_COMMAND);
+        return true;
+    }
+
+    /**
+     * sets the audio policy feature support status
+     *
+     * @param supported support status
+     */
+    public void setAudioPolicyRemoteSupported(boolean supported) {
+        if (supported) {
+            mAudioPolicyRemoteSupported = BluetoothStatusCodes.FEATURE_SUPPORTED;
+        } else {
+            mAudioPolicyRemoteSupported = BluetoothStatusCodes.FEATURE_NOT_SUPPORTED;
+        }
+    }
+
+    /**
+     * gets the audio policy feature support status
+     *
+     * @return int support status
+     */
+    public int getAudioPolicyRemoteSupported() {
+        return mAudioPolicyRemoteSupported;
+    }
 }
diff --git a/android/app/src/com/android/bluetooth/hfpclient/HeadsetClientStateMachineFactory.java b/android/app/src/com/android/bluetooth/hfpclient/HeadsetClientStateMachineFactory.java
index b0c7265..b21f537 100644
--- a/android/app/src/com/android/bluetooth/hfpclient/HeadsetClientStateMachineFactory.java
+++ b/android/app/src/com/android/bluetooth/hfpclient/HeadsetClientStateMachineFactory.java
@@ -18,6 +18,8 @@
 
 import android.os.HandlerThread;
 
+import com.android.bluetooth.hfp.HeadsetService;
+
 // Factory so that StateMachine objected can be mocked
 public class HeadsetClientStateMachineFactory {
     /**
@@ -26,6 +28,7 @@
      */
     public HeadsetClientStateMachine make(HeadsetClientService context, HandlerThread t,
             NativeInterface nativeInterface) {
-        return HeadsetClientStateMachine.make(context, t.getLooper(), nativeInterface);
+        return HeadsetClientStateMachine.make(context, HeadsetService.getHeadsetService(),
+                t.getLooper(), nativeInterface);
     }
 }
diff --git a/android/app/src/com/android/bluetooth/hfpclient/NativeInterface.java b/android/app/src/com/android/bluetooth/hfpclient/NativeInterface.java
index 1cb2cf7..2615b73 100644
--- a/android/app/src/com/android/bluetooth/hfpclient/NativeInterface.java
+++ b/android/app/src/com/android/bluetooth/hfpclient/NativeInterface.java
@@ -266,6 +266,17 @@
         return sendATCmdNative(getByteAddress(device), atCmd, val1, val2, arg);
     }
 
+    /**
+     * Set call audio policy to the specified paired device
+     *
+     * @param cmd Android specific command string
+     * @return True on success, False on failure
+     */
+    @VisibleForTesting
+    public boolean sendAndroidAt(BluetoothDevice device, String cmd) {
+        return sendAndroidAtNative(getByteAddress(device), cmd);
+    }
+
     // Native methods that call into the JNI interface
     private static native void classInitNative();
 
@@ -306,6 +317,8 @@
     private static native boolean sendATCmdNative(byte[] address, int atCmd, int val1, int val2,
             String arg);
 
+    private static native boolean sendAndroidAtNative(byte[] address, String cmd);
+
     private BluetoothDevice getDevice(byte[] address) {
         return mAdapterService.getDeviceFromByte(address);
     }
diff --git a/android/app/src/com/android/bluetooth/hfpclient/StackEvent.java b/android/app/src/com/android/bluetooth/hfpclient/StackEvent.java
index a0b8ce6..4c08946 100644
--- a/android/app/src/com/android/bluetooth/hfpclient/StackEvent.java
+++ b/android/app/src/com/android/bluetooth/hfpclient/StackEvent.java
@@ -51,6 +51,9 @@
     public static final int EVENT_TYPE_RING_INDICATION = 21;
     public static final int EVENT_TYPE_UNKNOWN_EVENT = 22;
 
+    public static final int CMD_RESULT_TYPE_OK = 0;
+    public static final int CMD_RESULT_TYPE_CME_ERROR = 7;
+
     public int type = EVENT_TYPE_NONE;
     public int valueInt = 0;
     public int valueInt2 = 0;
diff --git a/android/app/src/com/android/bluetooth/hfpclient/VendorCommandResponseProcessor.java b/android/app/src/com/android/bluetooth/hfpclient/VendorCommandResponseProcessor.java
index 205ba40..cd2ec0d 100644
--- a/android/app/src/com/android/bluetooth/hfpclient/VendorCommandResponseProcessor.java
+++ b/android/app/src/com/android/bluetooth/hfpclient/VendorCommandResponseProcessor.java
@@ -69,6 +69,9 @@
         SUPPORTED_VENDOR_EVENTS.put(
                 "+XAPL=",
                 BluetoothAssignedNumbers.APPLE);
+        SUPPORTED_VENDOR_EVENTS.put(
+                "+ANDROID:",
+                BluetoothAssignedNumbers.GOOGLE);
     }
 
     VendorCommandResponseProcessor(HeadsetClientService context, NativeInterface nativeInterface) {
@@ -148,10 +151,14 @@
         if (vendorId == null) {
             Log.e(TAG, "Invalid response: " + atString + ". " + eventCode);
             return false;
+        } else if (vendorId == BluetoothAssignedNumbers.GOOGLE) {
+            Log.i(TAG, "received +ANDROID event. Setting Audio policy to true");
+            mService.setAudioPolicyRemoteSupported(device, true);
+        } else {
+            broadcastVendorSpecificEventIntent(vendorId, eventCode, atString, device);
+            logD("process vendor event " + vendorId + ", " + eventCode + ", "
+                    + atString + " for device" + device);
         }
-        broadcastVendorSpecificEventIntent(vendorId, eventCode, atString, device);
-        logD("process vendor event " + vendorId + ", " + eventCode + ", "
-                + atString + " for device" + device);
         return true;
     }
 
diff --git a/android/app/src/com/android/bluetooth/le_audio/LeAudioService.java b/android/app/src/com/android/bluetooth/le_audio/LeAudioService.java
index 42243ab..9921c33 100644
--- a/android/app/src/com/android/bluetooth/le_audio/LeAudioService.java
+++ b/android/app/src/com/android/bluetooth/le_audio/LeAudioService.java
@@ -77,7 +77,6 @@
 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.
@@ -91,7 +90,7 @@
     private static final int SM_THREAD_JOIN_TIMEOUT_MS = 1000;
 
     // Upper limit of all LeAudio devices: Bonded or Connected
-    private static final int MAX_LE_AUDIO_STATE_MACHINES = 10;
+    private static final int MAX_LE_AUDIO_DEVICES = 10;
     private static LeAudioService sLeAudioService;
 
     /**
@@ -156,16 +155,27 @@
         BluetoothDevice mLostLeadDeviceWhileStreaming;
     }
 
+    private static class LeAudioDeviceDescriptor {
+        LeAudioDeviceDescriptor() {
+            mStateMachine = null;
+            mGroupId = LE_AUDIO_GROUP_ID_INVALID;
+            mSinkAudioLocation = BluetoothLeAudio.AUDIO_LOCATION_INVALID;
+            mDirection = AUDIO_DIRECTION_NONE;
+        }
+
+        public LeAudioStateMachine mStateMachine;
+        public Integer mGroupId;
+        public Integer mSinkAudioLocation;
+        public Integer mDirection;
+    }
+
     List<BluetoothLeAudioCodecConfig> mInputLocalCodecCapabilities = new ArrayList<>();
     List<BluetoothLeAudioCodecConfig> mOutputLocalCodecCapabilities = new ArrayList<>();
 
     @GuardedBy("mGroupLock")
     private final Map<Integer, LeAudioGroupDescriptor> mGroupDescriptors = new LinkedHashMap<>();
-    private final Map<BluetoothDevice, LeAudioStateMachine> mStateMachines = new LinkedHashMap<>();
-
-    @GuardedBy("mGroupLock")
-    private final Map<BluetoothDevice, Integer> mDeviceGroupIdMap = new ConcurrentHashMap<>();
-    private final Map<BluetoothDevice, Integer> mDeviceAudioLocationMap = new ConcurrentHashMap<>();
+    private final Map<BluetoothDevice, LeAudioDeviceDescriptor> mDeviceDescriptors =
+            new LinkedHashMap<>();
 
     private BroadcastReceiver mBondStateChangedReceiver;
     private BroadcastReceiver mConnectionStateChangedReceiver;
@@ -219,17 +229,15 @@
                 "AudioManager cannot be null when LeAudioService starts");
 
         // Start handler thread for state machines
-        mStateMachines.clear();
         mStateMachinesThread = new HandlerThread("LeAudioService.StateMachines");
         mStateMachinesThread.start();
 
-        mDeviceAudioLocationMap.clear();
         mBroadcastStateMap.clear();
         mBroadcastMetadataList.clear();
         mBroadcastsPlaybackMap.clear();
 
         synchronized (mGroupLock) {
-            mDeviceGroupIdMap.clear();
+            mDeviceDescriptors.clear();
             mGroupDescriptors.clear();
         }
 
@@ -326,7 +334,18 @@
                     break;
                 }
             }
-            mDeviceGroupIdMap.clear();
+
+            // Destroy state machines and stop handler thread
+            for (LeAudioDeviceDescriptor descriptor : mDeviceDescriptors.values()) {
+                LeAudioStateMachine sm = descriptor.mStateMachine;
+                if (sm == null) {
+                    continue;
+                }
+                sm.doQuit();
+                sm.cleanup();
+            }
+
+            mDeviceDescriptors.clear();
             mGroupDescriptors.clear();
         }
 
@@ -339,7 +358,6 @@
 
         mActiveAudioOutDevice = null;
         mActiveAudioInDevice = null;
-        mDatabaseManager = null;
         mLeAudioCodecConfig = null;
 
         // Set the service and BLE devices as inactive
@@ -353,16 +371,6 @@
         unregisterReceiver(mMuteStateChangedReceiver);
         mMuteStateChangedReceiver = null;
 
-        // Destroy state machines and stop handler thread
-        synchronized (mStateMachines) {
-            for (LeAudioStateMachine sm : mStateMachines.values()) {
-                sm.doQuit();
-                sm.cleanup();
-            }
-            mStateMachines.clear();
-        }
-
-        mDeviceAudioLocationMap.clear();
 
         if (mBroadcastCallbacks != null) {
             mBroadcastCallbacks.kill();
@@ -440,6 +448,27 @@
         return mVolumeControlService.getAudioDeviceGroupVolume(groupId);
     }
 
+    LeAudioDeviceDescriptor createDeviceDescriptor(BluetoothDevice device) {
+        LeAudioDeviceDescriptor descriptor = mDeviceDescriptors.get(device);
+        if (descriptor == null) {
+
+            // Limit the maximum number of devices to avoid DoS attack
+            if (mDeviceDescriptors.size() >= MAX_LE_AUDIO_DEVICES) {
+                Log.e(TAG, "Maximum number of LeAudio state machines reached: "
+                        + MAX_LE_AUDIO_DEVICES);
+                return null;
+            }
+
+            mDeviceDescriptors.put(device, new LeAudioDeviceDescriptor());
+            descriptor = mDeviceDescriptors.get(device);
+            Log.d(TAG, "Created descriptor for device: " + device);
+        } else {
+            Log.w(TAG, "Device: " + device + ", already exists");
+        }
+
+        return descriptor;
+    }
+
     public boolean connect(BluetoothDevice device) {
         if (DBG) {
             Log.d(TAG, "connect(): " + device);
@@ -455,7 +484,11 @@
             return false;
         }
 
-        synchronized (mStateMachines) {
+        synchronized (mGroupLock) {
+            if (createDeviceDescriptor(device) == null) {
+                return false;
+            }
+
             LeAudioStateMachine sm = getOrCreateStateMachine(device);
             if (sm == null) {
                 Log.e(TAG, "Ignored connect request for " + device + " : no state machine");
@@ -478,9 +511,14 @@
             Log.d(TAG, "disconnect(): " + device);
         }
 
-        // Disconnect this device
-        synchronized (mStateMachines) {
-            LeAudioStateMachine sm = mStateMachines.get(device);
+        synchronized (mGroupLock) {
+            LeAudioDeviceDescriptor descriptor = getDeviceDescriptor(device);
+            if (descriptor == null) {
+                Log.e(TAG, "disconnect: No valid descriptor for device: " + device);
+                return false;
+            }
+
+            LeAudioStateMachine sm = descriptor.mStateMachine;
             if (sm == null) {
                 Log.e(TAG, "Ignored disconnect request for " + device
                         + " : no state machine");
@@ -493,10 +531,11 @@
     }
 
     public List<BluetoothDevice> getConnectedDevices() {
-        synchronized (mStateMachines) {
+        synchronized (mGroupLock) {
             List<BluetoothDevice> devices = new ArrayList<>();
-            for (LeAudioStateMachine sm : mStateMachines.values()) {
-                if (sm.isConnected()) {
+            for (LeAudioDeviceDescriptor descriptor : mDeviceDescriptors.values()) {
+                LeAudioStateMachine sm = descriptor.mStateMachine;
+                if (sm != null && sm.isConnected()) {
                     devices.add(sm.getDevice());
                 }
             }
@@ -505,11 +544,31 @@
     }
 
     BluetoothDevice getConnectedGroupLeadDevice(int groupId) {
+        BluetoothDevice device = null;
+
         if (mActiveAudioOutDevice != null
                 && getGroupId(mActiveAudioOutDevice) == groupId) {
-            return mActiveAudioOutDevice;
+            device = mActiveAudioOutDevice;
+        } else {
+            device = getFirstDeviceFromGroup(groupId);
         }
-        return getFirstDeviceFromGroup(groupId);
+
+        if (device == null) {
+            return device;
+        }
+
+        LeAudioDeviceDescriptor descriptor = getDeviceDescriptor(device);
+        if (descriptor == null) {
+            Log.e(TAG, "getConnectedGroupLeadDevice: No valid descriptor for device: " + device);
+            return null;
+        }
+
+        LeAudioStateMachine sm = descriptor.mStateMachine;
+        if (sm != null && sm.getConnectionState() == BluetoothProfile.STATE_CONNECTED) {
+            return device;
+        }
+
+        return null;
     }
 
     List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
@@ -521,14 +580,21 @@
         if (bondedDevices == null) {
             return devices;
         }
-        synchronized (mStateMachines) {
+        synchronized (mGroupLock) {
             for (BluetoothDevice device : bondedDevices) {
                 final ParcelUuid[] featureUuids = device.getUuids();
                 if (!Utils.arrayContains(featureUuids, BluetoothUuid.LE_AUDIO)) {
                     continue;
                 }
                 int connectionState = BluetoothProfile.STATE_DISCONNECTED;
-                LeAudioStateMachine sm = mStateMachines.get(device);
+                LeAudioDeviceDescriptor descriptor = getDeviceDescriptor(device);
+                if (descriptor == null) {
+                    Log.e(TAG, "getDevicesMatchingConnectionStates: "
+                            + "No valid descriptor for device: " + device);
+                    return null;
+                }
+
+                LeAudioStateMachine sm = descriptor.mStateMachine;
                 if (sm != null) {
                     connectionState = sm.getConnectionState();
                 }
@@ -551,9 +617,11 @@
     @VisibleForTesting
     List<BluetoothDevice> getDevices() {
         List<BluetoothDevice> devices = new ArrayList<>();
-        synchronized (mStateMachines) {
-            for (LeAudioStateMachine sm : mStateMachines.values()) {
-                devices.add(sm.getDevice());
+        synchronized (mGroupLock) {
+            for (LeAudioDeviceDescriptor descriptor : mDeviceDescriptors.values()) {
+                if (descriptor.mStateMachine != null) {
+                    devices.add(descriptor.mStateMachine.getDevice());
+                }
             }
             return devices;
         }
@@ -569,8 +637,13 @@
      * {@link BluetoothProfile#STATE_DISCONNECTING} if this profile is being disconnected
      */
     public int getConnectionState(BluetoothDevice device) {
-        synchronized (mStateMachines) {
-            LeAudioStateMachine sm = mStateMachines.get(device);
+        synchronized (mGroupLock) {
+            LeAudioDeviceDescriptor descriptor = getDeviceDescriptor(device);
+            if (descriptor == null) {
+                return BluetoothProfile.STATE_DISCONNECTED;
+            }
+
+            LeAudioStateMachine sm = descriptor.mStateMachine;
             if (sm == null) {
                 return BluetoothProfile.STATE_DISCONNECTED;
             }
@@ -611,8 +684,10 @@
      * @param group_id group Id to verify
      * @return true given group exists, otherwise false
      */
-    public boolean isValidDeviceGroup(int group_id) {
-        return group_id != LE_AUDIO_GROUP_ID_INVALID && mDeviceGroupIdMap.containsValue(group_id);
+    public boolean isValidDeviceGroup(int groupId) {
+        synchronized (mGroupLock) {
+            return groupId != LE_AUDIO_GROUP_ID_INVALID && mGroupDescriptors.containsKey(groupId);
+        }
     }
 
     /**
@@ -628,8 +703,9 @@
         }
 
         synchronized (mGroupLock) {
-            for (Map.Entry<BluetoothDevice, Integer> entry : mDeviceGroupIdMap.entrySet()) {
-                if (entry.getValue() == groupId) {
+            for (Map.Entry<BluetoothDevice, LeAudioDeviceDescriptor> entry
+                    : mDeviceDescriptors.entrySet()) {
+                if (entry.getValue().mGroupId == groupId) {
                     result.add(entry.getKey());
                 }
             }
@@ -773,15 +849,16 @@
             return null;
         }
         synchronized (mGroupLock) {
-            for (Map.Entry<BluetoothDevice, Integer> entry : mDeviceGroupIdMap.entrySet()) {
-                if (entry.getValue() != groupId) {
+            for (LeAudioDeviceDescriptor descriptor : mDeviceDescriptors.values()) {
+                if (!descriptor.mGroupId.equals(groupId)) {
                     continue;
                 }
-                LeAudioStateMachine sm = mStateMachines.get(entry.getKey());
+
+                LeAudioStateMachine sm = descriptor.mStateMachine;
                 if (sm == null || sm.getConnectionState() != BluetoothProfile.STATE_CONNECTED) {
                     continue;
                 }
-                return entry.getKey();
+                return sm.getDevice();
             }
         }
         return null;
@@ -803,8 +880,13 @@
         }
 
         if (device != null && mActiveAudioInDevice != null) {
-            int previousGroupId = getGroupId(mActiveAudioInDevice);
-            if (previousGroupId == groupId) {
+            LeAudioDeviceDescriptor deviceDescriptor = getDeviceDescriptor(device);
+            if (deviceDescriptor == null) {
+                Log.e(TAG, "updateActiveInDevice: No valid descriptor for device: " + device);
+                return false;
+            }
+
+            if (deviceDescriptor.mGroupId.equals(groupId)) {
                 /* This is thes same group as aleady notified to the system.
                  * Therefore do not change the device we have connected to the group,
                  * unless, previous one is disconnected now
@@ -812,9 +894,9 @@
                 if (mActiveAudioInDevice.isConnected()) {
                     device = mActiveAudioInDevice;
                 }
-            } else if (previousGroupId != LE_AUDIO_GROUP_ID_INVALID) {
+            } else if (deviceDescriptor.mGroupId != LE_AUDIO_GROUP_ID_INVALID) {
                 /* Mark old group as no active */
-                LeAudioGroupDescriptor descriptor = getGroupDescriptor(previousGroupId);
+                LeAudioGroupDescriptor descriptor = getGroupDescriptor(deviceDescriptor.mGroupId);
                 if (descriptor != null) {
                     descriptor.mIsActive = false;
                 }
@@ -860,8 +942,13 @@
         }
 
         if (device != null && mActiveAudioOutDevice != null) {
-            int previousGroupId = getGroupId(mActiveAudioOutDevice);
-            if (previousGroupId == groupId) {
+            LeAudioDeviceDescriptor deviceDescriptor = getDeviceDescriptor(device);
+            if (deviceDescriptor == null) {
+                Log.e(TAG, "updateActiveOutDevice: No valid descriptor for device: " + device);
+                return false;
+            }
+
+            if (deviceDescriptor.mGroupId.equals(groupId)) {
                 /* This is the same group as already notified to the system.
                  * Therefore do not change the device we have connected to the group,
                  * unless, previous one is disconnected now
@@ -869,10 +956,11 @@
                 if (mActiveAudioOutDevice.isConnected()) {
                     device = mActiveAudioOutDevice;
                 }
-            } else if (previousGroupId != LE_AUDIO_GROUP_ID_INVALID) {
-                Log.i(TAG, " Switching active group from " + previousGroupId + " to " + groupId);
+            } else if (deviceDescriptor.mGroupId != LE_AUDIO_GROUP_ID_INVALID) {
+                Log.i(TAG, " Switching active group from " + deviceDescriptor.mGroupId + " to "
+                        + groupId);
                 /* Mark old group as no active */
-                LeAudioGroupDescriptor descriptor = getGroupDescriptor(previousGroupId);
+                LeAudioGroupDescriptor descriptor = getGroupDescriptor(deviceDescriptor.mGroupId);
                 if (descriptor != null) {
                     descriptor.mIsActive = false;
                 }
@@ -1034,7 +1122,13 @@
         int groupId = LE_AUDIO_GROUP_ID_INVALID;
 
         if (device != null) {
-            groupId = mDeviceGroupIdMap.getOrDefault(device, LE_AUDIO_GROUP_ID_INVALID);
+            LeAudioDeviceDescriptor descriptor = getDeviceDescriptor(device);
+            if (descriptor == null) {
+                Log.e(TAG, "setActiveGroupWithDevice: No valid descriptor for device: " + device);
+                return;
+            }
+
+            groupId = descriptor.mGroupId;
         }
 
         int currentlyActiveGroupId = getActiveGroupId();
@@ -1129,21 +1223,30 @@
     }
 
     void connectSet(BluetoothDevice device) {
-        int groupId = getGroupId(device);
-        if (groupId == LE_AUDIO_GROUP_ID_INVALID) {
+        LeAudioDeviceDescriptor descriptor = getDeviceDescriptor(device);
+        if (descriptor == null) {
+            Log.e(TAG, "connectSet: No valid descriptor for device: " + device);
+            return;
+        }
+        if (descriptor.mGroupId == LE_AUDIO_GROUP_ID_INVALID) {
             return;
         }
 
         if (DBG) {
-            Log.d(TAG, "connect() others from group id: " + groupId);
+            Log.d(TAG, "connect() others from group id: " + descriptor.mGroupId);
         }
 
-        for (BluetoothDevice storedDevice : mDeviceGroupIdMap.keySet()) {
+        Integer setGroupId = descriptor.mGroupId;
+
+        for (Map.Entry<BluetoothDevice, LeAudioDeviceDescriptor> entry
+                : mDeviceDescriptors.entrySet()) {
+            BluetoothDevice storedDevice = entry.getKey();
+            descriptor = entry.getValue();
             if (device.equals(storedDevice)) {
                 continue;
             }
 
-            if (getGroupId(storedDevice) != groupId) {
+            if (!descriptor.mGroupId.equals(setGroupId)) {
                 continue;
             }
 
@@ -1151,7 +1254,7 @@
                 Log.d(TAG, "connect(): " + storedDevice);
             }
 
-            synchronized (mStateMachines) {
+            synchronized (mGroupLock) {
                 LeAudioStateMachine sm = getOrCreateStateMachine(storedDevice);
                 if (sm == null) {
                     Log.e(TAG, "Ignored connect request for " + storedDevice
@@ -1199,8 +1302,15 @@
                 Log.d(TAG, "Clearing lost dev: " + descriptor.mLostLeadDeviceWhileStreaming);
             }
 
-            LeAudioStateMachine sm =
-                    mStateMachines.get(descriptor.mLostLeadDeviceWhileStreaming);
+            LeAudioDeviceDescriptor deviceDescriptor =
+                    getDeviceDescriptor(descriptor.mLostLeadDeviceWhileStreaming);
+            if (deviceDescriptor == null) {
+                Log.e(TAG, "clearLostDevicesWhileStreaming: No valid descriptor for device: "
+                        + descriptor.mLostLeadDeviceWhileStreaming);
+                return;
+            }
+
+            LeAudioStateMachine sm = deviceDescriptor.mStateMachine;
             if (sm != null) {
                 LeAudioStackEvent stackEvent =
                         new LeAudioStackEvent(
@@ -1284,12 +1394,17 @@
     void messageFromNative(LeAudioStackEvent stackEvent) {
         Log.d(TAG, "Message from native: " + stackEvent);
         BluetoothDevice device = stackEvent.device;
-        Intent intent = null;
 
         if (stackEvent.type == LeAudioStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED) {
             // Some events require device state machine
-            synchronized (mStateMachines) {
-                LeAudioStateMachine sm = mStateMachines.get(device);
+            synchronized (mGroupLock) {
+                LeAudioDeviceDescriptor deviceDescriptor = getDeviceDescriptor(device);
+                if (deviceDescriptor == null) {
+                    Log.e(TAG, "messageFromNative: No valid descriptor for device: " + device);
+                    return;
+                }
+
+                LeAudioStateMachine sm = deviceDescriptor.mStateMachine;
                 if (sm != null) {
                     /*
                      * To improve scenario when lead Le Audio device is disconnected for the
@@ -1299,41 +1414,39 @@
                      * the hood and keep using lead device as a audio device indetifier in
                      * the audio framework in order to not stop the stream.
                      */
-                    int groupId = getGroupId(device);
-                    synchronized (mGroupLock) {
-                        LeAudioGroupDescriptor descriptor = mGroupDescriptors.get(groupId);
-                        switch (stackEvent.valueInt1) {
-                            case LeAudioStackEvent.CONNECTION_STATE_DISCONNECTING:
-                            case LeAudioStackEvent.CONNECTION_STATE_DISCONNECTED:
-                                boolean disconnectDueToUnbond =
-                                        (BluetoothDevice.BOND_NONE
-                                                == mAdapterService.getBondState(device));
-                                if (descriptor != null && (Objects.equals(device,
-                                        mActiveAudioOutDevice)
-                                        || Objects.equals(device, mActiveAudioInDevice))
-                                        && (getConnectedPeerDevices(groupId).size() > 1)
-                                        && !disconnectDueToUnbond) {
+                    int groupId = deviceDescriptor.mGroupId;
+                    LeAudioGroupDescriptor descriptor = mGroupDescriptors.get(groupId);
+                    switch (stackEvent.valueInt1) {
+                        case LeAudioStackEvent.CONNECTION_STATE_DISCONNECTING:
+                        case LeAudioStackEvent.CONNECTION_STATE_DISCONNECTED:
+                            boolean disconnectDueToUnbond =
+                                    (BluetoothDevice.BOND_NONE
+                                            == mAdapterService.getBondState(device));
+                            if (descriptor != null && (Objects.equals(device,
+                                    mActiveAudioOutDevice)
+                                    || Objects.equals(device, mActiveAudioInDevice))
+                                    && (getConnectedPeerDevices(groupId).size() > 1)
+                                    && !disconnectDueToUnbond) {
 
-                                    if (DBG) Log.d(TAG, "Adding to lost devices : " + device);
-                                    descriptor.mLostLeadDeviceWhileStreaming = device;
-                                    return;
+                                if (DBG) Log.d(TAG, "Adding to lost devices : " + device);
+                                descriptor.mLostLeadDeviceWhileStreaming = device;
+                                return;
+                            }
+                            break;
+                        case LeAudioStackEvent.CONNECTION_STATE_CONNECTED:
+                        case LeAudioStackEvent.CONNECTION_STATE_CONNECTING:
+                            if (descriptor != null
+                                    && Objects.equals(
+                                            descriptor.mLostLeadDeviceWhileStreaming,
+                                            device)) {
+                                if (DBG) {
+                                    Log.d(TAG, "Removing from lost devices : " + device);
                                 }
-                                break;
-                            case LeAudioStackEvent.CONNECTION_STATE_CONNECTED:
-                            case LeAudioStackEvent.CONNECTION_STATE_CONNECTING:
-                                if (descriptor != null
-                                        && Objects.equals(
-                                                descriptor.mLostLeadDeviceWhileStreaming,
-                                                device)) {
-                                    if (DBG) {
-                                        Log.d(TAG, "Removing from lost devices : " + device);
-                                    }
-                                    descriptor.mLostLeadDeviceWhileStreaming = null;
-                                    /* Try to connect other devices from the group */
-                                    connectSet(device);
-                                }
-                                break;
-                        }
+                                descriptor.mLostLeadDeviceWhileStreaming = null;
+                                /* Try to connect other devices from the group */
+                                connectSet(device);
+                            }
+                            break;
                     }
                 } else {
                     /* state machine does not exist yet */
@@ -1411,26 +1524,36 @@
             int src_audio_location = stackEvent.valueInt4;
             int available_contexts = stackEvent.valueInt5;
 
-            LeAudioGroupDescriptor descriptor = getGroupDescriptor(groupId);
-            if (descriptor != null) {
-                if (descriptor.mIsActive) {
-                    descriptor.mIsActive =
-                            updateActiveDevices(groupId, descriptor.mDirection, direction,
-                            descriptor.mIsActive);
-                    if (!descriptor.mIsActive) {
-                        notifyGroupStatusChanged(groupId, BluetoothLeAudio.GROUP_STATUS_INACTIVE);
+            synchronized (mGroupLock) {
+                LeAudioGroupDescriptor descriptor = getGroupDescriptor(groupId);
+                if (descriptor != null) {
+                    if (descriptor.mIsActive) {
+                        descriptor.mIsActive =
+                                updateActiveDevices(groupId, descriptor.mDirection, direction,
+                                descriptor.mIsActive);
+                        if (!descriptor.mIsActive) {
+                            notifyGroupStatusChanged(groupId,
+                                    BluetoothLeAudio.GROUP_STATUS_INACTIVE);
+                        }
                     }
+                    descriptor.mDirection = direction;
+                } else {
+                    Log.e(TAG, "no descriptors for group: " + groupId);
                 }
-                descriptor.mDirection = direction;
-            } else {
-                Log.e(TAG, "no descriptors for group: " + groupId);
             }
         } else if (stackEvent.type == LeAudioStackEvent.EVENT_TYPE_SINK_AUDIO_LOCATION_AVAILABLE) {
             Objects.requireNonNull(stackEvent.device,
                     "Device should never be null, event: " + stackEvent);
 
             int sink_audio_location = stackEvent.valueInt1;
-            mDeviceAudioLocationMap.put(device, sink_audio_location);
+
+            LeAudioDeviceDescriptor descriptor = getDeviceDescriptor(device);
+            if (descriptor == null) {
+                Log.e(TAG, "messageFromNative: No valid descriptor for device: " + device);
+                return;
+            }
+
+            descriptor.mSinkAudioLocation = sink_audio_location;
 
             if (DBG) {
                 Log.i(TAG, "EVENT_TYPE_SINK_AUDIO_LOCATION_AVAILABLE:" + device
@@ -1558,10 +1681,6 @@
                 setCcidInformation(userUuid, ccidInformation.first, ccidInformation.second);
             }
         }
-
-        if (intent != null) {
-            sendBroadcast(intent, BLUETOOTH_CONNECT);
-        }
     }
 
     private LeAudioStateMachine getOrCreateStateMachine(BluetoothDevice device) {
@@ -1569,25 +1688,26 @@
             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);
+
+        LeAudioDeviceDescriptor descriptor = getDeviceDescriptor(device);
+        if (descriptor == null) {
+            Log.e(TAG, "getOrCreateStateMachine: No valid descriptor for device: " + device);
+            return null;
+        }
+
+        LeAudioStateMachine sm = descriptor.mStateMachine;
+        if (sm != null) {
             return sm;
         }
+
+        if (DBG) {
+            Log.d(TAG, "Creating a new state machine for " + device);
+        }
+
+        sm = LeAudioStateMachine.make(device, this,
+                mLeAudioNativeInterface, mStateMachinesThread.getLooper());
+        descriptor.mStateMachine = sm;
+        return sm;
     }
 
     // Remove state machine if the bonding for a device is removed
@@ -1624,16 +1744,23 @@
             return;
         }
 
-        int groupId = getGroupId(device);
-        if (groupId != LE_AUDIO_GROUP_ID_INVALID) {
-            /* In case device is still in the group, let's remove it */
-            mLeAudioNativeInterface.groupRemoveNode(groupId, device);
-        }
+        synchronized (mGroupLock) {
+            LeAudioDeviceDescriptor descriptor = getDeviceDescriptor(device);
+            if (descriptor == null) {
+                Log.e(TAG, "bondStateChanged: No valid descriptor for device: " + device);
+                return;
+            }
 
-        mDeviceGroupIdMap.remove(device);
-        mDeviceAudioLocationMap.remove(device);
-        synchronized (mStateMachines) {
-            LeAudioStateMachine sm = mStateMachines.get(device);
+            if (descriptor.mGroupId != LE_AUDIO_GROUP_ID_INVALID) {
+                /* In case device is still in the group, let's remove it */
+                mLeAudioNativeInterface.groupRemoveNode(descriptor.mGroupId, device);
+            }
+
+            descriptor.mGroupId = LE_AUDIO_GROUP_ID_INVALID;
+            descriptor.mSinkAudioLocation = BluetoothLeAudio.AUDIO_LOCATION_INVALID;
+            descriptor.mDirection = AUDIO_DIRECTION_NONE;
+
+            LeAudioStateMachine sm = descriptor.mStateMachine;
             if (sm == null) {
                 return;
             }
@@ -1643,12 +1770,19 @@
                 return;
             }
             removeStateMachine(device);
+            mDeviceDescriptors.remove(device);
         }
     }
 
     private void removeStateMachine(BluetoothDevice device) {
-        synchronized (mStateMachines) {
-            LeAudioStateMachine sm = mStateMachines.get(device);
+        synchronized (mGroupLock) {
+            LeAudioDeviceDescriptor descriptor = getDeviceDescriptor(device);
+            if (descriptor == null) {
+                Log.e(TAG, "removeStateMachine: No valid descriptor for device: " + device);
+                return;
+            }
+
+            LeAudioStateMachine sm = descriptor.mStateMachine;
             if (sm == null) {
                 Log.w(TAG, "removeStateMachine: device " + device
                         + " does not have a state machine");
@@ -1657,7 +1791,7 @@
             Log.i(TAG, "removeStateMachine: removing state machine for device: " + device);
             sm.doQuit();
             sm.cleanup();
-            mStateMachines.remove(device);
+            descriptor.mStateMachine = null;
         }
     }
 
@@ -1679,21 +1813,27 @@
                     + " fromState=" + fromState + " toState=" + toState);
             return;
         }
+
+        LeAudioDeviceDescriptor deviceDescriptor = getDeviceDescriptor(device);
+        if (deviceDescriptor == null) {
+            Log.e(TAG, "connectionStateChanged: No valid descriptor for device: " + device);
+            return;
+        }
+
         if (toState == BluetoothProfile.STATE_CONNECTED) {
-            int myGroupId = getGroupId(device);
-            if (myGroupId == LE_AUDIO_GROUP_ID_INVALID
-                    || getConnectedPeerDevices(myGroupId).size() == 1) {
+            if (deviceDescriptor.mGroupId == LE_AUDIO_GROUP_ID_INVALID
+                    || getConnectedPeerDevices(deviceDescriptor.mGroupId).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);
             }
 
-            LeAudioGroupDescriptor descriptor = getGroupDescriptor(myGroupId);
+            LeAudioGroupDescriptor descriptor = getGroupDescriptor(deviceDescriptor.mGroupId);
             if (descriptor != null) {
                 descriptor.mIsConnected = true;
             } else {
-                Log.e(TAG, "no descriptors for group: " + myGroupId);
+                Log.e(TAG, "no descriptors for group: " + deviceDescriptor.mGroupId);
             }
         }
         // Check if the device is disconnected - if unbond, remove the state machine
@@ -1706,14 +1846,14 @@
                 removeStateMachine(device);
             }
 
-            int myGroupId = getGroupId(device);
-            LeAudioGroupDescriptor descriptor = getGroupDescriptor(myGroupId);
+            LeAudioGroupDescriptor descriptor = getGroupDescriptor(deviceDescriptor.mGroupId);
             if (descriptor == null) {
-                Log.e(TAG, "no descriptors for group: " + myGroupId);
+                Log.e(TAG, "no descriptors for group: " + deviceDescriptor.mGroupId);
                 return;
             }
 
-            List<BluetoothDevice> connectedDevices = getConnectedPeerDevices(myGroupId);
+            List<BluetoothDevice> connectedDevices =
+                    getConnectedPeerDevices(deviceDescriptor.mGroupId);
             /* Let's check if the last connected device is really connected */
             if (connectedDevices.size() == 1 && Objects.equals(
                     connectedDevices.get(0), descriptor.mLostLeadDeviceWhileStreaming)) {
@@ -1721,14 +1861,14 @@
                 return;
             }
 
-            if (getConnectedPeerDevices(myGroupId).isEmpty()) {
+            if (getConnectedPeerDevices(deviceDescriptor.mGroupId).isEmpty()) {
                 descriptor.mIsConnected = false;
                 if (descriptor.mIsActive) {
                     /* Notify Native layer */
                     setActiveDevice(null);
                     descriptor.mIsActive = false;
                     /* Update audio framework */
-                    updateActiveDevices(myGroupId,
+                    updateActiveDevices(deviceDescriptor.mGroupId,
                             descriptor.mDirection,
                             descriptor.mDirection,
                             descriptor.mIsActive);
@@ -1737,7 +1877,7 @@
             }
 
             if (descriptor.mIsActive) {
-                updateActiveDevices(myGroupId,
+                updateActiveDevices(deviceDescriptor.mGroupId,
                         descriptor.mDirection,
                         descriptor.mDirection,
                         descriptor.mIsActive);
@@ -1834,8 +1974,14 @@
         if (device == null) {
             return BluetoothLeAudio.AUDIO_LOCATION_INVALID;
         }
-        return mDeviceAudioLocationMap.getOrDefault(device,
-                BluetoothLeAudio.AUDIO_LOCATION_INVALID);
+
+        LeAudioDeviceDescriptor descriptor = getDeviceDescriptor(device);
+        if (descriptor == null) {
+            Log.e(TAG, "getAudioLocation: No valid descriptor for device: " + device);
+            return BluetoothLeAudio.AUDIO_LOCATION_INVALID;
+        }
+
+        return descriptor.mSinkAudioLocation;
     }
 
     /**
@@ -1926,8 +2072,15 @@
         if (device == null) {
             return LE_AUDIO_GROUP_ID_INVALID;
         }
+
         synchronized (mGroupLock) {
-            return mDeviceGroupIdMap.getOrDefault(device, LE_AUDIO_GROUP_ID_INVALID);
+            LeAudioDeviceDescriptor descriptor = getDeviceDescriptor(device);
+            if (descriptor == null) {
+                Log.e(TAG, "getGroupId: No valid descriptor for device: " + device);
+                return LE_AUDIO_GROUP_ID_INVALID;
+            }
+
+            return descriptor.mGroupId;
         }
     }
 
@@ -1999,7 +2152,7 @@
         mBluetoothEnabled = true;
 
         synchronized (mGroupLock) {
-            if (mDeviceGroupIdMap.isEmpty()) {
+            if (mDeviceDescriptors.isEmpty()) {
                 return;
             }
         }
@@ -2011,7 +2164,12 @@
         }
 
         synchronized (mGroupLock) {
-            for (Map.Entry<BluetoothDevice, Integer> entry : mDeviceGroupIdMap.entrySet()) {
+            for (Map.Entry<BluetoothDevice, LeAudioDeviceDescriptor> entry
+                    : mDeviceDescriptors.entrySet()) {
+                if (entry.getValue().mGroupId == LE_AUDIO_GROUP_ID_INVALID) {
+                    continue;
+                }
+
                 mcpService.setDeviceAuthorized(entry.getKey(), true);
             }
         }
@@ -2023,13 +2181,35 @@
         }
     }
 
+    private LeAudioDeviceDescriptor getDeviceDescriptor(BluetoothDevice device) {
+        synchronized (mGroupLock) {
+            return mDeviceDescriptors.get(device);
+        }
+    }
+
     private void handleGroupNodeAdded(BluetoothDevice device, int groupId) {
         synchronized (mGroupLock) {
             if (DBG) {
                 Log.d(TAG, "Device " + device + " added to group " + groupId);
             }
 
-            mDeviceGroupIdMap.put(device, groupId);
+            LeAudioDeviceDescriptor deviceDescriptor = getDeviceDescriptor(device);
+            if (deviceDescriptor == null) {
+                deviceDescriptor = createDeviceDescriptor(device);
+                if (deviceDescriptor == null) {
+                    Log.e(TAG, "handleGroupNodeAdded: Can't create descriptor for added from"
+                            + " storage device: " + device);
+                    return;
+                }
+
+                LeAudioStateMachine sm = getOrCreateStateMachine(device);
+                if (getOrCreateStateMachine(device) == null) {
+                    Log.e(TAG, "Can't get state machine for device: " + device);
+                    return;
+                }
+            }
+            deviceDescriptor.mGroupId = groupId;
+
             LeAudioGroupDescriptor descriptor = mGroupDescriptors.get(groupId);
             if (descriptor == null) {
                 mGroupDescriptors.put(groupId, new LeAudioGroupDescriptor());
@@ -2071,17 +2251,39 @@
             Log.d(TAG, "Removing device " + device + " grom group " + groupId);
         }
 
-        LeAudioGroupDescriptor descriptor = getGroupDescriptor(groupId);
-        if (DBG) {
-            Log.d(TAG, "Lost lead device is " + descriptor.mLostLeadDeviceWhileStreaming);
-        }
-        if (Objects.equals(device, descriptor.mLostLeadDeviceWhileStreaming)) {
-            clearLostDevicesWhileStreaming(descriptor);
-        }
-
         synchronized (mGroupLock) {
-            mDeviceGroupIdMap.remove(device);
-            if (!mDeviceGroupIdMap.containsValue(groupId)) {
+            LeAudioGroupDescriptor groupDescriptor = getGroupDescriptor(groupId);
+            if (DBG) {
+                Log.d(TAG, "Lost lead device is " + groupDescriptor.mLostLeadDeviceWhileStreaming);
+            }
+            if (Objects.equals(device, groupDescriptor.mLostLeadDeviceWhileStreaming)) {
+                clearLostDevicesWhileStreaming(groupDescriptor);
+            }
+
+            LeAudioDeviceDescriptor deviceDescriptor = getDeviceDescriptor(device);
+            if (deviceDescriptor == null) {
+                Log.e(TAG, "handleGroupNodeRemoved: No valid descriptor for device: " + device);
+                return;
+            }
+            deviceDescriptor.mGroupId = LE_AUDIO_GROUP_ID_INVALID;
+
+            boolean isGroupEmpty = true;
+
+            for (LeAudioDeviceDescriptor descriptor : mDeviceDescriptors.values()) {
+                if (descriptor.mGroupId == groupId) {
+                    isGroupEmpty = false;
+                    break;
+                }
+            }
+
+            if (isGroupEmpty) {
+                /* Device is currently an active device. Group needs to be inactivated before
+                 * removing
+                 */
+                if (Objects.equals(device, mActiveAudioOutDevice)
+                        || Objects.equals(device, mActiveAudioInDevice)) {
+                    handleGroupTransitToInactive(groupId);
+                }
                 mGroupDescriptors.remove(groupId);
             }
             notifyGroupNodeRemoved(device, groupId);
@@ -2911,16 +3113,22 @@
     @Override
     public void dump(StringBuilder sb) {
         super.dump(sb);
-        ProfileService.println(sb, "State machines: ");
-        for (LeAudioStateMachine sm : mStateMachines.values()) {
-            sm.dump(sb);
-        }
         ProfileService.println(sb, "Active Groups information: ");
         ProfileService.println(sb, "  currentlyActiveGroupId: " + getActiveGroupId());
         ProfileService.println(sb, "  mActiveAudioOutDevice: " + mActiveAudioOutDevice);
         ProfileService.println(sb, "  mActiveAudioInDevice: " + mActiveAudioInDevice);
         ProfileService.println(sb, "  mHfpHandoverDevice:" + mHfpHandoverDevice);
 
+        for (Map.Entry<BluetoothDevice, LeAudioDeviceDescriptor> entry
+                : mDeviceDescriptors.entrySet()) {
+            LeAudioDeviceDescriptor descriptor = entry.getValue();
+
+            descriptor.mStateMachine.dump(sb);
+            ProfileService.println(sb, "    mGroupId: " + descriptor.mGroupId);
+            ProfileService.println(sb, "    mSinkAudioLocation: " + descriptor.mSinkAudioLocation);
+            ProfileService.println(sb, "    mDirection: " + descriptor.mDirection);
+        }
+
         for (Map.Entry<Integer, LeAudioGroupDescriptor> entry : mGroupDescriptors.entrySet()) {
             LeAudioGroupDescriptor descriptor = entry.getValue();
             Integer groupId = entry.getKey();
diff --git a/android/app/src/com/android/bluetooth/le_audio/LeAudioStateMachine.java b/android/app/src/com/android/bluetooth/le_audio/LeAudioStateMachine.java
index a838cbb..95c4e0f 100644
--- a/android/app/src/com/android/bluetooth/le_audio/LeAudioStateMachine.java
+++ b/android/app/src/com/android/bluetooth/le_audio/LeAudioStateMachine.java
@@ -55,8 +55,6 @@
 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;
diff --git a/android/app/src/com/android/bluetooth/map/BluetoothMapContent.java b/android/app/src/com/android/bluetooth/map/BluetoothMapContent.java
index bf31e43..1c684bf 100644
--- a/android/app/src/com/android/bluetooth/map/BluetoothMapContent.java
+++ b/android/app/src/com/android/bluetooth/map/BluetoothMapContent.java
@@ -40,6 +40,7 @@
 import com.android.bluetooth.BluetoothMethodProxy;
 import com.android.bluetooth.DeviceWorkArounds;
 import com.android.bluetooth.SignedLongLong;
+import com.android.bluetooth.Utils;
 import com.android.bluetooth.map.BluetoothMapUtils.TYPE;
 import com.android.bluetooth.map.BluetoothMapbMessageMime.MimePart;
 import com.android.bluetooth.mapapi.BluetoothMapContract;
@@ -223,7 +224,8 @@
     };
 
     /* CONVO LISTING projections and column indexes */
-    private static final String[] MMS_SMS_THREAD_PROJECTION = {
+    @VisibleForTesting
+    static final String[] MMS_SMS_THREAD_PROJECTION = {
             Threads._ID,
             Threads.DATE,
             Threads.SNIPPET,
@@ -1338,9 +1340,15 @@
         }
 
         // Fix Subject Display issue with HONDA Carkit - Ignore subject Mask.
-        if (DeviceWorkArounds.addressStartsWith(BluetoothMapService.getRemoteDevice().getAddress(),
-                    DeviceWorkArounds.HONDA_CARKIT)
-                || (ap.getParameterMask() & MASK_SUBJECT) != 0) {
+        boolean isHondaCarkit;
+        if (Utils.isInstrumentationTestMode()) {
+            isHondaCarkit = false;
+        } else {
+            isHondaCarkit = DeviceWorkArounds.addressStartsWith(
+                    BluetoothMapService.getRemoteDevice().getAddress(),
+                    DeviceWorkArounds.HONDA_CARKIT);
+        }
+        if (isHondaCarkit || (ap.getParameterMask() & MASK_SUBJECT) != 0) {
             if (fi.mMsgType == FilterInfo.TYPE_SMS) {
                 subject = c.getString(fi.mSmsColSubject);
             } else if (fi.mMsgType == FilterInfo.TYPE_MMS) {
@@ -2283,7 +2291,8 @@
                     if (D) {
                         Log.d(TAG, "msgType: " + fi.mMsgType + " where: " + where);
                     }
-                    smsCursor = mResolver.query(Sms.CONTENT_URI, SMS_PROJECTION, where, null,
+                    smsCursor = BluetoothMethodProxy.getInstance().contentResolverQuery(mResolver,
+                            Sms.CONTENT_URI, SMS_PROJECTION, where, null,
                             Sms.DATE + " DESC" + limit);
                     if (smsCursor != null) {
                         BluetoothMapMessageListingElement e = null;
@@ -2325,7 +2334,8 @@
                     if (D) {
                         Log.d(TAG, "msgType: " + fi.mMsgType + " where: " + where);
                     }
-                    mmsCursor = mResolver.query(Mms.CONTENT_URI, MMS_PROJECTION, where, null,
+                    mmsCursor = BluetoothMethodProxy.getInstance().contentResolverQuery(mResolver,
+                            Mms.CONTENT_URI, MMS_PROJECTION, where, null,
                             Mms.DATE + " DESC" + limit);
                     if (mmsCursor != null) {
                         BluetoothMapMessageListingElement e = null;
@@ -2368,10 +2378,9 @@
                         Log.d(TAG, "msgType: " + fi.mMsgType + " where: " + where);
                     }
                     Uri contentUri = Uri.parse(mBaseUri + BluetoothMapContract.TABLE_MESSAGE);
-                    emailCursor =
-                            mResolver.query(contentUri, BluetoothMapContract.BT_MESSAGE_PROJECTION,
-                                    where, null,
-                                    BluetoothMapContract.MessageColumns.DATE + " DESC" + limit);
+                    emailCursor = BluetoothMethodProxy.getInstance().contentResolverQuery(mResolver,
+                            contentUri, BluetoothMapContract.BT_MESSAGE_PROJECTION, where, null,
+                            BluetoothMapContract.MessageColumns.DATE + " DESC" + limit);
                     if (emailCursor != null) {
                         BluetoothMapMessageListingElement e = null;
                         // store column index so we dont have to look them up anymore (optimization)
@@ -2412,8 +2421,8 @@
                 }
 
                 Uri contentUri = Uri.parse(mBaseUri + BluetoothMapContract.TABLE_MESSAGE);
-                imCursor = mResolver.query(contentUri,
-                        BluetoothMapContract.BT_INSTANT_MESSAGE_PROJECTION, where, null,
+                imCursor = BluetoothMethodProxy.getInstance().contentResolverQuery(mResolver,
+                        contentUri, BluetoothMapContract.BT_INSTANT_MESSAGE_PROJECTION, where, null,
                         BluetoothMapContract.MessageColumns.DATE + " DESC" + limit);
                 if (imCursor != null) {
                     BluetoothMapMessageListingElement e = null;
@@ -2521,8 +2530,8 @@
         if (smsSelected(fi, ap) && folderElement.hasSmsMmsContent()) {
             fi.mMsgType = FilterInfo.TYPE_SMS;
             String where = setWhereFilter(folderElement, fi, ap);
-            Cursor c = mResolver.query(Sms.CONTENT_URI, SMS_PROJECTION, where, null,
-                    Sms.DATE + " DESC");
+            Cursor c = BluetoothMethodProxy.getInstance().contentResolverQuery(mResolver,
+                    Sms.CONTENT_URI, SMS_PROJECTION, where, null, Sms.DATE + " DESC");
             try {
                 if (c != null) {
                     cnt = c.getCount();
@@ -2537,8 +2546,8 @@
         if (mmsSelected(ap) && folderElement.hasSmsMmsContent()) {
             fi.mMsgType = FilterInfo.TYPE_MMS;
             String where = setWhereFilter(folderElement, fi, ap);
-            Cursor c = mResolver.query(Mms.CONTENT_URI, MMS_PROJECTION, where, null,
-                    Mms.DATE + " DESC");
+            Cursor c = BluetoothMethodProxy.getInstance().contentResolverQuery(mResolver,
+                    Mms.CONTENT_URI, MMS_PROJECTION, where, null, Mms.DATE + " DESC");
             try {
                 if (c != null) {
                     cnt += c.getCount();
@@ -2555,8 +2564,9 @@
             String where = setWhereFilter(folderElement, fi, ap);
             if (!where.isEmpty()) {
                 Uri contentUri = Uri.parse(mBaseUri + BluetoothMapContract.TABLE_MESSAGE);
-                Cursor c = mResolver.query(contentUri, BluetoothMapContract.BT_MESSAGE_PROJECTION,
-                        where, null, BluetoothMapContract.MessageColumns.DATE + " DESC");
+                Cursor c = BluetoothMethodProxy.getInstance().contentResolverQuery(mResolver,
+                        contentUri, BluetoothMapContract.BT_MESSAGE_PROJECTION, where, null,
+                        BluetoothMapContract.MessageColumns.DATE + " DESC");
                 try {
                     if (c != null) {
                         cnt += c.getCount();
@@ -2574,8 +2584,8 @@
             String where = setWhereFilter(folderElement, fi, ap);
             if (!where.isEmpty()) {
                 Uri contentUri = Uri.parse(mBaseUri + BluetoothMapContract.TABLE_MESSAGE);
-                Cursor c = mResolver.query(contentUri,
-                        BluetoothMapContract.BT_INSTANT_MESSAGE_PROJECTION, where, null,
+                Cursor c = BluetoothMethodProxy.getInstance().contentResolverQuery(mResolver,
+                        contentUri, BluetoothMapContract.BT_INSTANT_MESSAGE_PROJECTION, where, null,
                         BluetoothMapContract.MessageColumns.DATE + " DESC");
                 try {
                     if (c != null) {
@@ -2617,8 +2627,8 @@
             String where = setWhereFilterFolderType(folderElement, fi);
             where += " AND " + Sms.READ + "=0 ";
             where += setWhereFilterPeriod(ap, fi);
-            Cursor c = mResolver.query(Sms.CONTENT_URI, SMS_PROJECTION, where, null,
-                    Sms.DATE + " DESC");
+            Cursor c = BluetoothMethodProxy.getInstance().contentResolverQuery(mResolver,
+                    Sms.CONTENT_URI, SMS_PROJECTION, where, null, Sms.DATE + " DESC");
             try {
                 if (c != null) {
                     cnt = c.getCount();
@@ -2635,8 +2645,8 @@
             String where = setWhereFilterFolderType(folderElement, fi);
             where += " AND " + Mms.READ + "=0 ";
             where += setWhereFilterPeriod(ap, fi);
-            Cursor c = mResolver.query(Mms.CONTENT_URI, MMS_PROJECTION, where, null,
-                    Sms.DATE + " DESC");
+            Cursor c = BluetoothMethodProxy.getInstance().contentResolverQuery(mResolver,
+                    Mms.CONTENT_URI, MMS_PROJECTION, where, null, Sms.DATE + " DESC");
             try {
                 if (c != null) {
                     cnt += c.getCount();
@@ -2656,8 +2666,9 @@
                 where += " AND " + BluetoothMapContract.MessageColumns.FLAG_READ + "=0 ";
                 where += setWhereFilterPeriod(ap, fi);
                 Uri contentUri = Uri.parse(mBaseUri + BluetoothMapContract.TABLE_MESSAGE);
-                Cursor c = mResolver.query(contentUri, BluetoothMapContract.BT_MESSAGE_PROJECTION,
-                        where, null, BluetoothMapContract.MessageColumns.DATE + " DESC");
+                Cursor c = BluetoothMethodProxy.getInstance().contentResolverQuery(mResolver,
+                        contentUri, BluetoothMapContract.BT_MESSAGE_PROJECTION, where, null,
+                        BluetoothMapContract.MessageColumns.DATE + " DESC");
                 try {
                     if (c != null) {
                         cnt += c.getCount();
@@ -2677,8 +2688,8 @@
                 where += " AND " + BluetoothMapContract.MessageColumns.FLAG_READ + "=0 ";
                 where += setWhereFilterPeriod(ap, fi);
                 Uri contentUri = Uri.parse(mBaseUri + BluetoothMapContract.TABLE_MESSAGE);
-                Cursor c = mResolver.query(contentUri,
-                        BluetoothMapContract.BT_INSTANT_MESSAGE_PROJECTION, where, null,
+                Cursor c = BluetoothMethodProxy.getInstance().contentResolverQuery(mResolver,
+                        contentUri, BluetoothMapContract.BT_INSTANT_MESSAGE_PROJECTION, where, null,
                         BluetoothMapContract.MessageColumns.DATE + " DESC");
                 try {
                     if (c != null) {
@@ -2794,7 +2805,8 @@
                 }
                 // TODO: Optimize: Reduce projection based on convo parameter mask
                 smsMmsCursor =
-                        mResolver.query(uri, MMS_SMS_THREAD_PROJECTION, selection.toString(), args,
+                        BluetoothMethodProxy.getInstance().contentResolverQuery(mResolver, uri,
+                                MMS_SMS_THREAD_PROJECTION, selection.toString(), null,
                                 sortOrder.toString());
                 if (smsMmsCursor != null) {
                     // store column index so we don't have to look them up anymore (optimization)
@@ -2855,13 +2867,12 @@
                     Log.v(TAG, "URI with parameters: " + contentUri.toString());
                 }
                 // TODO: Optimize: Reduce projection based on convo parameter mask
-                imEmailCursor =
-                        mResolver.query(contentUri, BluetoothMapContract.BT_CONVERSATION_PROJECTION,
-                                null, null,
-                                BluetoothMapContract.ConversationColumns.LAST_THREAD_ACTIVITY
-                                        + " DESC, "
-                                        + BluetoothMapContract.ConversationColumns.THREAD_ID
-                                        + " ASC");
+                imEmailCursor = BluetoothMethodProxy.getInstance().contentResolverQuery(mResolver,
+                        contentUri, BluetoothMapContract.BT_CONVERSATION_PROJECTION, null, null,
+                        BluetoothMapContract.ConversationColumns.LAST_THREAD_ACTIVITY
+                                + " DESC, "
+                                + BluetoothMapContract.ConversationColumns.THREAD_ID
+                                + " ASC");
                 if (imEmailCursor != null) {
                     BluetoothMapConvoListingElement e = null;
                     // store column index so we don't have to look them up anymore (optimization)
diff --git a/android/app/src/com/android/bluetooth/map/BluetoothMapContentObserver.java b/android/app/src/com/android/bluetooth/map/BluetoothMapContentObserver.java
index 62bf498..8179da3 100644
--- a/android/app/src/com/android/bluetooth/map/BluetoothMapContentObserver.java
+++ b/android/app/src/com/android/bluetooth/map/BluetoothMapContentObserver.java
@@ -81,6 +81,7 @@
 import java.util.Map;
 import java.util.Objects;
 import java.util.Set;
+import java.util.concurrent.TimeUnit;
 
 @TargetApi(19)
 public class BluetoothMapContentObserver {
@@ -142,10 +143,12 @@
     private BluetoothMapMasInstance mMasInstance = null;
     private int mMasId;
     private boolean mEnableSmsMms = false;
-    private boolean mObserverRegistered = false;
+    @VisibleForTesting
+    boolean mObserverRegistered = false;
     @VisibleForTesting
     BluetoothMapAccountItem mAccount;
-    private String mAuthority = null;
+    @VisibleForTesting
+    String mAuthority = null;
 
     // Default supported feature bit mask is 0x1f
     private int mMapSupportedFeatures = BluetoothMapUtils.MAP_FEATURE_DEFAULT_BITMASK;
@@ -1262,7 +1265,8 @@
         }
     }
 
-    private void initMsgList() throws RemoteException {
+    @VisibleForTesting
+    void initMsgList() throws RemoteException {
         if (V) {
             Log.d(TAG, "initMsgList");
         }
@@ -1276,7 +1280,8 @@
 
             Cursor c;
             try {
-                c = mResolver.query(Sms.CONTENT_URI, SMS_PROJECTION_SHORT, null, null, null);
+                c = BluetoothMethodProxy.getInstance().contentResolverQuery(mResolver,
+                        Sms.CONTENT_URI, SMS_PROJECTION_SHORT, null, null, null);
             } catch (SQLiteException e) {
                 Log.e(TAG, "Failed to initialize the list of messages: " + e.toString());
                 return;
@@ -1307,7 +1312,8 @@
 
             HashMap<Long, Msg> msgListMms = new HashMap<Long, Msg>();
 
-            c = mResolver.query(Mms.CONTENT_URI, MMS_PROJECTION_SHORT, null, null, null);
+            c = BluetoothMethodProxy.getInstance().contentResolverQuery(mResolver, Mms.CONTENT_URI,
+                    MMS_PROJECTION_SHORT, null, null, null);
             try {
                 if (c != null && c.moveToFirst()) {
                     do {
@@ -1417,7 +1423,8 @@
         }
     }
 
-    private void handleMsgListChangesSms() {
+    @VisibleForTesting
+    void handleMsgListChangesSms() {
         if (V) {
             Log.d(TAG, "handleMsgListChangesSms");
         }
@@ -1428,9 +1435,11 @@
         Cursor c;
         synchronized (getMsgListSms()) {
             if (mMapEventReportVersion == BluetoothMapUtils.MAP_EVENT_REPORT_V10) {
-                c = mResolver.query(Sms.CONTENT_URI, SMS_PROJECTION_SHORT, null, null, null);
+                c = BluetoothMethodProxy.getInstance().contentResolverQuery(mResolver,
+                        Sms.CONTENT_URI, SMS_PROJECTION_SHORT, null, null, null);
             } else {
-                c = mResolver.query(Sms.CONTENT_URI, SMS_PROJECTION_SHORT_EXT, null, null, null);
+                c = BluetoothMethodProxy.getInstance().contentResolverQuery(mResolver,
+                        Sms.CONTENT_URI, SMS_PROJECTION_SHORT_EXT, null, null, null);
             }
             try {
                 if (c != null && c.moveToFirst()) {
@@ -1462,7 +1471,7 @@
                                 long timestamp = c.getLong(c.getColumnIndex(Sms.DATE));
                                 String date = BluetoothMapUtils.getDateTimeString(timestamp);
                                 if (BluetoothMapUtils.isDateTimeOlderThanOneYear(timestamp)) {
-                                    // Skip sending new message events older than one year
+                                    // Skip sending message events older than one year
                                     listChanged = false;
                                     msgListSms.remove(id);
                                     continue;
@@ -1625,7 +1634,6 @@
 
                         if (msg == null) {
                             /* New message - only notify on retrieve conf */
-                            listChanged = true;
                             if (getMmsFolderName(type).equalsIgnoreCase(
                                     BluetoothMapContract.FOLDER_NAME_INBOX)
                                     && mtype != MESSAGE_TYPE_RETRIEVE_CONF) {
@@ -1637,11 +1645,13 @@
                             if (mTransmitEvents && // extract contact details only if needed
                                     mMapEventReportVersion
                                             != BluetoothMapUtils.MAP_EVENT_REPORT_V10) {
-                                long timestamp = c.getLong(c.getColumnIndex(Mms.DATE));
+                                // MMS date field is in seconds
+                                long timestamp =
+                                        TimeUnit.SECONDS.toMillis(
+                                            c.getLong(c.getColumnIndex(Mms.DATE)));
                                 String date = BluetoothMapUtils.getDateTimeString(timestamp);
                                 if (BluetoothMapUtils.isDateTimeOlderThanOneYear(timestamp)) {
                                     // Skip sending new message events older than one year
-                                    listChanged = false;
                                     msgListMms.remove(id);
                                     continue;
                                 }
@@ -1685,6 +1695,7 @@
                                 evt = new Event(EVENT_TYPE_NEW, id, getMmsFolderName(type), null,
                                         TYPE.MMS);
                             }
+                            listChanged = true;
 
                             sendEvent(evt);
                         } else {
@@ -1975,7 +1986,8 @@
         }
     }
 
-    private void handleContactListChanges(Uri uri) {
+    @VisibleForTesting
+    void handleContactListChanges(Uri uri) {
         if (uri.getAuthority().equals(mAuthority)) {
             try {
                 if (V) {
@@ -2951,7 +2963,8 @@
         if (handle != -1) {
             String whereClause = " _id= " + handle;
             Uri uri = Mms.CONTENT_URI;
-            Cursor queryResult = resolver.query(uri, null, whereClause, null, null);
+            Cursor queryResult = BluetoothMethodProxy.getInstance().contentResolverQuery(resolver,
+                    uri, null, whereClause, null, null);
             try {
                 if (queryResult != null) {
                     if (queryResult.getCount() > 0) {
@@ -2959,7 +2972,8 @@
                         ContentValues data = new ContentValues();
                         /* set folder to be outbox */
                         data.put(Mms.MESSAGE_BOX, folder);
-                        resolver.update(uri, data, whereClause, null);
+                        BluetoothMethodProxy.getInstance().contentResolverUpdate(resolver, uri,
+                                data, whereClause, null);
                         if (D) {
                             Log.d(TAG, "moved MMS message to " + getMmsFolderName(folder));
                         }
@@ -3578,7 +3592,7 @@
             if (D) {
                 Log.d(TAG, "Transparent in use - delete");
             }
-            resolver.delete(uri, null, null);
+            BluetoothMethodProxy.getInstance().contentResolverDelete(resolver, uri, null, null);
         } else if (result == Activity.RESULT_OK) {
             /* This will trigger a notification */
             moveMmsToFolder(handle, resolver, Mms.MESSAGE_BOX_SENT);
@@ -3653,7 +3667,7 @@
             /* Delete from DB */
             ContentResolver resolver = context.getContentResolver();
             if (resolver != null) {
-                resolver.delete(uri, null, null);
+                BluetoothMethodProxy.getInstance().contentResolverDelete(resolver, uri, null, null);
             } else {
                 Log.w(TAG, "Unable to get resolver");
             }
diff --git a/android/app/src/com/android/bluetooth/map/BluetoothMapObexServer.java b/android/app/src/com/android/bluetooth/map/BluetoothMapObexServer.java
index f855c78..c02fe4a 100644
--- a/android/app/src/com/android/bluetooth/map/BluetoothMapObexServer.java
+++ b/android/app/src/com/android/bluetooth/map/BluetoothMapObexServer.java
@@ -1308,7 +1308,8 @@
      * @param overwrite True: The msgType will be overwritten to match the message types supported
      * by this MAS instance. False: any unsupported message types will be masked out.
      */
-    private void setMsgTypeFilterParams(BluetoothMapAppParams appParams, boolean overwrite) {
+    @VisibleForTesting
+    void setMsgTypeFilterParams(BluetoothMapAppParams appParams, boolean overwrite) {
         int masFilterMask = 0;
         if (!mEnableSmsMms) {
             masFilterMask |= BluetoothMapAppParams.FILTER_NO_SMS_CDMA;
diff --git a/android/app/src/com/android/bluetooth/map/BluetoothMapUtils.java b/android/app/src/com/android/bluetooth/map/BluetoothMapUtils.java
index 7d7047f..5d26238 100644
--- a/android/app/src/com/android/bluetooth/map/BluetoothMapUtils.java
+++ b/android/app/src/com/android/bluetooth/map/BluetoothMapUtils.java
@@ -682,10 +682,10 @@
         cal.setTimeInMillis(timestamp);
         Calendar oneYearAgo = Calendar.getInstance();
         oneYearAgo.add(Calendar.YEAR, -1);
-        if (cal.compareTo(oneYearAgo) > 0) {
+        if (cal.before(oneYearAgo)) {
             if (V) {
-                Log.v(TAG, "isDateTimeOlderThanOneYear timestamp : " + timestamp
-                        + " oneYearAgo: " + oneYearAgo);
+                Log.v(TAG, "isDateTimeOlderThanOneYear " + cal.getTimeInMillis()
+                        + " oneYearAgo: " + oneYearAgo.getTimeInMillis());
             }
             return true;
         }
diff --git a/android/app/src/com/android/bluetooth/map/SmsMmsContacts.java b/android/app/src/com/android/bluetooth/map/SmsMmsContacts.java
index b02d148..b00bd24 100644
--- a/android/app/src/com/android/bluetooth/map/SmsMmsContacts.java
+++ b/android/app/src/com/android/bluetooth/map/SmsMmsContacts.java
@@ -49,7 +49,8 @@
     private static final Uri ADDRESS_URI =
             MmsSms.CONTENT_URI.buildUpon().appendPath("canonical-addresses").build();
 
-    private static final String[] ADDRESS_PROJECTION = {
+    @VisibleForTesting
+    static final String[] ADDRESS_PROJECTION = {
             CanonicalAddressesColumns._ID, CanonicalAddressesColumns.ADDRESS
     };
     private static final int COL_ADDR_ID =
@@ -57,7 +58,8 @@
     private static final int COL_ADDR_ADDR =
             Arrays.asList(ADDRESS_PROJECTION).indexOf(CanonicalAddressesColumns.ADDRESS);
 
-    private static final String[] CONTACT_PROJECTION = {Contacts._ID, Contacts.DISPLAY_NAME};
+    @VisibleForTesting
+    static final String[] CONTACT_PROJECTION = {Contacts._ID, Contacts.DISPLAY_NAME};
     private static final String CONTACT_SEL_VISIBLE = Contacts.IN_VISIBLE_GROUP + "=1";
     private static final int COL_CONTACT_ID =
             Arrays.asList(CONTACT_PROJECTION).indexOf(Contacts._ID);
diff --git a/android/app/src/com/android/bluetooth/mapclient/MapClientContent.java b/android/app/src/com/android/bluetooth/mapclient/MapClientContent.java
index 36d4535..aa60b5b 100644
--- a/android/app/src/com/android/bluetooth/mapclient/MapClientContent.java
+++ b/android/app/src/com/android/bluetooth/mapclient/MapClientContent.java
@@ -137,6 +137,10 @@
         SubscriptionManager subscriptionManager =
                 context.getSystemService(SubscriptionManager.class);
         List<SubscriptionInfo> subscriptions = subscriptionManager.getActiveSubscriptionInfoList();
+        if (subscriptions == null) {
+            Log.w(TAG, "Active subscription list is missing");
+            return;
+        }
         for (SubscriptionInfo info : subscriptions) {
             if (info.getSubscriptionType() == SubscriptionManager.SUBSCRIPTION_TYPE_REMOTE_SIM) {
                 clearMessages(context, info.getSubscriptionId());
diff --git a/android/app/src/com/android/bluetooth/mapclient/MapClientService.java b/android/app/src/com/android/bluetooth/mapclient/MapClientService.java
index 40bf81c..c1daa8c 100644
--- a/android/app/src/com/android/bluetooth/mapclient/MapClientService.java
+++ b/android/app/src/com/android/bluetooth/mapclient/MapClientService.java
@@ -339,9 +339,11 @@
             Log.d(TAG, "stop()");
         }
 
-        mAdapterService.notifyActivityAttributionInfo(
-                getAttributionSource(),
-                AdapterService.ACTIVITY_ATTRIBUTION_NO_ACTIVE_DEVICE_ADDRESS);
+        if (mAdapterService != null) {
+            mAdapterService.notifyActivityAttributionInfo(
+                    getAttributionSource(),
+                    AdapterService.ACTIVITY_ATTRIBUTION_NO_ACTIVE_DEVICE_ADDRESS);
+        }
         if (mMapReceiver != null) {
             unregisterReceiver(mMapReceiver);
             mMapReceiver = null;
@@ -355,6 +357,7 @@
             }
             stateMachine.doQuit();
         }
+        mMapInstanceMap.clear();
         return true;
     }
 
@@ -364,7 +367,6 @@
             Log.d(TAG, "in Cleanup");
         }
         removeUncleanAccounts();
-        mMapInstanceMap.clear();
         // TODO(b/72948646): should be moved to stop()
         setMapClientService(null);
     }
diff --git a/android/app/src/com/android/bluetooth/mapclient/MnsObexServer.java b/android/app/src/com/android/bluetooth/mapclient/MnsObexServer.java
index 6c0d536..accdbfb 100644
--- a/android/app/src/com/android/bluetooth/mapclient/MnsObexServer.java
+++ b/android/app/src/com/android/bluetooth/mapclient/MnsObexServer.java
@@ -20,6 +20,7 @@
 
 import com.android.bluetooth.ObexAppParameters;
 import com.android.bluetooth.ObexServerSockets;
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.obex.HeaderSet;
 import com.android.obex.Operation;
 import com.android.obex.ResponseCodes;
@@ -34,7 +35,8 @@
     private static final String TAG = "MnsObexServer";
     private static final boolean VDBG = MapClientService.VDBG;
 
-    private static final byte[] MNS_TARGET = new byte[]{
+    @VisibleForTesting
+    static final byte[] MNS_TARGET = new byte[]{
             (byte) 0xbb,
             0x58,
             0x2b,
@@ -53,7 +55,8 @@
             0x66
     };
 
-    private static final String TYPE = "x-bt/MAP-event-report";
+    @VisibleForTesting
+    static final String TYPE = "x-bt/MAP-event-report";
 
     private final WeakReference<MceStateMachine> mStateMachineReference;
     private final ObexServerSockets mObexServerSockets;
diff --git a/android/app/src/com/android/bluetooth/mcp/MediaControlGattService.java b/android/app/src/com/android/bluetooth/mcp/MediaControlGattService.java
index 0f1fef7..b37cbaa 100644
--- a/android/app/src/com/android/bluetooth/mcp/MediaControlGattService.java
+++ b/android/app/src/com/android/bluetooth/mcp/MediaControlGattService.java
@@ -17,6 +17,7 @@
 
 package com.android.bluetooth.mcp;
 
+import static android.bluetooth.BluetoothDevice.METADATA_GMCS_CCCD;
 import static android.bluetooth.BluetoothGattCharacteristic.PERMISSION_READ_ENCRYPTED;
 import static android.bluetooth.BluetoothGattCharacteristic.PERMISSION_WRITE_ENCRYPTED;
 import static android.bluetooth.BluetoothGattCharacteristic.PROPERTY_NOTIFY;
@@ -28,6 +29,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothDevice;
 import android.bluetooth.BluetoothGatt;
 import android.bluetooth.BluetoothGattCharacteristic;
@@ -37,12 +39,17 @@
 import android.bluetooth.BluetoothGattService;
 import android.bluetooth.BluetoothManager;
 import android.bluetooth.BluetoothProfile;
+import android.bluetooth.IBluetoothManager;
+import android.bluetooth.IBluetoothStateChangeCallback;
 import android.content.Context;
 import android.os.Handler;
 import android.os.Looper;
+import android.os.ParcelUuid;
+import android.os.RemoteException;
 import android.util.Log;
 import android.util.Pair;
 
+import com.android.bluetooth.Utils;
 import com.android.bluetooth.a2dp.A2dpService;
 import com.android.bluetooth.btservice.AdapterService;
 import com.android.bluetooth.hearingaid.HearingAidService;
@@ -441,7 +448,7 @@
                 } else {
                     status = BluetoothGatt.GATT_SUCCESS;
                     setCcc(device, op.mDescriptor.getCharacteristic().getUuid(), op.mOffset,
-                            op.mValue);
+                            op.mValue, true);
                 }
 
                 if (op.mResponseNeeded) {
@@ -522,6 +529,34 @@
         }
     }
 
+    private void restoreCccValuesForStoredDevices() {
+        for (BluetoothDevice device : mAdapterService.getBondedDevices()) {
+            byte[] gmcs_cccd = device.getMetadata(METADATA_GMCS_CCCD);
+
+            if ((gmcs_cccd == null) || (gmcs_cccd.length == 0)) {
+                return;
+            }
+
+            List<ParcelUuid> uuidList = Arrays.asList(Utils.byteArrayToUuid(gmcs_cccd));
+
+            /* Restore CCCD values for device */
+            for (ParcelUuid uuid : uuidList) {
+                setCcc(device, uuid.getUuid(), 0,
+                        BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE, false);
+            }
+        }
+    }
+
+    private final IBluetoothStateChangeCallback mBluetoothStateChangeCallback =
+            new IBluetoothStateChangeCallback.Stub() {
+                public void onBluetoothStateChange(boolean up) {
+                    if (DBG) Log.d(TAG, "onBluetoothStateChange: up=" + up);
+                    if (up) {
+                        restoreCccValuesForStoredDevices();
+                    }
+                }
+            };
+
     @VisibleForTesting
     final BluetoothGattServerCallback mServerCallback = new BluetoothGattServerCallback() {
         @Override
@@ -551,6 +586,7 @@
 
             mCharacteristics.get(CharId.CONTENT_CONTROL_ID)
                     .setValue(mCcid, BluetoothGattCharacteristic.FORMAT_UINT8, 0);
+            restoreCccValuesForStoredDevices();
             setInitialCharacteristicValuesAndNotify();
             initialStateRequest();
         }
@@ -796,6 +832,15 @@
         mMcpService = mcpService;
         mAdapterService =  Objects.requireNonNull(AdapterService.getAdapterService(),
                 "AdapterService shouldn't be null when creating MediaControlCattService");
+
+        IBluetoothManager mgr = BluetoothAdapter.getDefaultAdapter().getBluetoothManager();
+        if (mgr != null) {
+            try {
+                mgr.registerStateChangeCallback(mBluetoothStateChangeCallback);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
     }
 
     protected boolean init(UUID scvUuid) {
@@ -987,16 +1032,77 @@
         return mBluetoothGattServer.addService(mGattService);
     }
 
+    private void removeUuidFromMetadata(ParcelUuid charUuid, BluetoothDevice device) {
+        List<ParcelUuid> uuidList;
+        byte[] gmcs_cccd = device.getMetadata(METADATA_GMCS_CCCD);
+
+        if ((gmcs_cccd == null) || (gmcs_cccd.length == 0)) {
+            uuidList = new ArrayList<ParcelUuid>();
+        } else {
+            uuidList = new ArrayList<>(Arrays.asList(Utils.byteArrayToUuid(gmcs_cccd)));
+
+            if (!uuidList.contains(charUuid)) {
+                Log.d(TAG, "Characteristic CCCD can't be removed (not cached): "
+                        + charUuid.toString());
+                return;
+            }
+        }
+
+        uuidList.remove(charUuid);
+
+        if (!device.setMetadata(METADATA_GMCS_CCCD,
+                Utils.uuidsToByteArray(uuidList.toArray(new ParcelUuid[0])))) {
+            Log.e(TAG, "Can't set CCCD for GMCS characteristic UUID: " + charUuid.toString()
+                    + ", (remove)");
+        }
+    }
+
+    private void addUuidToMetadata(ParcelUuid charUuid, BluetoothDevice device) {
+        List<ParcelUuid> uuidList;
+        byte[] gmcs_cccd = device.getMetadata(METADATA_GMCS_CCCD);
+
+        if ((gmcs_cccd == null) || (gmcs_cccd.length == 0)) {
+            uuidList = new ArrayList<ParcelUuid>();
+        } else {
+            uuidList = new ArrayList<>(Arrays.asList(Utils.byteArrayToUuid(gmcs_cccd)));
+
+            if (uuidList.contains(charUuid)) {
+                Log.d(TAG, "Characteristic CCCD already added: " + charUuid.toString());
+                return;
+            }
+        }
+
+        uuidList.add(charUuid);
+
+        if (!device.setMetadata(METADATA_GMCS_CCCD,
+                Utils.uuidsToByteArray(uuidList.toArray(new ParcelUuid[0])))) {
+            Log.e(TAG, "Can't set CCCD for GMCS characteristic UUID: " + charUuid.toString()
+                    + ", (add)");
+        }
+    }
+
     @VisibleForTesting
-    void setCcc(BluetoothDevice device, UUID charUuid, int offset, byte[] value) {
+    void setCcc(BluetoothDevice device, UUID charUuid, int offset, byte[] value, boolean store) {
         HashMap<UUID, Short> characteristicCcc = mCccDescriptorValues.get(device.getAddress());
         if (characteristicCcc == null) {
             characteristicCcc = new HashMap<>();
             mCccDescriptorValues.put(device.getAddress(), characteristicCcc);
         }
 
-        characteristicCcc.put(
-                charUuid, ByteBuffer.wrap(value).order(ByteOrder.LITTLE_ENDIAN).getShort());
+        characteristicCcc.put(charUuid,
+                ByteBuffer.wrap(value).order(ByteOrder.LITTLE_ENDIAN).getShort());
+
+        if (!store) {
+            return;
+        }
+
+        if (Arrays.equals(value, BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE)) {
+            addUuidToMetadata(new ParcelUuid(charUuid), device);
+        } else if (Arrays.equals(value, BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE)) {
+            removeUuidFromMetadata(new ParcelUuid(charUuid), device);
+        } else {
+            Log.e(TAG, "Not handled CCC value: " + Arrays.toString(value));
+        }
     }
 
     private byte[] getCccBytes(BluetoothDevice device, UUID charUuid) {
diff --git a/android/app/src/com/android/bluetooth/opp/BluetoothOppLauncherActivity.java b/android/app/src/com/android/bluetooth/opp/BluetoothOppLauncherActivity.java
index 301b39f..b5dd0f7 100644
--- a/android/app/src/com/android/bluetooth/opp/BluetoothOppLauncherActivity.java
+++ b/android/app/src/com/android/bluetooth/opp/BluetoothOppLauncherActivity.java
@@ -422,7 +422,8 @@
             launchDevicePicker();
             finish();
         } catch (IllegalArgumentException exception) {
-            showToast(exception.getMessage());
+            String message = exception.getMessage();
+            showToast(message != null ? message : "IllegalArgumentException");
             finish();
         }
     }
diff --git a/android/app/src/com/android/bluetooth/opp/BluetoothOppService.java b/android/app/src/com/android/bluetooth/opp/BluetoothOppService.java
index 773d771..f9521b0 100644
--- a/android/app/src/com/android/bluetooth/opp/BluetoothOppService.java
+++ b/android/app/src/com/android/bluetooth/opp/BluetoothOppService.java
@@ -1115,6 +1115,11 @@
 
     // Run in a background thread at boot.
     private static void trimDatabase(ContentResolver contentResolver) {
+        if (contentResolver.acquireContentProviderClient(BluetoothShare.CONTENT_URI) == null) {
+            Log.w(TAG, "ContentProvider doesn't exist");
+            return;
+        }
+
         // remove the invisible/unconfirmed inbound shares
         int delNum = contentResolver.delete(BluetoothShare.CONTENT_URI, WHERE_INVISIBLE_UNCONFIRMED,
                 null);
diff --git a/android/app/src/com/android/bluetooth/opp/BluetoothOppTransfer.java b/android/app/src/com/android/bluetooth/opp/BluetoothOppTransfer.java
index 00fb596..eb10b23 100644
--- a/android/app/src/com/android/bluetooth/opp/BluetoothOppTransfer.java
+++ b/android/app/src/com/android/bluetooth/opp/BluetoothOppTransfer.java
@@ -92,23 +92,27 @@
 
     private BluetoothAdapter mAdapter;
 
-    private BluetoothDevice mDevice;
+    @VisibleForTesting
+    BluetoothDevice mDevice;
 
     private BluetoothOppBatch mBatch;
 
     private BluetoothOppObexSession mSession;
 
-    private BluetoothOppShareInfo mCurrentShare;
+    @VisibleForTesting
+    BluetoothOppShareInfo mCurrentShare;
 
     private ObexTransport mTransport;
 
     private HandlerThread mHandlerThread;
 
-    private EventHandler mSessionHandler;
+    @VisibleForTesting
+    EventHandler mSessionHandler;
 
     private long mTimestamp;
 
-    private class OppConnectionReceiver extends BroadcastReceiver {
+    @VisibleForTesting
+    class OppConnectionReceiver extends BroadcastReceiver {
         @Override
         public void onReceive(Context context, Intent intent) {
             String action = intent.getAction();
@@ -139,8 +143,8 @@
                         // Remove the timeout message triggered earlier during Obex Put
                         mSessionHandler.removeMessages(BluetoothOppObexSession.MSG_CONNECT_TIMEOUT);
                         // Now reuse the same message to clean up the session.
-                        mSessionHandler.sendMessage(mSessionHandler.obtainMessage(
-                                BluetoothOppObexSession.MSG_CONNECT_TIMEOUT));
+                        BluetoothMethodProxy.getInstance().handlerSendEmptyMessage(mSessionHandler,
+                                BluetoothOppObexSession.MSG_CONNECT_TIMEOUT);
                     }
                 } catch (Exception e) {
                     e.printStackTrace();
@@ -160,7 +164,11 @@
                         Log.w(TAG, "OPP SDP search, target device is null, ignoring result");
                         return;
                     }
-                    if (!device.getIdentityAddress().equalsIgnoreCase(mDevice.getIdentityAddress())) {
+                    String deviceIdentityAddress = device.getIdentityAddress();
+                    String transferDeviceIdentityAddress = mDevice.getIdentityAddress();
+                    if (deviceIdentityAddress == null || transferDeviceIdentityAddress == null
+                            || !deviceIdentityAddress.equalsIgnoreCase(
+                                    transferDeviceIdentityAddress)) {
                         Log.w(TAG, " OPP SDP search for wrong device, ignoring!!");
                         return;
                     }
@@ -676,10 +684,12 @@
     @VisibleForTesting
     SocketConnectThread mConnectThread;
 
-    private class SocketConnectThread extends Thread {
+    @VisibleForTesting
+    class SocketConnectThread extends Thread {
         private final String mHost;
 
-        private final BluetoothDevice mDevice;
+        @VisibleForTesting
+        final BluetoothDevice mDevice;
 
         private final int mChannel;
 
@@ -695,7 +705,8 @@
 
         private boolean mSdpInitiated = false;
 
-        private boolean mIsInterrupted = false;
+        @VisibleForTesting
+        boolean mIsInterrupted = false;
 
         /* create a Rfcomm/L2CAP Socket */
         SocketConnectThread(BluetoothDevice device, boolean retry) {
@@ -863,7 +874,8 @@
                 Log.e(TAG, "Error when close socket");
             }
         }
-        mSessionHandler.obtainMessage(TRANSPORT_ERROR).sendToTarget();
+        BluetoothMethodProxy.getInstance().handlerSendEmptyMessage(mSessionHandler,
+                TRANSPORT_ERROR);
         return;
     }
 
diff --git a/android/app/src/com/android/bluetooth/tbs/BluetoothGattServerProxy.java b/android/app/src/com/android/bluetooth/tbs/BluetoothGattServerProxy.java
index a68af61..59688a6 100644
--- a/android/app/src/com/android/bluetooth/tbs/BluetoothGattServerProxy.java
+++ b/android/app/src/com/android/bluetooth/tbs/BluetoothGattServerProxy.java
@@ -27,6 +27,7 @@
 import android.content.Context;
 
 import java.util.List;
+import java.util.UUID;
 
 /**
  * A proxy class that facilitates testing of the TbsService class.
@@ -63,6 +64,21 @@
         return mBluetoothGattServer.addService(service);
     }
 
+    /**
+     * A proxy that Returns a {@link BluetoothGattService} from the list of services offered
+     * by this device.
+     *
+     * <p>If multiple instances of the same service (as identified by UUID)
+     * exist, the first instance of the service is returned.
+     *
+     * @param uuid UUID of the requested service
+     * @return BluetoothGattService if supported, or null if the requested service is not offered by
+     * this device.
+     */
+    public BluetoothGattService getService(UUID uuid) {
+        return mBluetoothGattServer.getService(uuid);
+    }
+
     public boolean sendResponse(BluetoothDevice device, int requestId, int status, int offset,
             byte[] value) {
         return mBluetoothGattServer.sendResponse(device, requestId, status, offset, value);
diff --git a/android/app/src/com/android/bluetooth/tbs/TbsGatt.java b/android/app/src/com/android/bluetooth/tbs/TbsGatt.java
index 7c3f9fc..4c1833a 100644
--- a/android/app/src/com/android/bluetooth/tbs/TbsGatt.java
+++ b/android/app/src/com/android/bluetooth/tbs/TbsGatt.java
@@ -17,17 +17,27 @@
 
 package com.android.bluetooth.tbs;
 
+import static android.bluetooth.BluetoothDevice.METADATA_GTBS_CCCD;
+
+import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothDevice;
 import android.bluetooth.BluetoothGatt;
 import android.bluetooth.BluetoothGattCharacteristic;
 import android.bluetooth.BluetoothGattDescriptor;
 import android.bluetooth.BluetoothGattServerCallback;
 import android.bluetooth.BluetoothGattService;
+import android.bluetooth.BluetoothProfile;
+import android.bluetooth.IBluetoothManager;
+import android.bluetooth.IBluetoothStateChangeCallback;
 import android.content.Context;
 import android.os.Handler;
 import android.os.Looper;
+import android.os.ParcelUuid;
+import android.os.RemoteException;
 import android.util.Log;
 
+import com.android.bluetooth.btservice.AdapterService;
+import com.android.bluetooth.Utils;
 import com.android.internal.annotations.VisibleForTesting;
 
 import java.io.ByteArrayOutputStream;
@@ -35,6 +45,7 @@
 import java.util.Arrays;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 import java.util.UUID;
 
 public class TbsGatt {
@@ -131,9 +142,11 @@
     private final GattCharacteristic mTerminationReasonCharacteristic;
     private final GattCharacteristic mIncomingCallCharacteristic;
     private final GattCharacteristic mCallFriendlyNameCharacteristic;
+    private List<BluetoothDevice> mSubscribers = new ArrayList<>();
     private BluetoothGattServerProxy mBluetoothGattServer;
     private Handler mHandler;
     private Callback mCallback;
+    private AdapterService mAdapterService;
 
     public static abstract class Callback {
 
@@ -144,6 +157,17 @@
     }
 
     TbsGatt(Context context) {
+        mAdapterService =  Objects.requireNonNull(AdapterService.getAdapterService(),
+                "AdapterService shouldn't be null when creating MediaControlCattService");
+        IBluetoothManager mgr = BluetoothAdapter.getDefaultAdapter().getBluetoothManager();
+        if (mgr != null) {
+            try {
+                mgr.registerStateChangeCallback(mBluetoothStateChangeCallback);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+
         mContext = context;
         mBearerProviderNameCharacteristic = new GattCharacteristic(UUID_BEARER_PROVIDER_NAME,
                 BluetoothGattCharacteristic.PROPERTY_READ
@@ -252,11 +276,55 @@
         return mContext;
     }
 
+    private void removeUuidFromMetadata(ParcelUuid charUuid, BluetoothDevice device) {
+        List<ParcelUuid> uuidList;
+        byte[] gtbs_cccd = device.getMetadata(METADATA_GTBS_CCCD);
+
+        if ((gtbs_cccd == null) || (gtbs_cccd.length == 0)) {
+            uuidList = new ArrayList<ParcelUuid>();
+        } else {
+            uuidList = new ArrayList<>(Arrays.asList(Utils.byteArrayToUuid(gtbs_cccd)));
+
+            if (!uuidList.contains(charUuid)) {
+                Log.d(TAG, "Characteristic CCCD can't be removed (not cached): "
+                        + charUuid.toString());
+                return;
+            }
+        }
+
+        uuidList.remove(charUuid);
+
+        if (!device.setMetadata(METADATA_GTBS_CCCD,
+                Utils.uuidsToByteArray(uuidList.toArray(new ParcelUuid[0])))) {
+            Log.e(TAG, "Can't set CCCD for GTBS characteristic UUID: " + charUuid + ", (remove)");
+        }
+    }
+
+    private void addUuidToMetadata(ParcelUuid charUuid, BluetoothDevice device) {
+        List<ParcelUuid> uuidList;
+        byte[] gtbs_cccd = device.getMetadata(METADATA_GTBS_CCCD);
+
+        if ((gtbs_cccd == null) || (gtbs_cccd.length == 0)) {
+            uuidList = new ArrayList<ParcelUuid>();
+        } else {
+            uuidList = new ArrayList<>(Arrays.asList(Utils.byteArrayToUuid(gtbs_cccd)));
+
+            if (uuidList.contains(charUuid)) {
+                Log.d(TAG, "Characteristic CCCD already add: " + charUuid.toString());
+                return;
+            }
+        }
+
+        uuidList.add(charUuid);
+
+        if (!device.setMetadata(METADATA_GTBS_CCCD,
+                Utils.uuidsToByteArray(uuidList.toArray(new ParcelUuid[0])))) {
+            Log.e(TAG, "Can't set CCCD for GTBS characteristic UUID: " + charUuid + ", (add)");
+        }
+    }
+
     /** Class that handles GATT characteristic notifications */
     private class BluetoothGattCharacteristicNotifier {
-
-        private List<BluetoothDevice> mSubscribers = new ArrayList<>();
-
         public int setSubscriptionConfiguration(BluetoothDevice device, byte[] configuration) {
             if (Arrays.equals(configuration, BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE)) {
                 mSubscribers.remove(device);
@@ -455,6 +523,14 @@
                 return BluetoothGatt.GATT_FAILURE;
             }
 
+            if (Arrays.equals(value, BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE)) {
+                addUuidToMetadata(new ParcelUuid(characteristic.getUuid()), device);
+            } else if (Arrays.equals(value, BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE)) {
+                removeUuidFromMetadata(new ParcelUuid(characteristic.getUuid()), device);
+            } else {
+                Log.e(TAG, "Not handled CCC value: " + Arrays.toString(value));
+            }
+
             return characteristic.setSubscriptionConfiguration(device, value);
         }
     }
@@ -659,13 +735,56 @@
         return UUID.fromString(UUID_PREFIX + uuid16 + UUID_SUFFIX);
     }
 
+    private void restoreCccValuesForStoredDevices() {
+        BluetoothGattService gattService = mBluetoothGattServer.getService(UUID_GTBS);
+
+        for (BluetoothDevice device : mAdapterService.getBondedDevices()) {
+            byte[] gtbs_cccd = device.getMetadata(METADATA_GTBS_CCCD);
+
+            if ((gtbs_cccd == null) || (gtbs_cccd.length == 0)) {
+                return;
+            }
+
+            List<ParcelUuid> uuidList = Arrays.asList(Utils.byteArrayToUuid(gtbs_cccd));
+
+            /* Restore CCCD values for device */
+            for (ParcelUuid uuid : uuidList) {
+                BluetoothGattCharacteristic characteristic =
+                        gattService.getCharacteristic(uuid.getUuid());
+                if (characteristic == null) {
+                    Log.e(TAG, "Invalid UUID stored in metadata: " + uuid.toString());
+                    continue;
+                }
+
+                BluetoothGattDescriptor descriptor =
+                        characteristic.getDescriptor(UUID_CLIENT_CHARACTERISTIC_CONFIGURATION);
+                if (descriptor == null) {
+                    Log.e(TAG, "Invalid characteristic, does not include CCCD");
+                    continue;
+                }
+
+                descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
+                mSubscribers.add(device);
+            }
+        }
+    }
+
+    private final IBluetoothStateChangeCallback mBluetoothStateChangeCallback =
+            new IBluetoothStateChangeCallback.Stub() {
+                public void onBluetoothStateChange(boolean up) {
+                    if (DBG) Log.d(TAG, "onBluetoothStateChange: up=" + up);
+                    if (up) {
+                        restoreCccValuesForStoredDevices();
+                    }
+                }
+            };
+
     /**
      * Callback to handle incoming requests to the GATT server. All read/write requests for
      * characteristics and descriptors are handled here.
      */
     @VisibleForTesting
     final BluetoothGattServerCallback mGattServerCallback = new BluetoothGattServerCallback() {
-
         @Override
         public void onServiceAdded(int status, BluetoothGattService service) {
             if (DBG) {
@@ -674,6 +793,8 @@
             if (mCallback != null) {
                 mCallback.onServiceAdded(status == BluetoothGatt.GATT_SUCCESS);
             }
+
+            restoreCccValuesForStoredDevices();
         }
 
         @Override
diff --git a/android/app/src/com/android/bluetooth/telephony/BluetoothInCallService.java b/android/app/src/com/android/bluetooth/telephony/BluetoothInCallService.java
index 46eb260..7cc5bac 100644
--- a/android/app/src/com/android/bluetooth/telephony/BluetoothInCallService.java
+++ b/android/app/src/com/android/bluetooth/telephony/BluetoothInCallService.java
@@ -708,7 +708,7 @@
             // We do, however want to send conferences that have no children to the bluetooth
             // device (e.g. IMS Conference).
             boolean isConferenceWithNoChildren = isConferenceWithNoChildren(call);
-            Log.i(TAG, "sendListOfCalls isConferenceWithNoChildren " + isConferenceWithNoChildren 
+            Log.i(TAG, "sendListOfCalls isConferenceWithNoChildren " + isConferenceWithNoChildren
                 + ", call.getChildrenIds() size " + call.getChildrenIds().size());
             if (!call.isConference() || isConferenceWithNoChildren) {
                 sendClccForCall(call, shouldLog);
@@ -1479,7 +1479,9 @@
         mBluetoothLeCallControl.currentCallsList(tbsCalls);
     }
 
-    private final BluetoothLeCallControl.Callback mBluetoothLeCallControlCallback = new BluetoothLeCallControl.Callback() {
+    @VisibleForTesting
+    final BluetoothLeCallControl.Callback mBluetoothLeCallControlCallback =
+            new BluetoothLeCallControl.Callback() {
 
         @Override
         public void onAcceptCall(int requestId, UUID callId) {
diff --git a/android/app/tests/unit/AndroidTest.xml b/android/app/tests/unit/AndroidTest.xml
index 8414185..4358ed8 100644
--- a/android/app/tests/unit/AndroidTest.xml
+++ b/android/app/tests/unit/AndroidTest.xml
@@ -64,9 +64,10 @@
         <option name="hidden-api-checks" value="false"/>
     </test>
 
-    <!-- Only run Cts Tests in MTS if the Bluetooth Mainline module is installed. -->
+    <!-- Only run if the Bluetooth Mainline module is installed. -->
     <object type="module_controller"
             class="com.android.tradefed.testtype.suite.module.MainlineTestModuleController">
+        <option name="enable" value="true" />
         <option name="mainline-module-package-name" value="com.android.btservices" />
     </object>
 </configuration>
diff --git a/android/app/tests/unit/GoogleAndroidTest.xml b/android/app/tests/unit/GoogleAndroidTest.xml
index 24fa605..8300f6c 100644
--- a/android/app/tests/unit/GoogleAndroidTest.xml
+++ b/android/app/tests/unit/GoogleAndroidTest.xml
@@ -64,9 +64,10 @@
         <option name="hidden-api-checks" value="false"/>
     </test>
 
-    <!-- Only run Cts Tests in MTS if the Bluetooth Mainline module is installed. -->
+    <!-- Only run if the Google Bluetooth Mainline module is installed. -->
     <object type="module_controller"
             class="com.android.tradefed.testtype.suite.module.MainlineTestModuleController">
+        <option name="enable" value="true" />
         <option name="mainline-module-package-name" value="com.google.android.btservices" />
     </object>
 </configuration>
diff --git a/android/app/tests/unit/src/com/android/bluetooth/SignedLongLongTest.java b/android/app/tests/unit/src/com/android/bluetooth/SignedLongLongTest.java
new file mode 100644
index 0000000..0a4b682
--- /dev/null
+++ b/android/app/tests/unit/src/com/android/bluetooth/SignedLongLongTest.java
@@ -0,0 +1,139 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.bluetooth;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertThrows;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Test for SignedLongLong.java
+ */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class SignedLongLongTest {
+
+    @Test
+    public void compareTo_sameValue_returnsZero() {
+        long mostSigBits = 1352;
+        long leastSigBits = 53423;
+
+        SignedLongLong value = new SignedLongLong(mostSigBits, leastSigBits);
+        SignedLongLong sameValue = new SignedLongLong(mostSigBits, leastSigBits);
+
+        assertThat(value.compareTo(sameValue)).isEqualTo(0);
+    }
+
+    @Test
+    public void compareTo_biggerLeastSigBits_returnsMinusOne() {
+        long commonMostSigBits = 12345;
+        long leastSigBits = 1;
+        SignedLongLong value = new SignedLongLong(leastSigBits, commonMostSigBits);
+
+        long biggerLeastSigBits = 2;
+        SignedLongLong biggerValue = new SignedLongLong(biggerLeastSigBits, commonMostSigBits);
+
+        assertThat(value.compareTo(biggerValue)).isEqualTo(-1);
+    }
+
+    @Test
+    public void compareTo_smallerLeastSigBits_returnsOne() {
+        long commonMostSigBits = 12345;
+        long leastSigBits = 2;
+        SignedLongLong value = new SignedLongLong(leastSigBits, commonMostSigBits);
+
+        long smallerLeastSigBits = 1;
+        SignedLongLong smallerValue = new SignedLongLong(smallerLeastSigBits, commonMostSigBits);
+
+        assertThat(value.compareTo(smallerValue)).isEqualTo(1);
+    }
+
+    @Test
+    public void compareTo_biggerMostSigBits_returnsMinusOne() {
+        long commonLeastSigBits = 12345;
+        long mostSigBits = 1;
+        SignedLongLong value = new SignedLongLong(commonLeastSigBits, mostSigBits);
+
+        long biggerMostSigBits = 2;
+        SignedLongLong biggerValue = new SignedLongLong(commonLeastSigBits, biggerMostSigBits);
+
+        assertThat(value.compareTo(biggerValue)).isEqualTo(-1);
+    }
+
+    @Test
+    public void compareTo_smallerMostSigBits_returnsOne() {
+        long commonLeastSigBits = 12345;
+        long mostSigBits = 2;
+        SignedLongLong value = new SignedLongLong(commonLeastSigBits, mostSigBits);
+
+        long smallerMostSigBits = 1;
+        SignedLongLong smallerValue = new SignedLongLong(commonLeastSigBits, smallerMostSigBits);
+
+        assertThat(value.compareTo(smallerValue)).isEqualTo(1);
+    }
+
+    @Test
+    public void toString_RepresentedAsHexValues() {
+        SignedLongLong value = new SignedLongLong(2, 11);
+
+        assertThat(value.toString()).isEqualTo("B0000000000000002");
+    }
+
+    @SuppressWarnings("EqualsIncompatibleType")
+    @Test
+    public void equals_variousCases() {
+        SignedLongLong value = new SignedLongLong(1, 2);
+
+        assertThat(value.equals(value)).isTrue();
+        assertThat(value.equals(null)).isFalse();
+        assertThat(value.equals("a random string")).isFalse();
+        assertThat(value.equals(new SignedLongLong(1, 1))).isFalse();
+        assertThat(value.equals(new SignedLongLong(2, 2))).isFalse();
+        assertThat(value.equals(new SignedLongLong(1, 2))).isTrue();
+    }
+
+    @Test
+    public void fromString_whenStringIsNull_throwsNPE() {
+        assertThrows(NullPointerException.class, () -> SignedLongLong.fromString(null));
+    }
+
+    @Test
+    public void fromString_whenLengthIsInvalid_throwsNumberFormatException() {
+        assertThrows(NumberFormatException.class, () -> SignedLongLong.fromString(""));
+    }
+
+    @Test
+    public void fromString_whenLengthIsNotGreaterThan16() throws Exception {
+        String strValue = "1";
+
+        assertThat(SignedLongLong.fromString(strValue))
+                .isEqualTo(new SignedLongLong(1, 0));
+    }
+
+    @Test
+    public void fromString_whenLengthIsGreaterThan16() throws Exception {
+        String strValue = "B0000000000000002";
+
+        assertThat(SignedLongLong.fromString(strValue))
+                .isEqualTo(new SignedLongLong(2, 11));
+    }
+}
diff --git a/android/app/tests/unit/src/com/android/bluetooth/UtilsTest.java b/android/app/tests/unit/src/com/android/bluetooth/UtilsTest.java
index 6a63eb9..20e830c 100644
--- a/android/app/tests/unit/src/com/android/bluetooth/UtilsTest.java
+++ b/android/app/tests/unit/src/com/android/bluetooth/UtilsTest.java
@@ -17,12 +17,16 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
+import static org.mockito.Mockito.verify;
 
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothDevice;
 import android.content.Context;
 import android.location.LocationManager;
+import android.os.Build;
 import android.os.ParcelUuid;
 import android.os.UserHandle;
 
@@ -36,6 +40,11 @@
 import org.junit.runner.RunWith;
 import org.mockito.Mockito;
 
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
 import java.nio.ByteBuffer;
 import java.nio.ByteOrder;
 import java.util.UUID;
@@ -168,4 +177,76 @@
         String loggableAddress = "xx:xx:xx:xx:" + device.getAddress().substring(12);
         assertThat(Utils.getLoggableAddress(device)).isEqualTo(loggableAddress);
     }
+
+    @Test
+    public void checkCallerIsSystemMethods_doesNotCrash() {
+        Context context = InstrumentationRegistry.getTargetContext();
+        String tag = "test_tag";
+
+        Utils.checkCallerIsSystemOrActiveOrManagedUser(context, tag);
+        Utils.checkCallerIsSystemOrActiveOrManagedUser(null, tag);
+        Utils.checkCallerIsSystemOrActiveUser(tag);
+    }
+
+    @Test
+    public void testCopyStream() throws Exception {
+        byte[] data = new byte[] {1, 2, 3, 4, 5, 6, 7, 8};
+        ByteArrayInputStream in = new ByteArrayInputStream(data);
+        ByteArrayOutputStream out = new ByteArrayOutputStream();
+        int bufferSize = 4;
+
+        Utils.copyStream(in, out, bufferSize);
+
+        assertThat(out.toByteArray()).isEqualTo(data);
+    }
+
+    @Test
+    public void debugGetAdapterStateString() {
+        assertThat(Utils.debugGetAdapterStateString(BluetoothAdapter.STATE_OFF))
+                .isEqualTo("STATE_OFF");
+        assertThat(Utils.debugGetAdapterStateString(BluetoothAdapter.STATE_ON))
+                .isEqualTo("STATE_ON");
+        assertThat(Utils.debugGetAdapterStateString(BluetoothAdapter.STATE_TURNING_ON))
+                .isEqualTo("STATE_TURNING_ON");
+        assertThat(Utils.debugGetAdapterStateString(BluetoothAdapter.STATE_TURNING_OFF))
+                .isEqualTo("STATE_TURNING_OFF");
+        assertThat(Utils.debugGetAdapterStateString(-124))
+                .isEqualTo("UNKNOWN");
+    }
+
+    @Test
+    public void ellipsize() {
+        if (!Build.TYPE.equals("user")) {
+            // Only ellipsize release builds
+            String input = "a_long_string";
+            assertThat(Utils.ellipsize(input)).isEqualTo(input);
+            return;
+        }
+
+        assertThat(Utils.ellipsize("ab")).isEqualTo("ab");
+        assertThat(Utils.ellipsize("abc")).isEqualTo("a⋯c");
+        assertThat(Utils.ellipsize(null)).isEqualTo(null);
+    }
+
+    @Test
+    public void safeCloseStream_inputStream_doesNotCrash() throws Exception {
+        InputStream is = mock(InputStream.class);
+        Utils.safeCloseStream(is);
+        verify(is).close();
+
+        Mockito.clearInvocations(is);
+        doThrow(new IOException()).when(is).close();
+        Utils.safeCloseStream(is);
+    }
+
+    @Test
+    public void safeCloseStream_outputStream_doesNotCrash() throws Exception {
+        OutputStream os = mock(OutputStream.class);
+        Utils.safeCloseStream(os);
+        verify(os).close();
+
+        Mockito.clearInvocations(os);
+        doThrow(new IOException()).when(os).close();
+        Utils.safeCloseStream(os);
+    }
 }
diff --git a/android/app/tests/unit/src/com/android/bluetooth/audio_util/GPMWrapperTest.java b/android/app/tests/unit/src/com/android/bluetooth/audio_util/GPMWrapperTest.java
new file mode 100644
index 0000000..7f03b37
--- /dev/null
+++ b/android/app/tests/unit/src/com/android/bluetooth/audio_util/GPMWrapperTest.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright 2023 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 org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.media.MediaDescription;
+import android.media.MediaMetadata;
+import android.media.session.MediaSession;
+import android.media.session.PlaybackState;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class GPMWrapperTest {
+
+    private Context mContext;
+    private MediaController mMediaController;
+
+    @Before
+    public void setUp() throws Exception {
+        mContext = InstrumentationRegistry.getTargetContext();
+        mMediaController = mock(MediaController.class);
+    }
+
+    @Test
+    public void isMetadataSynced_whenQueueIsNull_returnsFalse() {
+        when(mMediaController.getQueue()).thenReturn(null);
+
+        GPMWrapper wrapper = new GPMWrapper(mContext, mMediaController, null);
+
+        assertThat(wrapper.isMetadataSynced()).isFalse();
+    }
+
+    @Test
+    public void isMetadataSynced_whenOutOfSync_returnsFalse() {
+        long activeQueueItemId = 3;
+        PlaybackState state = new PlaybackState.Builder()
+                .setActiveQueueItemId(activeQueueItemId).build();
+        when(mMediaController.getPlaybackState()).thenReturn(state);
+
+        List<MediaSession.QueueItem> queue = new ArrayList<>();
+        MediaDescription description = new MediaDescription.Builder()
+                .setTitle("Title from queue item")
+                .build();
+        MediaSession.QueueItem queueItem = new MediaSession.QueueItem(
+                description, activeQueueItemId);
+        queue.add(queueItem);
+        when(mMediaController.getQueue()).thenReturn(queue);
+
+        MediaMetadata metadata = new MediaMetadata.Builder()
+                .putString(MediaMetadata.METADATA_KEY_TITLE,
+                        "Different Title from MediaMetadata")
+                .build();
+        when(mMediaController.getMetadata()).thenReturn(metadata);
+
+        GPMWrapper wrapper = new GPMWrapper(mContext, mMediaController, null);
+
+        assertThat(wrapper.isMetadataSynced()).isFalse();
+    }
+
+    @Test
+    public void isMetadataSynced_whenSynced_returnsTrue() {
+        String title = "test_title";
+
+        long activeQueueItemId = 3;
+        PlaybackState state = new PlaybackState.Builder()
+                .setActiveQueueItemId(activeQueueItemId).build();
+        when(mMediaController.getPlaybackState()).thenReturn(state);
+
+        List<MediaSession.QueueItem> queue = new ArrayList<>();
+        MediaDescription description = new MediaDescription.Builder()
+                .setTitle(title)
+                .build();
+        MediaSession.QueueItem queueItem = new MediaSession.QueueItem(
+                description, activeQueueItemId);
+        queue.add(queueItem);
+        when(mMediaController.getQueue()).thenReturn(queue);
+
+        MediaMetadata metadata = new MediaMetadata.Builder()
+                .putString(MediaMetadata.METADATA_KEY_TITLE, title)
+                .build();
+        when(mMediaController.getMetadata()).thenReturn(metadata);
+
+        GPMWrapper wrapper = new GPMWrapper(mContext, mMediaController, null);
+
+        assertThat(wrapper.isMetadataSynced()).isTrue();
+    }
+}
diff --git a/android/app/tests/unit/src/com/android/bluetooth/audio_util/MediaPlayerWrapperTest.java b/android/app/tests/unit/src/com/android/bluetooth/audio_util/MediaPlayerWrapperTest.java
index 16ba201..2940d00 100644
--- a/android/app/tests/unit/src/com/android/bluetooth/audio_util/MediaPlayerWrapperTest.java
+++ b/android/app/tests/unit/src/com/android/bluetooth/audio_util/MediaPlayerWrapperTest.java
@@ -16,6 +16,8 @@
 
 package com.android.bluetooth.audio_util;
 
+import static com.google.common.truth.Truth.assertThat;
+
 import static org.mockito.Mockito.*;
 
 import android.content.Context;
@@ -717,4 +719,144 @@
         Assert.assertFalse(wrapper.getTimeoutHandler().hasMessages(MSG_TIMEOUT));
         verify(mFailHandler, never()).onTerribleFailure(any(), any(), anyBoolean());
     }
+
+    @Test
+    public void pauseCurrent() {
+        MediaController.TransportControls transportControls
+                = mock(MediaController.TransportControls.class);
+        when(mMockController.getTransportControls()).thenReturn(transportControls);
+        MediaPlayerWrapper wrapper =
+                MediaPlayerWrapperFactory.wrap(mMockContext, mMockController, mThread.getLooper());
+
+        wrapper.pauseCurrent();
+
+        verify(transportControls).pause();
+    }
+
+    @Test
+    public void playCurrent() {
+        MediaController.TransportControls transportControls
+                = mock(MediaController.TransportControls.class);
+        when(mMockController.getTransportControls()).thenReturn(transportControls);
+        MediaPlayerWrapper wrapper =
+                MediaPlayerWrapperFactory.wrap(mMockContext, mMockController, mThread.getLooper());
+
+        wrapper.playCurrent();
+
+        verify(transportControls).play();
+    }
+
+    @Test
+    public void playItemFromQueue() {
+        MediaController.TransportControls transportControls
+                = mock(MediaController.TransportControls.class);
+        when(mMockController.getTransportControls()).thenReturn(transportControls);
+        when(mMockController.getQueue()).thenReturn(new ArrayList<>());
+        MediaPlayerWrapper wrapper =
+                MediaPlayerWrapperFactory.wrap(mMockContext, mMockController, mThread.getLooper());
+
+        long queueItemId = 4;
+        wrapper.playItemFromQueue(queueItemId);
+
+        verify(transportControls).skipToQueueItem(queueItemId);
+    }
+
+    @Test
+    public void rewind() {
+        MediaController.TransportControls transportControls
+                = mock(MediaController.TransportControls.class);
+        when(mMockController.getTransportControls()).thenReturn(transportControls);
+        MediaPlayerWrapper wrapper =
+                MediaPlayerWrapperFactory.wrap(mMockContext, mMockController, mThread.getLooper());
+
+        wrapper.rewind();
+
+        verify(transportControls).rewind();
+    }
+
+    @Test
+    public void seekTo() {
+        MediaController.TransportControls transportControls
+                = mock(MediaController.TransportControls.class);
+        when(mMockController.getTransportControls()).thenReturn(transportControls);
+        MediaPlayerWrapper wrapper =
+                MediaPlayerWrapperFactory.wrap(mMockContext, mMockController, mThread.getLooper());
+
+        long position = 50;
+        wrapper.seekTo(position);
+
+        verify(transportControls).seekTo(position);
+    }
+
+    @Test
+    public void setPlaybackSpeed() {
+        MediaController.TransportControls transportControls
+                = mock(MediaController.TransportControls.class);
+        when(mMockController.getTransportControls()).thenReturn(transportControls);
+        MediaPlayerWrapper wrapper =
+                MediaPlayerWrapperFactory.wrap(mMockContext, mMockController, mThread.getLooper());
+
+        float speed = 2.0f;
+        wrapper.setPlaybackSpeed(speed);
+
+        verify(transportControls).setPlaybackSpeed(speed);
+    }
+
+    @Test
+    public void skipToNext() {
+        MediaController.TransportControls transportControls
+                = mock(MediaController.TransportControls.class);
+        when(mMockController.getTransportControls()).thenReturn(transportControls);
+        MediaPlayerWrapper wrapper =
+                MediaPlayerWrapperFactory.wrap(mMockContext, mMockController, mThread.getLooper());
+
+        wrapper.skipToNext();
+
+        verify(transportControls).skipToNext();
+    }
+
+    @Test
+    public void skipToPrevious() {
+        MediaController.TransportControls transportControls
+                = mock(MediaController.TransportControls.class);
+        when(mMockController.getTransportControls()).thenReturn(transportControls);
+        MediaPlayerWrapper wrapper =
+                MediaPlayerWrapperFactory.wrap(mMockContext, mMockController, mThread.getLooper());
+
+        wrapper.skipToPrevious();
+
+        verify(transportControls).skipToPrevious();
+    }
+
+    @Test
+    public void stopCurrent() {
+        MediaController.TransportControls transportControls
+                = mock(MediaController.TransportControls.class);
+        when(mMockController.getTransportControls()).thenReturn(transportControls);
+        MediaPlayerWrapper wrapper =
+                MediaPlayerWrapperFactory.wrap(mMockContext, mMockController, mThread.getLooper());
+
+        wrapper.stopCurrent();
+
+        verify(transportControls).stop();
+    }
+
+    @Test
+    public void toggleRepeat_andToggleShuffle_doesNotCrash() {
+        MediaPlayerWrapper wrapper =
+                MediaPlayerWrapperFactory.wrap(mMockContext, mMockController, mThread.getLooper());
+
+        wrapper.toggleRepeat(true);
+        wrapper.toggleRepeat(false);
+        wrapper.toggleShuffle(true);
+        wrapper.toggleShuffle(false);
+    }
+
+    @Test
+    public void toString_doesNotCrash() {
+        MediaPlayerWrapper wrapper =
+                MediaPlayerWrapperFactory.wrap(mMockContext, mMockController, mThread.getLooper());
+
+        assertThat(wrapper.toString()).isNotEmpty();
+    }
 }
diff --git a/android/app/tests/unit/src/com/android/bluetooth/avrcp/AvrcpVolumeManagerTest.java b/android/app/tests/unit/src/com/android/bluetooth/avrcp/AvrcpVolumeManagerTest.java
new file mode 100644
index 0000000..dd57998
--- /dev/null
+++ b/android/app/tests/unit/src/com/android/bluetooth/avrcp/AvrcpVolumeManagerTest.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright 2023 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.android.bluetooth.avrcp.AvrcpVolumeManager.AVRCP_MAX_VOL;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.content.Context;
+import android.media.AudioManager;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SmallTest;
+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;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class AvrcpVolumeManagerTest {
+    private static final String REMOTE_DEVICE_ADDRESS = "00:01:02:03:04:05";
+    private static final int TEST_DEVICE_MAX_VOUME = 25;
+
+    @Mock
+    AvrcpNativeInterface mNativeInterface;
+
+    @Mock
+    AudioManager mAudioManager;
+
+    Context mContext;
+    BluetoothDevice mRemoteDevice;
+    AvrcpVolumeManager mAvrcpVolumeManager;
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+        mContext = InstrumentationRegistry.getTargetContext();
+        when(mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC))
+                .thenReturn(TEST_DEVICE_MAX_VOUME);
+        mRemoteDevice = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(REMOTE_DEVICE_ADDRESS);
+        mAvrcpVolumeManager = new AvrcpVolumeManager(mContext, mAudioManager, mNativeInterface);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        mAvrcpVolumeManager.removeStoredVolumeForDevice(mRemoteDevice);
+    }
+
+    @Test
+    public void avrcpToSystemVolume() {
+        assertThat(AvrcpVolumeManager.avrcpToSystemVolume(0)).isEqualTo(0);
+        assertThat(AvrcpVolumeManager.avrcpToSystemVolume(AVRCP_MAX_VOL))
+                .isEqualTo(TEST_DEVICE_MAX_VOUME);
+    }
+
+    @Test
+    public void dump() {
+        StringBuilder sb = new StringBuilder();
+        mAvrcpVolumeManager.dump(sb);
+
+        assertThat(sb.toString()).isNotEmpty();
+    }
+
+    @Test
+    public void sendVolumeChanged() {
+        mAvrcpVolumeManager.sendVolumeChanged(mRemoteDevice, TEST_DEVICE_MAX_VOUME);
+
+        verify(mNativeInterface).sendVolumeChanged(REMOTE_DEVICE_ADDRESS, AVRCP_MAX_VOL);
+    }
+
+    @Test
+    public void setVolume() {
+        mAvrcpVolumeManager.setVolume(mRemoteDevice, AVRCP_MAX_VOL);
+
+        verify(mAudioManager).setStreamVolume(eq(AudioManager.STREAM_MUSIC),
+                eq(TEST_DEVICE_MAX_VOUME), anyInt());
+    }
+}
diff --git a/android/app/tests/unit/src/com/android/bluetooth/bas/BatteryServiceTest.java b/android/app/tests/unit/src/com/android/bluetooth/bas/BatteryServiceTest.java
index 8c5ee71..e4a706d 100644
--- a/android/app/tests/unit/src/com/android/bluetooth/bas/BatteryServiceTest.java
+++ b/android/app/tests/unit/src/com/android/bluetooth/bas/BatteryServiceTest.java
@@ -242,6 +242,31 @@
         Assert.assertFalse("Connect expected to fail", mService.connect(mDevice));
     }
 
+    @Test
+    public void getConnectionState_whenNoDevicesAreConnected_returnsDisconnectedState() {
+        Assert.assertEquals(mService.getConnectionState(mDevice),
+                BluetoothProfile.STATE_DISCONNECTED);
+    }
+
+    @Test
+    public void getDevices_whenNoDevicesAreConnected_returnsEmptyList() {
+        Assert.assertTrue(mService.getDevices().isEmpty());
+    }
+
+    @Test
+    public void getDevicesMatchingConnectionStates() {
+        when(mAdapterService.getBondedDevices()).thenReturn(new BluetoothDevice[] {mDevice});
+        int states[] = new int[] {BluetoothProfile.STATE_DISCONNECTED};
+
+        Assert.assertTrue(mService.getDevicesMatchingConnectionStates(states).contains(mDevice));
+    }
+
+    @Test
+    public void setConnectionPolicy() {
+        Assert.assertTrue(mService.setConnectionPolicy(
+                mDevice, BluetoothProfile.CONNECTION_POLICY_FORBIDDEN));
+    }
+
     /**
      *  Helper function to test okToConnect() method
      *
diff --git a/android/app/tests/unit/src/com/android/bluetooth/bass_client/PeriodicAdvertisementResultTest.java b/android/app/tests/unit/src/com/android/bluetooth/bass_client/PeriodicAdvertisementResultTest.java
new file mode 100644
index 0000000..b80c949
--- /dev/null
+++ b/android/app/tests/unit/src/com/android/bluetooth/bass_client/PeriodicAdvertisementResultTest.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright 2023 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.bass_client;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class PeriodicAdvertisementResultTest {
+    private static final String REMOTE_DEVICE_ADDRESS = "00:01:02:03:04:05";
+
+    BluetoothDevice mDevice;
+
+    @Before
+    public void setUp() {
+        mDevice = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(REMOTE_DEVICE_ADDRESS);
+    }
+
+    @Test
+    public void constructor() {
+        int addressType = 1;
+        int syncHandle = 2;
+        int advSid = 3;
+        int paInterval = 4;
+        int broadcastId = 5;
+        PeriodicAdvertisementResult result = new PeriodicAdvertisementResult(
+                mDevice, addressType, syncHandle, advSid, paInterval, broadcastId);
+
+        assertThat(result.getAddressType()).isEqualTo(addressType);
+        assertThat(result.getSyncHandle()).isEqualTo(syncHandle);
+        assertThat(result.getAdvSid()).isEqualTo(advSid);
+        assertThat(result.getAdvInterval()).isEqualTo(paInterval);
+        assertThat(result.getBroadcastId()).isEqualTo(broadcastId);
+    }
+
+    @Test
+    public void updateMethods() {
+        int addressType = 1;
+        int syncHandle = 2;
+        int advSid = 3;
+        int paInterval = 4;
+        int broadcastId = 5;
+        PeriodicAdvertisementResult result = new PeriodicAdvertisementResult(
+                mDevice, addressType, syncHandle, advSid, paInterval, broadcastId);
+
+        int newAddressType = 6;
+        result.updateAddressType(newAddressType);
+        assertThat(result.getAddressType()).isEqualTo(newAddressType);
+
+        int newSyncHandle = 7;
+        result.updateSyncHandle(newSyncHandle);
+        assertThat(result.getSyncHandle()).isEqualTo(newSyncHandle);
+
+        int newAdvSid = 8;
+        result.updateAdvSid(newAdvSid);
+        assertThat(result.getAdvSid()).isEqualTo(newAdvSid);
+
+        int newAdvInterval = 9;
+        result.updateAdvInterval(newAdvInterval);
+        assertThat(result.getAdvInterval()).isEqualTo(newAdvInterval);
+
+        int newBroadcastId = 10;
+        result.updateBroadcastId(newBroadcastId);
+        assertThat(result.getBroadcastId()).isEqualTo(newBroadcastId);
+    }
+
+    @Test
+    public void print_doesNotCrash() {
+        int addressType = 1;
+        int syncHandle = 2;
+        int advSid = 3;
+        int paInterval = 4;
+        int broadcastId = 5;
+        PeriodicAdvertisementResult result = new PeriodicAdvertisementResult(
+                mDevice, addressType, syncHandle, advSid, paInterval, broadcastId);
+
+        result.print();
+    }
+}
diff --git a/android/app/tests/unit/src/com/android/bluetooth/btservice/ActiveDeviceManagerTest.java b/android/app/tests/unit/src/com/android/bluetooth/btservice/ActiveDeviceManagerTest.java
index 0854962..c6ed3c1 100644
--- a/android/app/tests/unit/src/com/android/bluetooth/btservice/ActiveDeviceManagerTest.java
+++ b/android/app/tests/unit/src/com/android/bluetooth/btservice/ActiveDeviceManagerTest.java
@@ -32,6 +32,7 @@
 import android.bluetooth.BluetoothHearingAid;
 import android.bluetooth.BluetoothLeAudio;
 import android.bluetooth.BluetoothProfile;
+import android.bluetooth.BluetoothSinkAudioPolicy;
 import android.content.Context;
 import android.content.Intent;
 import android.media.AudioManager;
@@ -123,6 +124,8 @@
         mMostRecentDevice = null;
 
         when(mA2dpService.setActiveDevice(any())).thenReturn(true);
+        when(mHeadsetService.getHfpCallAudioPolicy(any())).thenReturn(
+                new BluetoothSinkAudioPolicy.Builder().build());
         when(mHeadsetService.setActiveDevice(any())).thenReturn(true);
         when(mHearingAidService.setActiveDevice(any())).thenReturn(true);
         when(mLeAudioService.setActiveDevice(any())).thenReturn(true);
@@ -310,6 +313,24 @@
     }
 
     /**
+     * A headset device with connecting audio policy set to NOT ALLOWED.
+     */
+    @Test
+    public void notAllowedConnectingPolicyHeadsetConnected_noSetActiveDevice() {
+        // setting connecting policy to NOT ALLOWED
+        when(mHeadsetService.getHfpCallAudioPolicy(mHeadsetDevice))
+                .thenReturn(new BluetoothSinkAudioPolicy.Builder()
+                        .setCallEstablishPolicy(BluetoothSinkAudioPolicy.POLICY_ALLOWED)
+                        .setActiveDevicePolicyAfterConnection(
+                                BluetoothSinkAudioPolicy.POLICY_NOT_ALLOWED)
+                        .setInBandRingtonePolicy(BluetoothSinkAudioPolicy.POLICY_ALLOWED)
+                        .build());
+
+        headsetConnected(mHeadsetDevice);
+        verify(mHeadsetService, never()).setActiveDevice(mHeadsetDevice);
+    }
+
+    /**
      * A combo (A2DP + Headset) device is connected. Then a Hearing Aid is connected.
      */
     @Test
diff --git a/android/app/tests/unit/src/com/android/bluetooth/btservice/AdapterServiceFactoryResetTest.java b/android/app/tests/unit/src/com/android/bluetooth/btservice/AdapterServiceFactoryResetTest.java
new file mode 100644
index 0000000..6acc5a0
--- /dev/null
+++ b/android/app/tests/unit/src/com/android/bluetooth/btservice/AdapterServiceFactoryResetTest.java
@@ -0,0 +1,477 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.bluetooth.btservice;
+
+import static android.Manifest.permission.BLUETOOTH_SCAN;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.*;
+
+import android.app.AlarmManager;
+import android.app.admin.DevicePolicyManager;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothManager;
+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.BatteryStatsManager;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.Looper;
+import android.os.PowerManager;
+import android.os.Process;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.permission.PermissionCheckerManager;
+import android.permission.PermissionManager;
+import android.provider.Settings;
+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.TestUtils;
+import com.android.bluetooth.Utils;
+import com.android.bluetooth.a2dp.A2dpService;
+import com.android.bluetooth.a2dpsink.A2dpSinkService;
+import com.android.bluetooth.avrcp.AvrcpTargetService;
+import com.android.bluetooth.avrcpcontroller.AvrcpControllerService;
+import com.android.bluetooth.bas.BatteryService;
+import com.android.bluetooth.bass_client.BassClientService;
+import com.android.bluetooth.csip.CsipSetCoordinatorService;
+import com.android.bluetooth.gatt.GattService;
+import com.android.bluetooth.hap.HapClientService;
+import com.android.bluetooth.hearingaid.HearingAidService;
+import com.android.bluetooth.hfp.HeadsetService;
+import com.android.bluetooth.hfpclient.HeadsetClientService;
+import com.android.bluetooth.hid.HidDeviceService;
+import com.android.bluetooth.hid.HidHostService;
+import com.android.bluetooth.le_audio.LeAudioService;
+import com.android.bluetooth.map.BluetoothMapService;
+import com.android.bluetooth.mapclient.MapClientService;
+import com.android.bluetooth.mcp.McpService;
+import com.android.bluetooth.opp.BluetoothOppService;
+import com.android.bluetooth.pan.PanService;
+import com.android.bluetooth.pbap.BluetoothPbapService;
+import com.android.bluetooth.pbapclient.PbapClientService;
+import com.android.bluetooth.sap.SapService;
+import com.android.bluetooth.tbs.TbsService;
+import com.android.bluetooth.vc.VolumeControlService;
+import com.android.internal.app.IBatteryStats;
+
+import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.Arrays;
+import java.util.HashMap;
+
+@MediumTest
+@RunWith(AndroidJUnit4.class)
+public class AdapterServiceFactoryResetTest {
+    private static final String TAG = AdapterServiceFactoryResetTest.class.getSimpleName();
+
+    private AdapterService mAdapterService;
+    private AdapterService.AdapterServiceBinder mServiceBinder;
+
+    private @Mock Context mMockContext;
+    private @Mock ApplicationInfo mMockApplicationInfo;
+    private @Mock AlarmManager mMockAlarmManager;
+    private @Mock Resources mMockResources;
+    private @Mock UserManager mMockUserManager;
+    private @Mock DevicePolicyManager mMockDevicePolicyManager;
+    private @Mock ProfileService mMockGattService;
+    private @Mock ProfileService mMockService;
+    private @Mock ProfileService mMockService2;
+    private @Mock IBluetoothCallback mIBluetoothCallback;
+    private @Mock Binder mBinder;
+    private @Mock AudioManager mAudioManager;
+    private @Mock android.app.Application mApplication;
+    private @Mock MetricsLogger mMockMetricsLogger;
+
+    // BatteryStatsManager is final and cannot be mocked with regular mockito, so just mock the
+    // underlying binder calls.
+    final BatteryStatsManager mBatteryStatsManager =
+            new BatteryStatsManager(mock(IBatteryStats.class));
+
+    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 = 1000;
+    private static final int ONE_SECOND_MS = 1000;
+    private static final int NATIVE_INIT_MS = 8000;
+
+    private final AttributionSource mAttributionSource = new AttributionSource.Builder(
+            Process.myUid()).build();
+
+    private BluetoothManager mBluetoothManager;
+    private PowerManager mPowerManager;
+    private PermissionCheckerManager mPermissionCheckerManager;
+    private PermissionManager mPermissionManager;
+    private PackageManager mMockPackageManager;
+    private MockContentResolver mMockContentResolver;
+    private HashMap<String, HashMap<String, String>> mAdapterConfig;
+    private int mForegroundUserId;
+
+    private void configureEnabledProfiles() {
+        Log.e(TAG, "configureEnabledProfiles");
+        Config.setProfileEnabled(PanService.class, true);
+        Config.setProfileEnabled(BluetoothPbapService.class, true);
+        Config.setProfileEnabled(GattService.class, true);
+
+        Config.setProfileEnabled(A2dpService.class, false);
+        Config.setProfileEnabled(A2dpSinkService.class, false);
+        Config.setProfileEnabled(AvrcpTargetService.class, false);
+        Config.setProfileEnabled(AvrcpControllerService.class, false);
+        Config.setProfileEnabled(BassClientService.class, false);
+        Config.setProfileEnabled(BatteryService.class, false);
+        Config.setProfileEnabled(CsipSetCoordinatorService.class, false);
+        Config.setProfileEnabled(HapClientService.class, false);
+        Config.setProfileEnabled(HeadsetService.class, false);
+        Config.setProfileEnabled(HeadsetClientService.class, false);
+        Config.setProfileEnabled(HearingAidService.class, false);
+        Config.setProfileEnabled(HidDeviceService.class, false);
+        Config.setProfileEnabled(HidHostService.class, false);
+        Config.setProfileEnabled(LeAudioService.class, false);
+        Config.setProfileEnabled(TbsService.class, false);
+        Config.setProfileEnabled(BluetoothMapService.class, false);
+        Config.setProfileEnabled(MapClientService.class, false);
+        Config.setProfileEnabled(McpService.class, false);
+        Config.setProfileEnabled(BluetoothOppService.class, false);
+        Config.setProfileEnabled(PbapClientService.class, false);
+        Config.setProfileEnabled(SapService.class, false);
+        Config.setProfileEnabled(VolumeControlService.class, false);
+    }
+
+    @BeforeClass
+    public static void setupClass() {
+        Log.e(TAG, "setupClass");
+        // Bring native layer up and down to make sure config files are properly loaded
+        if (Looper.myLooper() == null) {
+            Looper.prepare();
+        }
+        Assert.assertNotNull(Looper.myLooper());
+        AdapterService adapterService = new AdapterService();
+        adapterService.initNative(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);
+        Assert.assertNotNull("metrics salt is null: " + adapterConfig.toString(),
+                AdapterServiceTest.getMetricsSalt(adapterConfig));
+    }
+
+    @Before
+    public void setUp() throws PackageManager.NameNotFoundException {
+        Log.e(TAG, "setUp()");
+        MockitoAnnotations.initMocks(this);
+        if (Looper.myLooper() == null) {
+            Looper.prepare();
+        }
+        Assert.assertNotNull(Looper.myLooper());
+
+        // Dispatch all async work through instrumentation so we can wait until
+        // it's drained below
+        AsyncTask.setDefaultExecutor((r) -> {
+            androidx.test.platform.app.InstrumentationRegistry.getInstrumentation()
+                    .runOnMainSync(r);
+        });
+        androidx.test.platform.app.InstrumentationRegistry.getInstrumentation().getUiAutomation()
+                .adoptShellPermissionIdentity();
+
+        androidx.test.platform.app.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(InstrumentationRegistry.getTargetContext());
+        mMockContentResolver.addProvider(Settings.AUTHORITY, new MockContentProvider() {
+            @Override
+            public Bundle call(String method, String request, Bundle args) {
+                return Bundle.EMPTY;
+            }
+        });
+
+        mPowerManager = InstrumentationRegistry.getTargetContext()
+                .getSystemService(PowerManager.class);
+        mPermissionCheckerManager = InstrumentationRegistry.getTargetContext()
+                .getSystemService(PermissionCheckerManager.class);
+
+        mPermissionManager = InstrumentationRegistry.getTargetContext()
+                .getSystemService(PermissionManager.class);
+
+        mBluetoothManager = InstrumentationRegistry.getTargetContext()
+                .getSystemService(BluetoothManager.class);
+
+        when(mMockContext.getCacheDir()).thenReturn(InstrumentationRegistry.getTargetContext()
+                .getCacheDir());
+        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);
+        when(mMockContext.getSystemService(Context.USER_SERVICE)).thenReturn(mMockUserManager);
+        when(mMockContext.getSystemServiceName(UserManager.class)).thenReturn(Context.USER_SERVICE);
+        when(mMockContext.getSystemService(Context.DEVICE_POLICY_SERVICE)).thenReturn(
+                mMockDevicePolicyManager);
+        when(mMockContext.getSystemServiceName(DevicePolicyManager.class))
+                .thenReturn(Context.DEVICE_POLICY_SERVICE);
+        when(mMockContext.getSystemService(Context.POWER_SERVICE)).thenReturn(mPowerManager);
+        when(mMockContext.getSystemServiceName(PowerManager.class))
+                .thenReturn(Context.POWER_SERVICE);
+        when(mMockContext.getSystemServiceName(PermissionCheckerManager.class))
+                .thenReturn(Context.PERMISSION_CHECKER_SERVICE);
+        when(mMockContext.getSystemService(Context.PERMISSION_CHECKER_SERVICE))
+                .thenReturn(mPermissionCheckerManager);
+        when(mMockContext.getSystemServiceName(PermissionManager.class))
+                .thenReturn(Context.PERMISSION_SERVICE);
+        when(mMockContext.getSystemService(Context.PERMISSION_SERVICE))
+                .thenReturn(mPermissionManager);
+        when(mMockContext.getSystemService(Context.ALARM_SERVICE)).thenReturn(mMockAlarmManager);
+        when(mMockContext.getSystemServiceName(AlarmManager.class))
+                .thenReturn(Context.ALARM_SERVICE);
+        when(mMockContext.getSystemService(Context.AUDIO_SERVICE)).thenReturn(mAudioManager);
+        when(mMockContext.getSystemServiceName(AudioManager.class))
+                .thenReturn(Context.AUDIO_SERVICE);
+        when(mMockContext.getSystemService(Context.BATTERY_STATS_SERVICE))
+                .thenReturn(mBatteryStatsManager);
+        when(mMockContext.getSystemServiceName(BatteryStatsManager.class))
+                .thenReturn(Context.BATTERY_STATS_SERVICE);
+        when(mMockContext.getSystemService(Context.BLUETOOTH_SERVICE))
+                .thenReturn(mBluetoothManager);
+        when(mMockContext.getSystemServiceName(BluetoothManager.class))
+                .thenReturn(Context.BLUETOOTH_SERVICE);
+        when(mMockContext.getSharedPreferences(anyString(), anyInt()))
+                .thenReturn(InstrumentationRegistry.getTargetContext()
+                        .getSharedPreferences("AdapterServiceTestPrefs", Context.MODE_PRIVATE));
+
+        when(mMockContext.getAttributionSource()).thenReturn(mAttributionSource);
+        doAnswer(invocation -> {
+            Object[] args = invocation.getArguments();
+            return InstrumentationRegistry.getTargetContext().getDatabasePath((String) args[0]);
+        }).when(mMockContext).getDatabasePath(anyString());
+
+        // Sets the foreground user id to match that of the tests (restored in tearDown)
+        mForegroundUserId = Utils.getForegroundUserId();
+        int callingUid = Binder.getCallingUid();
+        UserHandle callingUser = UserHandle.getUserHandleForUid(callingUid);
+        Utils.setForegroundUserId(callingUser.getIdentifier());
+
+        when(mMockDevicePolicyManager.isCommonCriteriaModeEnabled(any())).thenReturn(false);
+
+        when(mIBluetoothCallback.asBinder()).thenReturn(mBinder);
+
+        doReturn(Process.BLUETOOTH_UID).when(mMockPackageManager)
+                .getPackageUidAsUser(any(), anyInt(), anyInt());
+
+        when(mMockGattService.getName()).thenReturn("GattService");
+        when(mMockService.getName()).thenReturn("Service1");
+        when(mMockService2.getName()).thenReturn("Service2");
+
+        when(mMockMetricsLogger.init(any())).thenReturn(true);
+        when(mMockMetricsLogger.close()).thenReturn(true);
+
+        configureEnabledProfiles();
+        Config.init(mMockContext);
+
+        mAdapterService.setMetricsLogger(mMockMetricsLogger);
+
+        // Attach a context to the service for permission checks.
+        mAdapterService.attach(mMockContext, null, null, null, mApplication, null);
+        mAdapterService.onCreate();
+
+        // Wait for any async events to drain
+        androidx.test.platform.app.InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+
+        mServiceBinder.registerCallback(mIBluetoothCallback, mAttributionSource);
+
+        mAdapterConfig = TestUtils.readAdapterConfig();
+        Assert.assertNotNull(mAdapterConfig);
+    }
+
+    @After
+    public void tearDown() {
+        Log.e(TAG, "tearDown()");
+
+        // Enable the stack to re-create the config. Next tests rely on it.
+        doEnable(0, false);
+
+        // Restores the foregroundUserId to the ID prior to the test setup
+        Utils.setForegroundUserId(mForegroundUserId);
+
+        mServiceBinder.unregisterCallback(mIBluetoothCallback, mAttributionSource);
+        mAdapterService.cleanup();
+    }
+
+    @AfterClass
+    public static void tearDownOnce() {
+        AsyncTask.setDefaultExecutor(AsyncTask.SERIAL_EXECUTOR);
+    }
+
+    private void verifyStateChange(int prevState, int currState, int callNumber, int timeoutMs) {
+        try {
+            verify(mIBluetoothCallback, timeout(timeoutMs).times(callNumber))
+                .onBluetoothStateChange(prevState, currState);
+        } catch (RemoteException e) {
+            // the mocked onBluetoothStateChange doesn't throw RemoteException
+        }
+    }
+
+    private void doEnable(int invocationNumber, boolean onlyGatt) {
+        Log.e(TAG, "doEnable() start");
+        Assert.assertFalse(mAdapterService.getState() == BluetoothAdapter.STATE_ON);
+
+        int startServiceCalls;
+        startServiceCalls = 2 * (onlyGatt ? 1 : 3); // Start and stop GATT + 2
+
+        mAdapterService.enable(false);
+
+        verifyStateChange(BluetoothAdapter.STATE_OFF, BluetoothAdapter.STATE_BLE_TURNING_ON,
+                invocationNumber + 1, CONTEXT_SWITCH_MS);
+
+        // Start GATT
+        verify(mMockContext, timeout(GATT_START_TIME_MS).times(
+                startServiceCalls * invocationNumber + 1)).startService(any());
+        mAdapterService.addProfile(mMockGattService);
+        mAdapterService.onProfileServiceStateChanged(mMockGattService, BluetoothAdapter.STATE_ON);
+
+        verifyStateChange(BluetoothAdapter.STATE_BLE_TURNING_ON, BluetoothAdapter.STATE_BLE_ON,
+                invocationNumber + 1, NATIVE_INIT_MS);
+
+        mServiceBinder.onLeServiceUp(mAttributionSource);
+
+        verifyStateChange(BluetoothAdapter.STATE_BLE_ON, BluetoothAdapter.STATE_TURNING_ON,
+                invocationNumber + 1, CONTEXT_SWITCH_MS);
+
+        if (!onlyGatt) {
+            // Start Mock PBAP and PAN services
+            verify(mMockContext, timeout(ONE_SECOND_MS).times(
+                    startServiceCalls * invocationNumber + 3)).startService(any());
+
+            mAdapterService.addProfile(mMockService);
+            mAdapterService.addProfile(mMockService2);
+            mAdapterService.onProfileServiceStateChanged(mMockService, BluetoothAdapter.STATE_ON);
+            mAdapterService.onProfileServiceStateChanged(mMockService2, BluetoothAdapter.STATE_ON);
+        }
+
+        verifyStateChange(BluetoothAdapter.STATE_TURNING_ON, BluetoothAdapter.STATE_ON,
+                invocationNumber + 1, PROFILE_SERVICE_TOGGLE_TIME_MS);
+
+        verify(mMockContext, timeout(CONTEXT_SWITCH_MS).times(2 * invocationNumber + 2))
+                .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);
+
+        Log.e(TAG, "doEnable() complete success");
+    }
+
+    /**
+     * Test: Verify that obfuscated Bluetooth address changes after factory reset
+     *
+     * There are 4 types of factory reset that we are talking about:
+     * 1. Factory reset all user data from Settings -> Will restart phone
+     * 2. Factory reset WiFi and Bluetooth from Settings -> Will only restart WiFi and BT
+     * 3. Call BluetoothAdapter.factoryReset() -> Will disable Bluetooth and reset config in
+     * memory and disk
+     * 4. Call AdapterService.factoryReset() -> Will only reset config in memory
+     *
+     * We can only use No. 4 here
+     */
+    @Ignore("AdapterService.factoryReset() does not reload config into memory and hence old salt"
+            + " is still used until next time Bluetooth library is initialized. However Bluetooth"
+            + " cannot be used until Bluetooth process restart any way. Thus it is almost"
+            + " guaranteed that user has to re-enable Bluetooth and hence re-generate new salt"
+            + " after factory reset")
+    @Test
+    public void testObfuscateBluetoothAddress_FactoryReset() {
+        Assert.assertFalse(mAdapterService.getState() == BluetoothAdapter.STATE_ON);
+        BluetoothDevice device = TestUtils.getTestDevice(BluetoothAdapter.getDefaultAdapter(), 0);
+        byte[] obfuscatedAddress1 = mAdapterService.obfuscateAddress(device);
+        Assert.assertTrue(obfuscatedAddress1.length > 0);
+        Assert.assertFalse(AdapterServiceTest.isByteArrayAllZero(obfuscatedAddress1));
+        mServiceBinder.factoryReset(mAttributionSource);
+        byte[] obfuscatedAddress2 = mAdapterService.obfuscateAddress(device);
+        Assert.assertTrue(obfuscatedAddress2.length > 0);
+        Assert.assertFalse(AdapterServiceTest.isByteArrayAllZero(obfuscatedAddress2));
+        Assert.assertFalse(Arrays.equals(obfuscatedAddress2,
+                obfuscatedAddress1));
+        doEnable(0, false);
+        byte[] obfuscatedAddress3 = mAdapterService.obfuscateAddress(device);
+        Assert.assertTrue(obfuscatedAddress3.length > 0);
+        Assert.assertFalse(AdapterServiceTest.isByteArrayAllZero(obfuscatedAddress3));
+        Assert.assertArrayEquals(obfuscatedAddress3,
+                obfuscatedAddress2);
+        mServiceBinder.factoryReset(mAttributionSource);
+        byte[] obfuscatedAddress4 = mAdapterService.obfuscateAddress(device);
+        Assert.assertTrue(obfuscatedAddress4.length > 0);
+        Assert.assertFalse(AdapterServiceTest.isByteArrayAllZero(obfuscatedAddress4));
+        Assert.assertFalse(Arrays.equals(obfuscatedAddress4,
+                obfuscatedAddress3));
+    }
+
+    /**
+     * Test: Verify that obfuscated Bluetooth address changes after factory reset and reloading
+     *       native layer
+     */
+    @Test
+    public void testObfuscateBluetoothAddress_FactoryResetAndReloadNativeLayer() throws
+            PackageManager.NameNotFoundException {
+        byte[] metricsSalt1 = AdapterServiceTest.getMetricsSalt(mAdapterConfig);
+        Assert.assertNotNull(metricsSalt1);
+        Assert.assertFalse(mAdapterService.getState() == BluetoothAdapter.STATE_ON);
+        BluetoothDevice device = TestUtils.getTestDevice(BluetoothAdapter.getDefaultAdapter(), 0);
+        byte[] obfuscatedAddress1 = mAdapterService.obfuscateAddress(device);
+        Assert.assertTrue(obfuscatedAddress1.length > 0);
+        Assert.assertFalse(AdapterServiceTest.isByteArrayAllZero(obfuscatedAddress1));
+        Assert.assertArrayEquals(AdapterServiceTest.obfuscateInJava(metricsSalt1, device),
+                obfuscatedAddress1);
+        mServiceBinder.factoryReset(mAttributionSource);
+        tearDown();
+        setUp();
+        // Cannot verify metrics salt since it is not written to disk until native cleanup
+        byte[] obfuscatedAddress2 = mAdapterService.obfuscateAddress(device);
+        Assert.assertTrue(obfuscatedAddress2.length > 0);
+        Assert.assertFalse(AdapterServiceTest.isByteArrayAllZero(obfuscatedAddress2));
+        Assert.assertFalse(Arrays.equals(obfuscatedAddress2,
+                obfuscatedAddress1));
+    }
+}
diff --git a/android/app/tests/unit/src/com/android/bluetooth/btservice/AdapterServiceRestartTest.java b/android/app/tests/unit/src/com/android/bluetooth/btservice/AdapterServiceRestartTest.java
new file mode 100644
index 0000000..c93837b
--- /dev/null
+++ b/android/app/tests/unit/src/com/android/bluetooth/btservice/AdapterServiceRestartTest.java
@@ -0,0 +1,374 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.bluetooth.btservice;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.*;
+
+import android.app.AlarmManager;
+import android.app.admin.DevicePolicyManager;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothManager;
+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.BatteryStatsManager;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.Looper;
+import android.os.PowerManager;
+import android.os.Process;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.permission.PermissionCheckerManager;
+import android.permission.PermissionManager;
+import android.provider.Settings;
+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.TestUtils;
+import com.android.bluetooth.Utils;
+import com.android.bluetooth.a2dp.A2dpService;
+import com.android.bluetooth.a2dpsink.A2dpSinkService;
+import com.android.bluetooth.avrcp.AvrcpTargetService;
+import com.android.bluetooth.avrcpcontroller.AvrcpControllerService;
+import com.android.bluetooth.bas.BatteryService;
+import com.android.bluetooth.bass_client.BassClientService;
+import com.android.bluetooth.csip.CsipSetCoordinatorService;
+import com.android.bluetooth.gatt.GattService;
+import com.android.bluetooth.hap.HapClientService;
+import com.android.bluetooth.hearingaid.HearingAidService;
+import com.android.bluetooth.hfp.HeadsetService;
+import com.android.bluetooth.hfpclient.HeadsetClientService;
+import com.android.bluetooth.hid.HidDeviceService;
+import com.android.bluetooth.hid.HidHostService;
+import com.android.bluetooth.le_audio.LeAudioService;
+import com.android.bluetooth.map.BluetoothMapService;
+import com.android.bluetooth.mapclient.MapClientService;
+import com.android.bluetooth.mcp.McpService;
+import com.android.bluetooth.opp.BluetoothOppService;
+import com.android.bluetooth.pan.PanService;
+import com.android.bluetooth.pbap.BluetoothPbapService;
+import com.android.bluetooth.pbapclient.PbapClientService;
+import com.android.bluetooth.sap.SapService;
+import com.android.bluetooth.tbs.TbsService;
+import com.android.bluetooth.vc.VolumeControlService;
+import com.android.internal.app.IBatteryStats;
+
+import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.HashMap;
+
+@MediumTest
+@RunWith(AndroidJUnit4.class)
+public class AdapterServiceRestartTest {
+    private static final String TAG = AdapterServiceTest.class.getSimpleName();
+
+    private AdapterService mAdapterService;
+    private AdapterService.AdapterServiceBinder mServiceBinder;
+
+    private @Mock Context mMockContext;
+    private @Mock ApplicationInfo mMockApplicationInfo;
+    private @Mock AlarmManager mMockAlarmManager;
+    private @Mock Resources mMockResources;
+    private @Mock UserManager mMockUserManager;
+    private @Mock DevicePolicyManager mMockDevicePolicyManager;
+    private @Mock IBluetoothCallback mIBluetoothCallback;
+    private @Mock Binder mBinder;
+    private @Mock AudioManager mAudioManager;
+    private @Mock android.app.Application mApplication;
+    private @Mock MetricsLogger mMockMetricsLogger;
+
+    // BatteryStatsManager is final and cannot be mocked with regular mockito, so just mock the
+    // underlying binder calls.
+    final BatteryStatsManager mBatteryStatsManager =
+            new BatteryStatsManager(mock(IBatteryStats.class));
+
+    private final AttributionSource mAttributionSource = new AttributionSource.Builder(
+            Process.myUid()).build();
+
+    private BluetoothManager mBluetoothManager;
+    private PowerManager mPowerManager;
+    private PermissionCheckerManager mPermissionCheckerManager;
+    private PermissionManager mPermissionManager;
+    private PackageManager mMockPackageManager;
+    private MockContentResolver mMockContentResolver;
+    private HashMap<String, HashMap<String, String>> mAdapterConfig;
+    private int mForegroundUserId;
+
+    private void configureEnabledProfiles() {
+        Log.e(TAG, "configureEnabledProfiles");
+        Config.setProfileEnabled(PanService.class, true);
+        Config.setProfileEnabled(BluetoothPbapService.class, true);
+        Config.setProfileEnabled(GattService.class, true);
+
+        Config.setProfileEnabled(A2dpService.class, false);
+        Config.setProfileEnabled(A2dpSinkService.class, false);
+        Config.setProfileEnabled(AvrcpTargetService.class, false);
+        Config.setProfileEnabled(AvrcpControllerService.class, false);
+        Config.setProfileEnabled(BassClientService.class, false);
+        Config.setProfileEnabled(BatteryService.class, false);
+        Config.setProfileEnabled(CsipSetCoordinatorService.class, false);
+        Config.setProfileEnabled(HapClientService.class, false);
+        Config.setProfileEnabled(HeadsetService.class, false);
+        Config.setProfileEnabled(HeadsetClientService.class, false);
+        Config.setProfileEnabled(HearingAidService.class, false);
+        Config.setProfileEnabled(HidDeviceService.class, false);
+        Config.setProfileEnabled(HidHostService.class, false);
+        Config.setProfileEnabled(LeAudioService.class, false);
+        Config.setProfileEnabled(TbsService.class, false);
+        Config.setProfileEnabled(BluetoothMapService.class, false);
+        Config.setProfileEnabled(MapClientService.class, false);
+        Config.setProfileEnabled(McpService.class, false);
+        Config.setProfileEnabled(BluetoothOppService.class, false);
+        Config.setProfileEnabled(PbapClientService.class, false);
+        Config.setProfileEnabled(SapService.class, false);
+        Config.setProfileEnabled(VolumeControlService.class, false);
+    }
+
+    @BeforeClass
+    public static void setupClass() {
+        Log.e(TAG, "setupClass");
+        // Bring native layer up and down to make sure config files are properly loaded
+        if (Looper.myLooper() == null) {
+            Looper.prepare();
+        }
+        Assert.assertNotNull(Looper.myLooper());
+        AdapterService adapterService = new AdapterService();
+        adapterService.initNative(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);
+        Assert.assertNotNull("metrics salt is null: " + adapterConfig.toString(),
+                AdapterServiceTest.getMetricsSalt(adapterConfig));
+    }
+
+    @Before
+    public void setUp() throws PackageManager.NameNotFoundException {
+        Log.e(TAG, "setUp()");
+        MockitoAnnotations.initMocks(this);
+        if (Looper.myLooper() == null) {
+            Looper.prepare();
+        }
+        Assert.assertNotNull(Looper.myLooper());
+
+        // Dispatch all async work through instrumentation so we can wait until
+        // it's drained below
+        AsyncTask.setDefaultExecutor((r) -> {
+            androidx.test.platform.app.InstrumentationRegistry.getInstrumentation()
+                    .runOnMainSync(r);
+        });
+        androidx.test.platform.app.InstrumentationRegistry.getInstrumentation().getUiAutomation()
+                .adoptShellPermissionIdentity();
+
+        androidx.test.platform.app.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(InstrumentationRegistry.getTargetContext());
+        mMockContentResolver.addProvider(Settings.AUTHORITY, new MockContentProvider() {
+            @Override
+            public Bundle call(String method, String request, Bundle args) {
+                return Bundle.EMPTY;
+            }
+        });
+
+        mPowerManager = InstrumentationRegistry.getTargetContext()
+                .getSystemService(PowerManager.class);
+        mPermissionCheckerManager = InstrumentationRegistry.getTargetContext()
+                .getSystemService(PermissionCheckerManager.class);
+
+        mPermissionManager = InstrumentationRegistry.getTargetContext()
+                .getSystemService(PermissionManager.class);
+
+        mBluetoothManager = InstrumentationRegistry.getTargetContext()
+                .getSystemService(BluetoothManager.class);
+
+        when(mMockContext.getCacheDir()).thenReturn(InstrumentationRegistry.getTargetContext()
+                .getCacheDir());
+        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);
+        when(mMockContext.getSystemService(Context.USER_SERVICE)).thenReturn(mMockUserManager);
+        when(mMockContext.getSystemServiceName(UserManager.class)).thenReturn(Context.USER_SERVICE);
+        when(mMockContext.getSystemService(Context.DEVICE_POLICY_SERVICE)).thenReturn(
+                mMockDevicePolicyManager);
+        when(mMockContext.getSystemServiceName(DevicePolicyManager.class))
+                .thenReturn(Context.DEVICE_POLICY_SERVICE);
+        when(mMockContext.getSystemService(Context.POWER_SERVICE)).thenReturn(mPowerManager);
+        when(mMockContext.getSystemServiceName(PowerManager.class))
+                .thenReturn(Context.POWER_SERVICE);
+        when(mMockContext.getSystemServiceName(PermissionCheckerManager.class))
+                .thenReturn(Context.PERMISSION_CHECKER_SERVICE);
+        when(mMockContext.getSystemService(Context.PERMISSION_CHECKER_SERVICE))
+                .thenReturn(mPermissionCheckerManager);
+        when(mMockContext.getSystemServiceName(PermissionManager.class))
+                .thenReturn(Context.PERMISSION_SERVICE);
+        when(mMockContext.getSystemService(Context.PERMISSION_SERVICE))
+                .thenReturn(mPermissionManager);
+        when(mMockContext.getSystemService(Context.ALARM_SERVICE)).thenReturn(mMockAlarmManager);
+        when(mMockContext.getSystemServiceName(AlarmManager.class))
+                .thenReturn(Context.ALARM_SERVICE);
+        when(mMockContext.getSystemService(Context.AUDIO_SERVICE)).thenReturn(mAudioManager);
+        when(mMockContext.getSystemServiceName(AudioManager.class))
+                .thenReturn(Context.AUDIO_SERVICE);
+        when(mMockContext.getSystemService(Context.BATTERY_STATS_SERVICE))
+                .thenReturn(mBatteryStatsManager);
+        when(mMockContext.getSystemServiceName(BatteryStatsManager.class))
+                .thenReturn(Context.BATTERY_STATS_SERVICE);
+        when(mMockContext.getSystemService(Context.BLUETOOTH_SERVICE))
+                .thenReturn(mBluetoothManager);
+        when(mMockContext.getSystemServiceName(BluetoothManager.class))
+                .thenReturn(Context.BLUETOOTH_SERVICE);
+        when(mMockContext.getSharedPreferences(anyString(), anyInt()))
+                .thenReturn(InstrumentationRegistry.getTargetContext()
+                        .getSharedPreferences("AdapterServiceTestPrefs", Context.MODE_PRIVATE));
+
+        when(mMockContext.getAttributionSource()).thenReturn(mAttributionSource);
+        doAnswer(invocation -> {
+            Object[] args = invocation.getArguments();
+            return InstrumentationRegistry.getTargetContext().getDatabasePath((String) args[0]);
+        }).when(mMockContext).getDatabasePath(anyString());
+
+        // Sets the foreground user id to match that of the tests (restored in tearDown)
+        mForegroundUserId = Utils.getForegroundUserId();
+        int callingUid = Binder.getCallingUid();
+        UserHandle callingUser = UserHandle.getUserHandleForUid(callingUid);
+        Utils.setForegroundUserId(callingUser.getIdentifier());
+
+        when(mMockDevicePolicyManager.isCommonCriteriaModeEnabled(any())).thenReturn(false);
+
+        when(mIBluetoothCallback.asBinder()).thenReturn(mBinder);
+
+        doReturn(Process.BLUETOOTH_UID).when(mMockPackageManager)
+                .getPackageUidAsUser(any(), anyInt(), anyInt());
+
+        when(mMockMetricsLogger.init(any())).thenReturn(true);
+        when(mMockMetricsLogger.close()).thenReturn(true);
+
+        configureEnabledProfiles();
+        Config.init(mMockContext);
+
+        mAdapterService.setMetricsLogger(mMockMetricsLogger);
+
+        // Attach a context to the service for permission checks.
+        mAdapterService.attach(mMockContext, null, null, null, mApplication, null);
+        mAdapterService.onCreate();
+
+        // Wait for any async events to drain
+        androidx.test.platform.app.InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+
+        mServiceBinder.registerCallback(mIBluetoothCallback, mAttributionSource);
+
+        mAdapterConfig = TestUtils.readAdapterConfig();
+        Assert.assertNotNull(mAdapterConfig);
+    }
+
+    @After
+    public void tearDown() {
+        Log.e(TAG, "tearDown()");
+
+        // Restores the foregroundUserId to the ID prior to the test setup
+        Utils.setForegroundUserId(mForegroundUserId);
+
+        mServiceBinder.unregisterCallback(mIBluetoothCallback, mAttributionSource);
+        mAdapterService.cleanup();
+    }
+
+    @AfterClass
+    public static void tearDownOnce() {
+        AsyncTask.setDefaultExecutor(AsyncTask.SERIAL_EXECUTOR);
+    }
+
+    /**
+     * Test: Check if obfuscated Bluetooth address stays the same after re-initializing
+     *       {@link AdapterService}
+     */
+    @Test
+    public void testObfuscateBluetoothAddress_PersistentBetweenAdapterServiceInitialization() throws
+            PackageManager.NameNotFoundException {
+        byte[] metricsSalt = AdapterServiceTest.getMetricsSalt(mAdapterConfig);
+        Assert.assertNotNull(metricsSalt);
+        Assert.assertFalse(mAdapterService.getState() == BluetoothAdapter.STATE_ON);
+        BluetoothDevice device = TestUtils.getTestDevice(BluetoothAdapter.getDefaultAdapter(), 0);
+        byte[] obfuscatedAddress1 = mAdapterService.obfuscateAddress(device);
+        Assert.assertTrue(obfuscatedAddress1.length > 0);
+        Assert.assertFalse(AdapterServiceTest.isByteArrayAllZero(obfuscatedAddress1));
+        Assert.assertArrayEquals(AdapterServiceTest.obfuscateInJava(metricsSalt, device),
+                obfuscatedAddress1);
+        tearDown();
+        setUp();
+
+        byte[] metricsSalt2 = AdapterServiceTest.getMetricsSalt(mAdapterConfig);
+        Assert.assertNotNull(metricsSalt2);
+        Assert.assertArrayEquals(metricsSalt, metricsSalt2);
+
+        Assert.assertFalse(mAdapterService.getState() == BluetoothAdapter.STATE_ON);
+        byte[] obfuscatedAddress2 = mAdapterService.obfuscateAddress(device);
+        Assert.assertTrue(obfuscatedAddress2.length > 0);
+        Assert.assertFalse(AdapterServiceTest.isByteArrayAllZero(obfuscatedAddress2));
+        Assert.assertArrayEquals(obfuscatedAddress2,
+                obfuscatedAddress1);
+    }
+
+    /**
+     * Test: Check if id gotten stays the same after re-initializing
+     *       {@link AdapterService}
+     */
+    @Test
+    public void testgetMetricId_PersistentBetweenAdapterServiceInitialization() throws
+            PackageManager.NameNotFoundException {
+        Assert.assertFalse(mAdapterService.getState() == BluetoothAdapter.STATE_ON);
+        BluetoothDevice device = TestUtils.getTestDevice(BluetoothAdapter.getDefaultAdapter(), 0);
+        int id1 = mAdapterService.getMetricId(device);
+        Assert.assertTrue(id1 > 0);
+        tearDown();
+        setUp();
+        Assert.assertFalse(mAdapterService.getState() == BluetoothAdapter.STATE_ON);
+        int id2 = mAdapterService.getMetricId(device);
+        Assert.assertEquals(id2, id1);
+    }
+}
diff --git a/android/app/tests/unit/src/com/android/bluetooth/btservice/AdapterServiceTest.java b/android/app/tests/unit/src/com/android/bluetooth/btservice/AdapterServiceTest.java
index 29414ca..c320b35 100644
--- a/android/app/tests/unit/src/com/android/bluetooth/btservice/AdapterServiceTest.java
+++ b/android/app/tests/unit/src/com/android/bluetooth/btservice/AdapterServiceTest.java
@@ -101,7 +101,6 @@
 import java.io.PrintWriter;
 import java.security.InvalidKeyException;
 import java.security.NoSuchAlgorithmException;
-import java.util.Arrays;
 import java.util.HashMap;
 
 import javax.crypto.Mac;
@@ -280,6 +279,10 @@
                 .thenReturn(mBatteryStatsManager);
         when(mMockContext.getSystemServiceName(BatteryStatsManager.class))
                 .thenReturn(Context.BATTERY_STATS_SERVICE);
+        when(mMockContext.getSharedPreferences(anyString(), anyInt()))
+                .thenReturn(InstrumentationRegistry.getTargetContext()
+                        .getSharedPreferences("AdapterServiceTestPrefs", Context.MODE_PRIVATE));
+
         when(mMockContext.getAttributionSource()).thenReturn(mAttributionSource);
         doAnswer(invocation -> {
             Object[] args = invocation.getArguments();
@@ -767,103 +770,6 @@
                 obfuscatedAddress1);
     }
 
-    /**
-     * Test: Check if obfuscated Bluetooth address stays the same after re-initializing
-     *       {@link AdapterService}
-     */
-    @Test
-    public void testObfuscateBluetoothAddress_PersistentBetweenAdapterServiceInitialization() throws
-            PackageManager.NameNotFoundException {
-        byte[] metricsSalt = getMetricsSalt(mAdapterConfig);
-        Assert.assertNotNull(metricsSalt);
-        Assert.assertFalse(mAdapterService.getState() == BluetoothAdapter.STATE_ON);
-        BluetoothDevice device = TestUtils.getTestDevice(BluetoothAdapter.getDefaultAdapter(), 0);
-        byte[] obfuscatedAddress1 = mAdapterService.obfuscateAddress(device);
-        Assert.assertTrue(obfuscatedAddress1.length > 0);
-        Assert.assertFalse(isByteArrayAllZero(obfuscatedAddress1));
-        Assert.assertArrayEquals(obfuscateInJava(metricsSalt, device),
-                obfuscatedAddress1);
-        tearDown();
-        setUp();
-        Assert.assertFalse(mAdapterService.getState() == BluetoothAdapter.STATE_ON);
-        byte[] obfuscatedAddress2 = mAdapterService.obfuscateAddress(device);
-        Assert.assertTrue(obfuscatedAddress2.length > 0);
-        Assert.assertFalse(isByteArrayAllZero(obfuscatedAddress2));
-        Assert.assertArrayEquals(obfuscatedAddress2,
-                obfuscatedAddress1);
-    }
-
-    /**
-     * Test: Verify that obfuscated Bluetooth address changes after factory reset
-     *
-     * There are 4 types of factory reset that we are talking about:
-     * 1. Factory reset all user data from Settings -> Will restart phone
-     * 2. Factory reset WiFi and Bluetooth from Settings -> Will only restart WiFi and BT
-     * 3. Call BluetoothAdapter.factoryReset() -> Will disable Bluetooth and reset config in
-     * memory and disk
-     * 4. Call AdapterService.factoryReset() -> Will only reset config in memory
-     *
-     * We can only use No. 4 here
-     */
-    @Ignore("AdapterService.factoryReset() does not reload config into memory and hence old salt"
-            + " is still used until next time Bluetooth library is initialized. However Bluetooth"
-            + " cannot be used until Bluetooth process restart any way. Thus it is almost"
-            + " guaranteed that user has to re-enable Bluetooth and hence re-generate new salt"
-            + " after factory reset")
-    @Test
-    public void testObfuscateBluetoothAddress_FactoryReset() {
-        Assert.assertFalse(mAdapterService.getState() == BluetoothAdapter.STATE_ON);
-        BluetoothDevice device = TestUtils.getTestDevice(BluetoothAdapter.getDefaultAdapter(), 0);
-        byte[] obfuscatedAddress1 = mAdapterService.obfuscateAddress(device);
-        Assert.assertTrue(obfuscatedAddress1.length > 0);
-        Assert.assertFalse(isByteArrayAllZero(obfuscatedAddress1));
-        mServiceBinder.factoryReset(mAttributionSource);
-        byte[] obfuscatedAddress2 = mAdapterService.obfuscateAddress(device);
-        Assert.assertTrue(obfuscatedAddress2.length > 0);
-        Assert.assertFalse(isByteArrayAllZero(obfuscatedAddress2));
-        Assert.assertFalse(Arrays.equals(obfuscatedAddress2,
-                obfuscatedAddress1));
-        doEnable(0, false);
-        byte[] obfuscatedAddress3 = mAdapterService.obfuscateAddress(device);
-        Assert.assertTrue(obfuscatedAddress3.length > 0);
-        Assert.assertFalse(isByteArrayAllZero(obfuscatedAddress3));
-        Assert.assertArrayEquals(obfuscatedAddress3,
-                obfuscatedAddress2);
-        mServiceBinder.factoryReset(mAttributionSource);
-        byte[] obfuscatedAddress4 = mAdapterService.obfuscateAddress(device);
-        Assert.assertTrue(obfuscatedAddress4.length > 0);
-        Assert.assertFalse(isByteArrayAllZero(obfuscatedAddress4));
-        Assert.assertFalse(Arrays.equals(obfuscatedAddress4,
-                obfuscatedAddress3));
-    }
-
-    /**
-     * Test: Verify that obfuscated Bluetooth address changes after factory reset and reloading
-     *       native layer
-     */
-    @Test
-    public void testObfuscateBluetoothAddress_FactoryResetAndReloadNativeLayer() throws
-            PackageManager.NameNotFoundException {
-        byte[] metricsSalt1 = getMetricsSalt(mAdapterConfig);
-        Assert.assertNotNull(metricsSalt1);
-        Assert.assertFalse(mAdapterService.getState() == BluetoothAdapter.STATE_ON);
-        BluetoothDevice device = TestUtils.getTestDevice(BluetoothAdapter.getDefaultAdapter(), 0);
-        byte[] obfuscatedAddress1 = mAdapterService.obfuscateAddress(device);
-        Assert.assertTrue(obfuscatedAddress1.length > 0);
-        Assert.assertFalse(isByteArrayAllZero(obfuscatedAddress1));
-        Assert.assertArrayEquals(obfuscateInJava(metricsSalt1, device),
-                obfuscatedAddress1);
-        mServiceBinder.factoryReset(mAttributionSource);
-        tearDown();
-        setUp();
-        // Cannot verify metrics salt since it is not written to disk until native cleanup
-        byte[] obfuscatedAddress2 = mAdapterService.obfuscateAddress(device);
-        Assert.assertTrue(obfuscatedAddress2.length > 0);
-        Assert.assertFalse(isByteArrayAllZero(obfuscatedAddress2));
-        Assert.assertFalse(Arrays.equals(obfuscatedAddress2,
-                obfuscatedAddress1));
-    }
-
     @Test
     public void testAddressConsolidation() {
         // Create device properties
@@ -881,7 +787,7 @@
         Assert.assertEquals(identityAddress, TEST_BT_ADDR_2);
     }
 
-    private static byte[] getMetricsSalt(HashMap<String, HashMap<String, String>> adapterConfig) {
+    public static byte[] getMetricsSalt(HashMap<String, HashMap<String, String>> adapterConfig) {
         HashMap<String, String> metricsSection = adapterConfig.get("Metrics");
         if (metricsSection == null) {
             Log.e(TAG, "Metrics section is null: " + adapterConfig.toString());
@@ -900,7 +806,7 @@
         return metricsSalt;
     }
 
-    private static byte[] obfuscateInJava(byte[] key, BluetoothDevice device) {
+    public static byte[] obfuscateInJava(byte[] key, BluetoothDevice device) {
         String algorithm = "HmacSHA256";
         try {
             Mac hmac256 = Mac.getInstance(algorithm);
@@ -912,7 +818,7 @@
         }
     }
 
-    private static boolean isByteArrayAllZero(byte[] byteArray) {
+    public static boolean isByteArrayAllZero(byte[] byteArray) {
         for (byte i : byteArray) {
             if (i != 0) {
                 return false;
@@ -980,24 +886,6 @@
         Assert.assertEquals(id3, id1);
     }
 
-    /**
-     * Test: Check if id gotten stays the same after re-initializing
-     *       {@link AdapterService}
-     */
-    @Test
-    public void testgetMetricId_PersistentBetweenAdapterServiceInitialization() throws
-            PackageManager.NameNotFoundException {
-        Assert.assertFalse(mAdapterService.getState() == BluetoothAdapter.STATE_ON);
-        BluetoothDevice device = TestUtils.getTestDevice(BluetoothAdapter.getDefaultAdapter(), 0);
-        int id1 = mAdapterService.getMetricId(device);
-        Assert.assertTrue(id1 > 0);
-        tearDown();
-        setUp();
-        Assert.assertFalse(mAdapterService.getState() == BluetoothAdapter.STATE_ON);
-        int id2 = mAdapterService.getMetricId(device);
-        Assert.assertEquals(id2, id1);
-    }
-
     @Test
     public void testDump_doesNotCrash() {
         FileDescriptor fd = new FileDescriptor();
diff --git a/android/app/tests/unit/src/com/android/bluetooth/btservice/CompanionManagerTest.java b/android/app/tests/unit/src/com/android/bluetooth/btservice/CompanionManagerTest.java
new file mode 100644
index 0000000..41746f0
--- /dev/null
+++ b/android/app/tests/unit/src/com/android/bluetooth/btservice/CompanionManagerTest.java
@@ -0,0 +1,169 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.bluetooth.btservice;
+
+import static org.mockito.Mockito.*;
+
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothGatt;
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.os.HandlerThread;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.MediumTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.bluetooth.TestUtils;
+
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@MediumTest
+@RunWith(AndroidJUnit4.class)
+public class CompanionManagerTest {
+
+    private static final String TEST_DEVICE = "11:22:33:44:55:66";
+
+    private AdapterProperties mAdapterProperties;
+    private Context mTargetContext;
+    private CompanionManager mCompanionManager;
+
+    private HandlerThread mHandlerThread;
+
+    @Mock
+    private AdapterService mAdapterService;
+    @Mock
+    SharedPreferences mSharedPreferences;
+    @Mock
+    SharedPreferences.Editor mEditor;
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+        mTargetContext = InstrumentationRegistry.getTargetContext();
+        // Prepare the TestUtils
+        TestUtils.setAdapterService(mAdapterService);
+        // Start handler thread for this test
+        mHandlerThread = new HandlerThread("CompanionManagerTestHandlerThread");
+        mHandlerThread.start();
+        // Mock the looper
+        doReturn(mHandlerThread.getLooper()).when(mAdapterService).getMainLooper();
+        // Mock SharedPreferences
+        when(mSharedPreferences.edit()).thenReturn(mEditor);
+        doReturn(mSharedPreferences).when(mAdapterService).getSharedPreferences(eq(
+                CompanionManager.COMPANION_INFO), eq(Context.MODE_PRIVATE));
+        // Tell the AdapterService that it is a mock (see isMock documentation)
+        doReturn(true).when(mAdapterService).isMock();
+        // Use the resources in the instrumentation instead of the mocked AdapterService
+        when(mAdapterService.getResources()).thenReturn(mTargetContext.getResources());
+
+        // Must be called to initialize services
+        mCompanionManager = new CompanionManager(mAdapterService, null);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        mHandlerThread.quit();
+        TestUtils.clearAdapterService(mAdapterService);
+    }
+
+    @Test
+    public void testLoadCompanionInfo_hasCompanionDeviceKey() {
+        loadCompanionInfoHelper(TEST_DEVICE, CompanionManager.COMPANION_TYPE_PRIMARY);
+    }
+
+    @Test
+    public void testLoadCompanionInfo_noCompanionDeviceSetButHaveBondedDevices_shouldNotCrash() {
+        BluetoothDevice[] devices = new BluetoothDevice[2];
+        doReturn(devices).when(mAdapterService).getBondedDevices();
+        doThrow(new IllegalArgumentException())
+                .when(mSharedPreferences)
+                .getInt(eq(CompanionManager.COMPANION_TYPE_KEY), anyInt());
+        mCompanionManager.loadCompanionInfo();
+    }
+
+    @Test
+    public void testIsCompanionDevice() {
+        loadCompanionInfoHelper(TEST_DEVICE, CompanionManager.COMPANION_TYPE_NONE);
+        Assert.assertTrue(mCompanionManager.isCompanionDevice(TEST_DEVICE));
+
+        loadCompanionInfoHelper(TEST_DEVICE, CompanionManager.COMPANION_TYPE_PRIMARY);
+        Assert.assertTrue(mCompanionManager.isCompanionDevice(TEST_DEVICE));
+
+        loadCompanionInfoHelper(TEST_DEVICE, CompanionManager.COMPANION_TYPE_SECONDARY);
+        Assert.assertTrue(mCompanionManager.isCompanionDevice(TEST_DEVICE));
+    }
+
+    @Test
+    public void testGetGattConnParameterPrimary() {
+        loadCompanionInfoHelper(TEST_DEVICE, CompanionManager.COMPANION_TYPE_PRIMARY);
+        checkReasonableConnParameterHelper(BluetoothGatt.CONNECTION_PRIORITY_HIGH);
+        checkReasonableConnParameterHelper(BluetoothGatt.CONNECTION_PRIORITY_BALANCED);
+        checkReasonableConnParameterHelper(BluetoothGatt.CONNECTION_PRIORITY_LOW_POWER);
+
+        loadCompanionInfoHelper(TEST_DEVICE, CompanionManager.COMPANION_TYPE_SECONDARY);
+        checkReasonableConnParameterHelper(BluetoothGatt.CONNECTION_PRIORITY_HIGH);
+        checkReasonableConnParameterHelper(BluetoothGatt.CONNECTION_PRIORITY_BALANCED);
+        checkReasonableConnParameterHelper(BluetoothGatt.CONNECTION_PRIORITY_LOW_POWER);
+
+        loadCompanionInfoHelper(TEST_DEVICE, CompanionManager.COMPANION_TYPE_NONE);
+        checkReasonableConnParameterHelper(BluetoothGatt.CONNECTION_PRIORITY_HIGH);
+        checkReasonableConnParameterHelper(BluetoothGatt.CONNECTION_PRIORITY_BALANCED);
+        checkReasonableConnParameterHelper(BluetoothGatt.CONNECTION_PRIORITY_LOW_POWER);
+    }
+
+    private void loadCompanionInfoHelper(String address, int companionType) {
+        doReturn(address)
+                .when(mSharedPreferences)
+                .getString(eq(CompanionManager.COMPANION_DEVICE_KEY), anyString());
+        doReturn(companionType)
+                .when(mSharedPreferences)
+                .getInt(eq(CompanionManager.COMPANION_TYPE_KEY), anyInt());
+        mCompanionManager.loadCompanionInfo();
+    }
+
+    private void checkReasonableConnParameterHelper(int priority) {
+        // Max/Min values from the Bluetooth spec Version 5.3 | Vol 4, Part E | 7.8.18
+        final int minInterval = 6;    // 0x0006
+        final int maxInterval = 3200; // 0x0C80
+        final int minLatency = 0;     // 0x0000
+        final int maxLatency = 499;   // 0x01F3
+
+        int min = mCompanionManager.getGattConnParameters(
+                TEST_DEVICE, CompanionManager.GATT_CONN_INTERVAL_MIN,
+                priority);
+        int max = mCompanionManager.getGattConnParameters(
+                TEST_DEVICE, CompanionManager.GATT_CONN_INTERVAL_MAX,
+                priority);
+        int latency = mCompanionManager.getGattConnParameters(
+                TEST_DEVICE, CompanionManager.GATT_CONN_LATENCY,
+                priority);
+
+        Assert.assertTrue(max >= min);
+        Assert.assertTrue(max >= minInterval);
+        Assert.assertTrue(min >= minInterval);
+        Assert.assertTrue(max <= maxInterval);
+        Assert.assertTrue(min <= maxInterval);
+        Assert.assertTrue(latency >= minLatency);
+        Assert.assertTrue(latency <= maxLatency);
+    }
+}
diff --git a/android/app/tests/unit/src/com/android/bluetooth/btservice/DataMigrationTest.java b/android/app/tests/unit/src/com/android/bluetooth/btservice/DataMigrationTest.java
index d7f9bfb..ea39f28 100644
--- a/android/app/tests/unit/src/com/android/bluetooth/btservice/DataMigrationTest.java
+++ b/android/app/tests/unit/src/com/android/bluetooth/btservice/DataMigrationTest.java
@@ -66,7 +66,7 @@
 
     private static final String AUTHORITY = "bluetooth_legacy.provider";
 
-    private static final String TEST_PREF = "TestPref";
+    private static final String TEST_PREF = "DatabaseTestPref";
 
     private MockContentResolver mMockContentResolver;
 
@@ -82,6 +82,7 @@
         mTargetContext = InstrumentationRegistry.getTargetContext();
         mTargetContext.deleteSharedPreferences(TEST_PREF);
         mPrefs = mTargetContext.getSharedPreferences(TEST_PREF, Context.MODE_PRIVATE);
+        mPrefs.edit().clear().apply();
 
         mMockContentResolver = new MockContentResolver(mTargetContext);
         when(mMockContext.getContentResolver()).thenReturn(mMockContentResolver);
@@ -93,7 +94,8 @@
 
     @After
     public void tearDown() throws Exception {
-        mTargetContext.deleteSharedPreferences("TestPref");
+        mPrefs.edit().clear().apply();
+        mTargetContext.deleteSharedPreferences(TEST_PREF);
         mTargetContext.deleteDatabase("TestBluetoothDb");
         mTargetContext.deleteDatabase("TestOppDb");
     }
diff --git a/android/app/tests/unit/src/com/android/bluetooth/btservice/MetricsLoggerTest.java b/android/app/tests/unit/src/com/android/bluetooth/btservice/MetricsLoggerTest.java
index 851e998..731580a 100644
--- a/android/app/tests/unit/src/com/android/bluetooth/btservice/MetricsLoggerTest.java
+++ b/android/app/tests/unit/src/com/android/bluetooth/btservice/MetricsLoggerTest.java
@@ -26,6 +26,8 @@
 import com.android.bluetooth.BluetoothMetricsProto.ProfileConnectionStats;
 import com.android.bluetooth.BluetoothMetricsProto.ProfileId;
 
+import com.google.common.hash.BloomFilter;
+import com.google.common.hash.Funnels;
 import org.junit.After;
 import org.junit.Assert;
 import org.junit.Before;
@@ -34,6 +36,9 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.IOException;
 import java.util.HashMap;
 import java.util.List;
 
@@ -43,12 +48,15 @@
 @MediumTest
 @RunWith(AndroidJUnit4.class)
 public class MetricsLoggerTest {
+    private static final String TEST_BLOOMFILTER_NAME = "TestBloomfilter";
+
     private TestableMetricsLogger mTestableMetricsLogger;
     @Mock
     private AdapterService mMockAdapterService;
 
     public class TestableMetricsLogger extends MetricsLogger {
         public HashMap<Integer, Long> mTestableCounters = new HashMap<>();
+        public HashMap<String, Integer> mTestableDeviceNames = new HashMap<>();
 
         @Override
         public boolean count(int key, long count) {
@@ -63,6 +71,11 @@
         @Override
         protected void cancelPendingDrain() {
         }
+
+        @Override
+        protected void statslogBluetoothDeviceNames(String matchedString, byte[] sha256) {
+            mTestableDeviceNames.merge(matchedString, 1, Integer::sum);
+        }
     }
 
     @Before
@@ -71,6 +84,7 @@
         // Dump metrics to clean up internal states
         MetricsLogger.dumpProto(BluetoothLog.newBuilder());
         mTestableMetricsLogger = new TestableMetricsLogger();
+        mTestableMetricsLogger.mBloomFilterInitialized = true;
         doReturn(null)
                 .when(mMockAdapterService).registerReceiver(any(), any());
     }
@@ -207,4 +221,181 @@
         Assert.assertTrue(mTestableMetricsLogger.isInitialized());
         Assert.assertFalse(mTestableMetricsLogger.init(mMockAdapterService));
     }
+
+    @Test
+    public void testDeviceNameUploadingDeviceSet1() {
+        initTestingBloomfitler();
+
+        mTestableMetricsLogger.logSanitizedBluetoothDeviceName("a b c d e f g h pixel 7");
+        Assert.assertTrue(mTestableMetricsLogger.mTestableDeviceNames.isEmpty());
+
+        mTestableMetricsLogger.logSanitizedBluetoothDeviceName("AirpoDspro");
+        Assert.assertEquals(1,
+                mTestableMetricsLogger.mTestableDeviceNames.get("airpodspro").intValue());
+
+        mTestableMetricsLogger.logSanitizedBluetoothDeviceName("AirpoDs-pro");
+        Assert.assertEquals(2,
+                mTestableMetricsLogger.mTestableDeviceNames.get("airpodspro").intValue());
+
+        mTestableMetricsLogger.logSanitizedBluetoothDeviceName("Someone's AirpoDs");
+        Assert.assertEquals(1,
+                mTestableMetricsLogger.mTestableDeviceNames.get("airpods").intValue());
+
+        mTestableMetricsLogger.logSanitizedBluetoothDeviceName("Who's Pixel 7");
+        Assert.assertEquals(1,
+                mTestableMetricsLogger.mTestableDeviceNames.get("pixel7").intValue());
+
+        mTestableMetricsLogger.logSanitizedBluetoothDeviceName("陈的pixel 7手机");
+        Assert.assertEquals(2,
+                mTestableMetricsLogger.mTestableDeviceNames.get("pixel7").intValue());
+
+        mTestableMetricsLogger.logSanitizedBluetoothDeviceName("pixel 7 pro");
+        Assert.assertEquals(1,
+                mTestableMetricsLogger.mTestableDeviceNames.get("pixel7pro").intValue());
+
+        mTestableMetricsLogger.logSanitizedBluetoothDeviceName("My Pixel 7 PRO");
+        Assert.assertEquals(2,
+                mTestableMetricsLogger.mTestableDeviceNames.get("pixel7pro").intValue());
+
+        mTestableMetricsLogger.logSanitizedBluetoothDeviceName("My Pixel   7   PRO");
+        Assert.assertEquals(3,
+                mTestableMetricsLogger.mTestableDeviceNames.get("pixel7pro").intValue());
+
+        mTestableMetricsLogger.logSanitizedBluetoothDeviceName("My Pixel   7   - PRO");
+        Assert.assertEquals(4,
+                mTestableMetricsLogger.mTestableDeviceNames.get("pixel7pro").intValue());
+
+        mTestableMetricsLogger.logSanitizedBluetoothDeviceName("My BMW X5");
+        Assert.assertEquals(1,
+                mTestableMetricsLogger.mTestableDeviceNames.get("bmwx5").intValue());
+
+        mTestableMetricsLogger.logSanitizedBluetoothDeviceName("Jane Doe's Tesla Model--X");
+        Assert.assertEquals(1,
+                mTestableMetricsLogger.mTestableDeviceNames.get("teslamodelx").intValue());
+
+        mTestableMetricsLogger.logSanitizedBluetoothDeviceName("TESLA of Jane DOE");
+        Assert.assertEquals(1,
+                mTestableMetricsLogger.mTestableDeviceNames.get("tesla").intValue());
+
+        mTestableMetricsLogger
+                .logSanitizedBluetoothDeviceName("SONY WH-1000XM noise cancelling headsets");
+        Assert.assertEquals(1,
+                mTestableMetricsLogger.mTestableDeviceNames.get("sonywh1000xm").intValue());
+
+        mTestableMetricsLogger
+                .logSanitizedBluetoothDeviceName("SONY WH-1000XM4 noise cancelling headsets");
+        Assert.assertEquals(1,
+                mTestableMetricsLogger.mTestableDeviceNames.get("sonywh1000xm4").intValue());
+
+        mTestableMetricsLogger
+                .logSanitizedBluetoothDeviceName("Amazon Echo Dot in Kitchen");
+        Assert.assertEquals(1,
+                mTestableMetricsLogger.mTestableDeviceNames.get("amazonechodot").intValue());
+
+        mTestableMetricsLogger
+                .logSanitizedBluetoothDeviceName("斯巴鲁 Starlink");
+        Assert.assertEquals(1,
+                mTestableMetricsLogger.mTestableDeviceNames.get("starlink").intValue());
+
+        mTestableMetricsLogger
+                .logSanitizedBluetoothDeviceName("大黄蜂MyLink");
+        Assert.assertEquals(1,
+                mTestableMetricsLogger.mTestableDeviceNames.get("mylink").intValue());
+
+        mTestableMetricsLogger
+                .logSanitizedBluetoothDeviceName("Dad's Fitbit Charge 3");
+        Assert.assertEquals(1,
+                mTestableMetricsLogger.mTestableDeviceNames.get("fitbitcharge3").intValue());
+
+        mTestableMetricsLogger.mTestableDeviceNames.clear();
+        mTestableMetricsLogger.logSanitizedBluetoothDeviceName("");
+        Assert.assertTrue(mTestableMetricsLogger.mTestableDeviceNames.isEmpty());
+
+        mTestableMetricsLogger.logSanitizedBluetoothDeviceName(" ");
+        Assert.assertTrue(mTestableMetricsLogger.mTestableDeviceNames.isEmpty());
+
+        mTestableMetricsLogger.logSanitizedBluetoothDeviceName("SomeDevice1");
+        Assert.assertTrue(mTestableMetricsLogger.mTestableDeviceNames.isEmpty());
+
+        mTestableMetricsLogger.logSanitizedBluetoothDeviceName("Bluetooth headset");
+        Assert.assertTrue(mTestableMetricsLogger.mTestableDeviceNames.isEmpty());
+
+        mTestableMetricsLogger.logSanitizedBluetoothDeviceName("Some Device-2");
+        Assert.assertTrue(mTestableMetricsLogger.mTestableDeviceNames.isEmpty());
+
+        mTestableMetricsLogger.logSanitizedBluetoothDeviceName("abcgfDG gdfg");
+        Assert.assertTrue(mTestableMetricsLogger.mTestableDeviceNames.isEmpty());
+    }
+
+    @Test
+    public void testDeviceNameUploadingDeviceSet2() {
+        initTestingBloomfitler();
+
+        mTestableMetricsLogger
+                .logSanitizedBluetoothDeviceName("Galaxy Buds pro");
+        Assert.assertEquals(1,
+                mTestableMetricsLogger.mTestableDeviceNames.get("galaxybudspro").intValue());
+
+        mTestableMetricsLogger
+                .logSanitizedBluetoothDeviceName("Mike's new Galaxy Buds 2");
+        Assert.assertEquals(1,
+                mTestableMetricsLogger.mTestableDeviceNames.get("galaxybuds2").intValue());
+
+        mTestableMetricsLogger
+                .logSanitizedBluetoothDeviceName("My third Ford F-150");
+        Assert.assertEquals(1,
+                mTestableMetricsLogger.mTestableDeviceNames.get("fordf150").intValue());
+
+        mTestableMetricsLogger
+                .logSanitizedBluetoothDeviceName("BOSE QC_35 Noise Cancelling Headsets");
+        Assert.assertEquals(1,
+                mTestableMetricsLogger.mTestableDeviceNames.get("boseqc35").intValue());
+
+        mTestableMetricsLogger
+                .logSanitizedBluetoothDeviceName("BOSE Quiet Comfort 35 Headsets");
+        Assert.assertEquals(1,
+                mTestableMetricsLogger.mTestableDeviceNames.get("bosequietcomfort35").intValue());
+
+        mTestableMetricsLogger
+                .logSanitizedBluetoothDeviceName("Fitbit versa 3 band");
+        Assert.assertEquals(1,
+                mTestableMetricsLogger.mTestableDeviceNames.get("fitbitversa3").intValue());
+
+        mTestableMetricsLogger
+                .logSanitizedBluetoothDeviceName("vw atlas");
+        Assert.assertEquals(1,
+                mTestableMetricsLogger.mTestableDeviceNames.get("vwatlas").intValue());
+
+        mTestableMetricsLogger
+                .logSanitizedBluetoothDeviceName("My volkswagen tiguan");
+        Assert.assertEquals(1,
+                mTestableMetricsLogger.mTestableDeviceNames.get("volkswagentiguan").intValue());
+
+        mTestableMetricsLogger.mTestableDeviceNames.clear();
+        mTestableMetricsLogger.logSanitizedBluetoothDeviceName("");
+        Assert.assertTrue(mTestableMetricsLogger.mTestableDeviceNames.isEmpty());
+
+        mTestableMetricsLogger.logSanitizedBluetoothDeviceName(" ");
+        Assert.assertTrue(mTestableMetricsLogger.mTestableDeviceNames.isEmpty());
+
+        mTestableMetricsLogger.logSanitizedBluetoothDeviceName("weirddevice");
+        Assert.assertTrue(mTestableMetricsLogger.mTestableDeviceNames.isEmpty());
+
+        mTestableMetricsLogger.logSanitizedBluetoothDeviceName(""
+                + "My BOSE Quiet Comfort 35 Noise Cancelling Headsets");
+        // Too long, won't process
+        Assert.assertTrue(mTestableMetricsLogger.mTestableDeviceNames.isEmpty());
+
+    }
+    private void initTestingBloomfitler() {
+        byte[] bloomfilterData = DeviceBloomfilterGenerator.hexStringToByteArray(
+                DeviceBloomfilterGenerator.BLOOM_FILTER_DEFAULT);
+        try {
+            mTestableMetricsLogger.setBloomfilter(
+                    BloomFilter.readFrom(
+                            new ByteArrayInputStream(bloomfilterData), Funnels.byteArrayFunnel()));
+        } catch (IOException e) {
+            Assert.assertTrue(false);
+        }
+    }
 }
diff --git a/android/app/tests/unit/src/com/android/bluetooth/btservice/RemoteDevicesTest.java b/android/app/tests/unit/src/com/android/bluetooth/btservice/RemoteDevicesTest.java
index 42e93e5..86875f2 100644
--- a/android/app/tests/unit/src/com/android/bluetooth/btservice/RemoteDevicesTest.java
+++ b/android/app/tests/unit/src/com/android/bluetooth/btservice/RemoteDevicesTest.java
@@ -10,6 +10,7 @@
 import android.bluetooth.BluetoothHeadset;
 import android.bluetooth.BluetoothHeadsetClient;
 import android.bluetooth.BluetoothProfile;
+import android.bluetooth.BluetoothSinkAudioPolicy;
 import android.content.Intent;
 import android.os.Bundle;
 import android.os.HandlerThread;
@@ -21,6 +22,7 @@
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.bluetooth.Utils;
+import com.android.bluetooth.btservice.RemoteDevices.DeviceProperties;
 import com.android.bluetooth.hfp.HeadsetHalConstants;
 
 import org.junit.After;
@@ -517,6 +519,26 @@
         Assert.assertNull(mRemoteDevices.getDeviceProperties(mDevice1));
     }
 
+    @Test
+    public void testSetgetHfAudioPolicyForRemoteAg() {
+        // Verify that device property is null initially
+        Assert.assertNull(mRemoteDevices.getDeviceProperties(mDevice1));
+
+        mRemoteDevices.addDeviceProperties(Utils.getBytesFromAddress(TEST_BT_ADDR_1));
+
+        DeviceProperties deviceProp = mRemoteDevices.getDeviceProperties(mDevice1);
+        BluetoothSinkAudioPolicy policies = new BluetoothSinkAudioPolicy.Builder()
+                .setCallEstablishPolicy(BluetoothSinkAudioPolicy.POLICY_ALLOWED)
+                .setActiveDevicePolicyAfterConnection(BluetoothSinkAudioPolicy.POLICY_ALLOWED)
+                .setInBandRingtonePolicy(BluetoothSinkAudioPolicy.POLICY_ALLOWED)
+                .build();
+        deviceProp.setHfAudioPolicyForRemoteAg(policies);
+
+        // Verify that the audio policy properties are set and get propperly
+        Assert.assertEquals(policies, mRemoteDevices.getDeviceProperties(mDevice1)
+                .getHfAudioPolicyForRemoteAg());
+    }
+
     private static void verifyBatteryLevelChangedIntent(BluetoothDevice device, int batteryLevel,
             ArgumentCaptor<Intent> intentArgument) {
         verifyBatteryLevelChangedIntent(device, batteryLevel, intentArgument.getValue());
diff --git a/android/app/tests/unit/src/com/android/bluetooth/btservice/storage/DatabaseManagerTest.java b/android/app/tests/unit/src/com/android/bluetooth/btservice/storage/DatabaseManagerTest.java
index 28357b6..cd306f4 100644
--- a/android/app/tests/unit/src/com/android/bluetooth/btservice/storage/DatabaseManagerTest.java
+++ b/android/app/tests/unit/src/com/android/bluetooth/btservice/storage/DatabaseManagerTest.java
@@ -30,6 +30,7 @@
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothDevice;
 import android.bluetooth.BluetoothProfile;
+import android.bluetooth.BluetoothSinkAudioPolicy;
 import android.content.ContentValues;
 import android.database.Cursor;
 import android.database.sqlite.SQLiteDatabase;
@@ -387,6 +388,10 @@
                 value, true);
         testSetGetCustomMetaCase(false, BluetoothDevice.METADATA_LE_AUDIO,
                 value, true);
+        testSetGetCustomMetaCase(false, BluetoothDevice.METADATA_GMCS_CCCD,
+                value, true);
+        testSetGetCustomMetaCase(false, BluetoothDevice.METADATA_GTBS_CCCD,
+                value, true);
         testSetGetCustomMetaCase(false, badKey, value, false);
 
         // Device is in database
@@ -447,6 +452,24 @@
                 value, true);
         testSetGetCustomMetaCase(true, BluetoothDevice.METADATA_LE_AUDIO,
                 value, true);
+        testSetGetCustomMetaCase(true, BluetoothDevice.METADATA_GMCS_CCCD,
+                value, true);
+        testSetGetCustomMetaCase(true, BluetoothDevice.METADATA_GTBS_CCCD,
+                value, true);
+    }
+    @Test
+    public void testSetGetAudioPolicyMetaData() {
+        int badKey = 100;
+        BluetoothSinkAudioPolicy value = new BluetoothSinkAudioPolicy.Builder()
+                .setCallEstablishPolicy(BluetoothSinkAudioPolicy.POLICY_ALLOWED)
+                .setActiveDevicePolicyAfterConnection(BluetoothSinkAudioPolicy.POLICY_NOT_ALLOWED)
+                .setInBandRingtonePolicy(BluetoothSinkAudioPolicy.POLICY_ALLOWED)
+                .build();
+
+        // Device is not in database
+        testSetGetAudioPolicyMetadataCase(false, value, true);
+        // Device is in database
+        testSetGetAudioPolicyMetadataCase(true, value, true);
     }
 
     @Test
@@ -1143,7 +1166,7 @@
     @Test
     public void testDatabaseMigration_111_112() throws IOException {
         String testString = "TEST STRING";
-        // Create a database with version 109
+        // Create a database with version 111
         SupportSQLiteDatabase db = testHelper.createDatabase(DB_NAME, 111);
         // insert a device to the database
         ContentValues device = new ContentValues();
@@ -1189,7 +1212,7 @@
 
     @Test
     public void testDatabaseMigration_113_114() throws IOException {
-        // Create a database with version 112
+        // Create a database with version 113
         SupportSQLiteDatabase db = testHelper.createDatabase(DB_NAME, 113);
         // insert a device to the database
         ContentValues device = new ContentValues();
@@ -1209,6 +1232,83 @@
         }
     }
 
+    @Test
+    public void testDatabaseMigration_114_115() throws IOException {
+        // Create a database with version 114
+        SupportSQLiteDatabase db = testHelper.createDatabase(DB_NAME, 114);
+        // 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 114 to 115
+        db.close();
+        db = testHelper.runMigrationsAndValidate(DB_NAME, 115, true,
+                MetadataDatabase.MIGRATION_114_115);
+        Cursor cursor = db.query("SELECT * FROM metadata");
+
+        assertHasColumn(cursor, "call_establish_audio_policy", true);
+        assertHasColumn(cursor, "connecting_time_audio_policy", true);
+        assertHasColumn(cursor, "in_band_ringtone_audio_policy", true);
+        while (cursor.moveToNext()) {
+            // Check the new columns was added with default value
+            assertColumnBlobData(cursor, "call_establish_audio_policy", null);
+            assertColumnBlobData(cursor, "connecting_time_audio_policy", null);
+            assertColumnBlobData(cursor, "in_band_ringtone_audio_policy", null);
+        }
+    }
+
+    @Test
+    public void testDatabaseMigration_115_116() throws IOException {
+        // Create a database with version 115
+        SupportSQLiteDatabase db = testHelper.createDatabase(DB_NAME, 115);
+        // 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 115 to 116
+        db.close();
+        db = testHelper.runMigrationsAndValidate(DB_NAME, 116, true,
+                MetadataDatabase.MIGRATION_115_116);
+        Cursor cursor = db.query("SELECT * FROM metadata");
+        assertHasColumn(cursor, "preferred_output_only_profile", true);
+        assertHasColumn(cursor, "preferred_duplex_profile", true);
+        while (cursor.moveToNext()) {
+            // Check the new columns was added with default value
+            assertColumnIntData(cursor, "preferred_output_only_profile", 0);
+            assertColumnIntData(cursor, "preferred_duplex_profile", 0);
+        }
+    }
+
+    @Test
+    public void testDatabaseMigration_116_117() throws IOException {
+        // Create a database with version 116
+        SupportSQLiteDatabase db = testHelper.createDatabase(DB_NAME, 116);
+        // 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 116 to 117
+        db.close();
+        db = testHelper.runMigrationsAndValidate(DB_NAME, 117, true,
+                MetadataDatabase.MIGRATION_116_117);
+        Cursor cursor = db.query("SELECT * FROM metadata");
+        assertHasColumn(cursor, "gmcs_cccd", true);
+        assertHasColumn(cursor, "gtbs_cccd", true);
+        while (cursor.moveToNext()) {
+            // Check the new columns was added with default value
+            assertColumnBlobData(cursor, "gmcs_cccd", null);
+            assertColumnBlobData(cursor, "gtbs_cccd", null);
+        }
+    }
+
     /**
      * Helper function to check whether the database has the expected column
      */
@@ -1379,4 +1479,38 @@
         // Wait for clear database
         TestUtils.waitForLooperToFinishScheduledTask(mDatabaseManager.getHandlerLooper());
     }
+
+    void testSetGetAudioPolicyMetadataCase(boolean stored,
+                BluetoothSinkAudioPolicy policy, boolean expectedResult) {
+        BluetoothSinkAudioPolicy testPolicy = new BluetoothSinkAudioPolicy.Builder().build();
+        if (stored) {
+            Metadata data = new Metadata(TEST_BT_ADDR);
+            mDatabaseManager.mMetadataCache.put(TEST_BT_ADDR, data);
+            mDatabase.insert(data);
+            Assert.assertEquals(expectedResult,
+                    mDatabaseManager.setAudioPolicyMetadata(mTestDevice, testPolicy));
+        }
+        Assert.assertEquals(expectedResult,
+                mDatabaseManager.setAudioPolicyMetadata(mTestDevice, policy));
+        if (expectedResult) {
+            // Check for callback and get value
+            Assert.assertEquals(policy,
+                    mDatabaseManager.getAudioPolicyMetadata(mTestDevice));
+        } else {
+            Assert.assertNull(mDatabaseManager.getAudioPolicyMetadata(mTestDevice));
+            return;
+        }
+        // Wait for database update
+        TestUtils.waitForLooperToFinishScheduledTask(mDatabaseManager.getHandlerLooper());
+
+        // Check whether the value is saved in database
+        restartDatabaseManagerHelper();
+        Assert.assertEquals(policy,
+                mDatabaseManager.getAudioPolicyMetadata(mTestDevice));
+
+        mDatabaseManager.factoryReset();
+        mDatabaseManager.mMetadataCache.clear();
+        // Wait for clear database
+        TestUtils.waitForLooperToFinishScheduledTask(mDatabaseManager.getHandlerLooper());
+    }
 }
diff --git a/android/app/tests/unit/src/com/android/bluetooth/btservice/storage/schemas/com.android.bluetooth.btservice.storage.MetadataDatabase/115.json b/android/app/tests/unit/src/com/android/bluetooth/btservice/storage/schemas/com.android.bluetooth.btservice.storage.MetadataDatabase/115.json
new file mode 100644
index 0000000..5d576dc
--- /dev/null
+++ b/android/app/tests/unit/src/com/android/bluetooth/btservice/storage/schemas/com.android.bluetooth.btservice.storage.MetadataDatabase/115.json
@@ -0,0 +1,358 @@
+{
+  "formatVersion": 1,
+  "database": {
+    "version": 115,
+    "identityHash": "c61976c8f6248cefd19ef8f25f543e01",
+    "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, `hap_client_connection_policy` INTEGER, `map_client_connection_policy` INTEGER, `le_audio_connection_policy` INTEGER, `volume_control_connection_policy` INTEGER, `csip_set_coordinator_connection_policy` INTEGER, `le_call_control_connection_policy` INTEGER, `bass_client_connection_policy` INTEGER, `battery_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, `spatial_audio` BLOB, `fastpair_customized` BLOB, `le_audio` BLOB, `call_establish_audio_policy` INTEGER, `connecting_time_audio_policy` INTEGER, `in_band_ringtone_audio_policy` INTEGER, 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.hap_client_connection_policy",
+            "columnName": "hap_client_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": "profileConnectionPolicies.volume_control_connection_policy",
+            "columnName": "volume_control_connection_policy",
+            "affinity": "INTEGER",
+            "notNull": false
+          },
+          {
+            "fieldPath": "profileConnectionPolicies.csip_set_coordinator_connection_policy",
+            "columnName": "csip_set_coordinator_connection_policy",
+            "affinity": "INTEGER",
+            "notNull": false
+          },
+          {
+            "fieldPath": "profileConnectionPolicies.le_call_control_connection_policy",
+            "columnName": "le_call_control_connection_policy",
+            "affinity": "INTEGER",
+            "notNull": false
+          },
+          {
+            "fieldPath": "profileConnectionPolicies.bass_client_connection_policy",
+            "columnName": "bass_client_connection_policy",
+            "affinity": "INTEGER",
+            "notNull": false
+          },
+          {
+            "fieldPath": "profileConnectionPolicies.battery_connection_policy",
+            "columnName": "battery_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
+          },
+          {
+            "fieldPath": "publicMetadata.spatial_audio",
+            "columnName": "spatial_audio",
+            "affinity": "BLOB",
+            "notNull": false
+          },
+          {
+            "fieldPath": "publicMetadata.fastpair_customized",
+            "columnName": "fastpair_customized",
+            "affinity": "BLOB",
+            "notNull": false
+          },
+          {
+            "fieldPath": "publicMetadata.le_audio",
+            "columnName": "le_audio",
+            "affinity": "BLOB",
+            "notNull": false
+          },
+          {
+            "fieldPath": "audioPolicyMetadata.callEstablishAudioPolicy",
+            "columnName": "call_establish_audio_policy",
+            "affinity": "INTEGER",
+            "notNull": false
+          },
+          {
+            "fieldPath": "audioPolicyMetadata.connectingTimeAudioPolicy",
+            "columnName": "connecting_time_audio_policy",
+            "affinity": "INTEGER",
+            "notNull": false
+          },
+          {
+            "fieldPath": "audioPolicyMetadata.inBandRingtoneAudioPolicy",
+            "columnName": "in_band_ringtone_audio_policy",
+            "affinity": "INTEGER",
+            "notNull": false
+          }
+        ],
+        "primaryKey": {
+          "autoGenerate": false,
+          "columnNames": [
+            "address"
+          ]
+        },
+        "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, 'c61976c8f6248cefd19ef8f25f543e01')"
+    ]
+  }
+}
diff --git a/android/app/tests/unit/src/com/android/bluetooth/btservice/storage/schemas/com.android.bluetooth.btservice.storage.MetadataDatabase/116.json b/android/app/tests/unit/src/com/android/bluetooth/btservice/storage/schemas/com.android.bluetooth.btservice.storage.MetadataDatabase/116.json
new file mode 100644
index 0000000..2e85b51
--- /dev/null
+++ b/android/app/tests/unit/src/com/android/bluetooth/btservice/storage/schemas/com.android.bluetooth.btservice.storage.MetadataDatabase/116.json
@@ -0,0 +1,370 @@
+{
+  "formatVersion": 1,
+  "database": {
+    "version": 116,
+    "identityHash": "0b8549de3acad8b14fe6f7198206ea02",
+    "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, `preferred_output_only_profile` INTEGER NOT NULL, `preferred_duplex_profile` 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, `hap_client_connection_policy` INTEGER, `map_client_connection_policy` INTEGER, `le_audio_connection_policy` INTEGER, `volume_control_connection_policy` INTEGER, `csip_set_coordinator_connection_policy` INTEGER, `le_call_control_connection_policy` INTEGER, `bass_client_connection_policy` INTEGER, `battery_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, `spatial_audio` BLOB, `fastpair_customized` BLOB, `le_audio` BLOB, `call_establish_audio_policy` INTEGER, `connecting_time_audio_policy` INTEGER, `in_band_ringtone_audio_policy` INTEGER, 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": "preferred_output_only_profile",
+            "columnName": "preferred_output_only_profile",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "preferred_duplex_profile",
+            "columnName": "preferred_duplex_profile",
+            "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.hap_client_connection_policy",
+            "columnName": "hap_client_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": "profileConnectionPolicies.volume_control_connection_policy",
+            "columnName": "volume_control_connection_policy",
+            "affinity": "INTEGER",
+            "notNull": false
+          },
+          {
+            "fieldPath": "profileConnectionPolicies.csip_set_coordinator_connection_policy",
+            "columnName": "csip_set_coordinator_connection_policy",
+            "affinity": "INTEGER",
+            "notNull": false
+          },
+          {
+            "fieldPath": "profileConnectionPolicies.le_call_control_connection_policy",
+            "columnName": "le_call_control_connection_policy",
+            "affinity": "INTEGER",
+            "notNull": false
+          },
+          {
+            "fieldPath": "profileConnectionPolicies.bass_client_connection_policy",
+            "columnName": "bass_client_connection_policy",
+            "affinity": "INTEGER",
+            "notNull": false
+          },
+          {
+            "fieldPath": "profileConnectionPolicies.battery_connection_policy",
+            "columnName": "battery_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
+          },
+          {
+            "fieldPath": "publicMetadata.spatial_audio",
+            "columnName": "spatial_audio",
+            "affinity": "BLOB",
+            "notNull": false
+          },
+          {
+            "fieldPath": "publicMetadata.fastpair_customized",
+            "columnName": "fastpair_customized",
+            "affinity": "BLOB",
+            "notNull": false
+          },
+          {
+            "fieldPath": "publicMetadata.le_audio",
+            "columnName": "le_audio",
+            "affinity": "BLOB",
+            "notNull": false
+          },
+          {
+            "fieldPath": "audioPolicyMetadata.callEstablishAudioPolicy",
+            "columnName": "call_establish_audio_policy",
+            "affinity": "INTEGER",
+            "notNull": false
+          },
+          {
+            "fieldPath": "audioPolicyMetadata.connectingTimeAudioPolicy",
+            "columnName": "connecting_time_audio_policy",
+            "affinity": "INTEGER",
+            "notNull": false
+          },
+          {
+            "fieldPath": "audioPolicyMetadata.inBandRingtoneAudioPolicy",
+            "columnName": "in_band_ringtone_audio_policy",
+            "affinity": "INTEGER",
+            "notNull": false
+          }
+        ],
+        "primaryKey": {
+          "autoGenerate": false,
+          "columnNames": [
+            "address"
+          ]
+        },
+        "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, '0b8549de3acad8b14fe6f7198206ea02')"
+    ]
+  }
+}
\ No newline at end of file
diff --git a/android/app/tests/unit/src/com/android/bluetooth/btservice/storage/schemas/com.android.bluetooth.btservice.storage.MetadataDatabase/117.json b/android/app/tests/unit/src/com/android/bluetooth/btservice/storage/schemas/com.android.bluetooth.btservice.storage.MetadataDatabase/117.json
new file mode 100644
index 0000000..d4c1f7e
--- /dev/null
+++ b/android/app/tests/unit/src/com/android/bluetooth/btservice/storage/schemas/com.android.bluetooth.btservice.storage.MetadataDatabase/117.json
@@ -0,0 +1,382 @@
+{
+  "formatVersion": 1,
+  "database": {
+    "version": 117,
+    "identityHash": "b3363a857e6d4f3ece8ba92d57d52c26",
+    "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, `preferred_output_only_profile` INTEGER NOT NULL, `preferred_duplex_profile` 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, `hap_client_connection_policy` INTEGER, `map_client_connection_policy` INTEGER, `le_audio_connection_policy` INTEGER, `volume_control_connection_policy` INTEGER, `csip_set_coordinator_connection_policy` INTEGER, `le_call_control_connection_policy` INTEGER, `bass_client_connection_policy` INTEGER, `battery_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, `spatial_audio` BLOB, `fastpair_customized` BLOB, `le_audio` BLOB, `gmcs_cccd` BLOB, `gtbs_cccd` BLOB, `call_establish_audio_policy` INTEGER, `connecting_time_audio_policy` INTEGER, `in_band_ringtone_audio_policy` INTEGER, 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": "preferred_output_only_profile",
+            "columnName": "preferred_output_only_profile",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "preferred_duplex_profile",
+            "columnName": "preferred_duplex_profile",
+            "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.hap_client_connection_policy",
+            "columnName": "hap_client_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": "profileConnectionPolicies.volume_control_connection_policy",
+            "columnName": "volume_control_connection_policy",
+            "affinity": "INTEGER",
+            "notNull": false
+          },
+          {
+            "fieldPath": "profileConnectionPolicies.csip_set_coordinator_connection_policy",
+            "columnName": "csip_set_coordinator_connection_policy",
+            "affinity": "INTEGER",
+            "notNull": false
+          },
+          {
+            "fieldPath": "profileConnectionPolicies.le_call_control_connection_policy",
+            "columnName": "le_call_control_connection_policy",
+            "affinity": "INTEGER",
+            "notNull": false
+          },
+          {
+            "fieldPath": "profileConnectionPolicies.bass_client_connection_policy",
+            "columnName": "bass_client_connection_policy",
+            "affinity": "INTEGER",
+            "notNull": false
+          },
+          {
+            "fieldPath": "profileConnectionPolicies.battery_connection_policy",
+            "columnName": "battery_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
+          },
+          {
+            "fieldPath": "publicMetadata.spatial_audio",
+            "columnName": "spatial_audio",
+            "affinity": "BLOB",
+            "notNull": false
+          },
+          {
+            "fieldPath": "publicMetadata.fastpair_customized",
+            "columnName": "fastpair_customized",
+            "affinity": "BLOB",
+            "notNull": false
+          },
+          {
+            "fieldPath": "publicMetadata.le_audio",
+            "columnName": "le_audio",
+            "affinity": "BLOB",
+            "notNull": false
+          },
+          {
+            "fieldPath": "publicMetadata.gmcs_cccd",
+            "columnName": "gmcs_cccd",
+            "affinity": "BLOB",
+            "notNull": false
+          },
+          {
+            "fieldPath": "publicMetadata.gtbs_cccd",
+            "columnName": "gtbs_cccd",
+            "affinity": "BLOB",
+            "notNull": false
+          },
+          {
+            "fieldPath": "audioPolicyMetadata.callEstablishAudioPolicy",
+            "columnName": "call_establish_audio_policy",
+            "affinity": "INTEGER",
+            "notNull": false
+          },
+          {
+            "fieldPath": "audioPolicyMetadata.connectingTimeAudioPolicy",
+            "columnName": "connecting_time_audio_policy",
+            "affinity": "INTEGER",
+            "notNull": false
+          },
+          {
+            "fieldPath": "audioPolicyMetadata.inBandRingtoneAudioPolicy",
+            "columnName": "in_band_ringtone_audio_policy",
+            "affinity": "INTEGER",
+            "notNull": false
+          }
+        ],
+        "primaryKey": {
+          "autoGenerate": false,
+          "columnNames": [
+            "address"
+          ]
+        },
+        "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, 'b3363a857e6d4f3ece8ba92d57d52c26')"
+    ]
+  }
+}
\ No newline at end of file
diff --git a/android/app/tests/unit/src/com/android/bluetooth/csip/CsipSetCoordinatorStateMachineTest.java b/android/app/tests/unit/src/com/android/bluetooth/csip/CsipSetCoordinatorStateMachineTest.java
index b2d99f3..8ae5f93 100644
--- a/android/app/tests/unit/src/com/android/bluetooth/csip/CsipSetCoordinatorStateMachineTest.java
+++ b/android/app/tests/unit/src/com/android/bluetooth/csip/CsipSetCoordinatorStateMachineTest.java
@@ -17,6 +17,11 @@
 
 package com.android.bluetooth.csip;
 
+import static android.bluetooth.BluetoothProfile.STATE_CONNECTED;
+import static android.bluetooth.BluetoothProfile.STATE_CONNECTING;
+import static android.bluetooth.BluetoothProfile.STATE_DISCONNECTED;
+import static android.bluetooth.BluetoothProfile.STATE_DISCONNECTING;
+
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.ArgumentMatchers.eq;
@@ -25,9 +30,10 @@
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothDevice;
 import android.bluetooth.BluetoothProfile;
-import android.content.Context;
 import android.content.Intent;
 import android.os.HandlerThread;
+import android.os.Looper;
+import android.os.Message;
 import android.test.suitebuilder.annotation.MediumTest;
 
 import androidx.test.InstrumentationRegistry;
@@ -42,6 +48,7 @@
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
 import org.mockito.Mock;
+import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
 
 @MediumTest
@@ -49,11 +56,10 @@
 public class CsipSetCoordinatorStateMachineTest {
     private final String mFlagDexmarker = System.getProperty("dexmaker.share_classloader", "false");
 
-    private Context mTargetContext;
     private BluetoothAdapter mAdapter;
     private BluetoothDevice mTestDevice;
     private HandlerThread mHandlerThread;
-    private CsipSetCoordinatorStateMachine mStateMachine;
+    private CsipSetCoordinatorStateMachineWrapper mStateMachine;
     private static final int TIMEOUT_MS = 1000;
 
     @Mock private AdapterService mAdapterService;
@@ -66,7 +72,6 @@
             System.setProperty("dexmaker.share_classloader", "true");
         }
 
-        mTargetContext = InstrumentationRegistry.getTargetContext();
         // Set up mocks and test assets
         MockitoAnnotations.initMocks(this);
         TestUtils.setAdapterService(mAdapterService);
@@ -79,8 +84,8 @@
         // Set up thread and looper
         mHandlerThread = new HandlerThread("CsipSetCoordinatorServiceTestHandlerThread");
         mHandlerThread.start();
-        mStateMachine = new CsipSetCoordinatorStateMachine(
-                mTestDevice, mService, mNativeInterface, mHandlerThread.getLooper());
+        mStateMachine = spy(new CsipSetCoordinatorStateMachineWrapper(
+                mTestDevice, mService, mNativeInterface, mHandlerThread.getLooper()));
 
         // Override the timeout value to speed up the test
         CsipSetCoordinatorStateMachine.sConnectTimeoutMs = 1000;
@@ -103,7 +108,7 @@
     @Test
     public void testDefaultDisconnectedState() {
         Assert.assertEquals(
-                BluetoothProfile.STATE_DISCONNECTED, mStateMachine.getConnectionState());
+                STATE_DISCONNECTED, mStateMachine.getConnectionState());
     }
 
     /**
@@ -155,7 +160,7 @@
         ArgumentCaptor<Intent> intentArgument1 = ArgumentCaptor.forClass(Intent.class);
         verify(mService, timeout(TIMEOUT_MS).times(1))
                 .sendBroadcast(intentArgument1.capture(), anyString());
-        Assert.assertEquals(BluetoothProfile.STATE_CONNECTING,
+        Assert.assertEquals(STATE_CONNECTING,
                 intentArgument1.getValue().getIntExtra(BluetoothProfile.EXTRA_STATE, -1));
 
         // Check that we are in Connecting state
@@ -196,7 +201,7 @@
         ArgumentCaptor<Intent> intentArgument1 = ArgumentCaptor.forClass(Intent.class);
         verify(mService, timeout(TIMEOUT_MS).times(1))
                 .sendBroadcast(intentArgument1.capture(), anyString());
-        Assert.assertEquals(BluetoothProfile.STATE_CONNECTING,
+        Assert.assertEquals(STATE_CONNECTING,
                 intentArgument1.getValue().getIntExtra(BluetoothProfile.EXTRA_STATE, -1));
 
         // Check that we are in Connecting state
@@ -207,7 +212,7 @@
         ArgumentCaptor<Intent> intentArgument2 = ArgumentCaptor.forClass(Intent.class);
         verify(mService, timeout(CsipSetCoordinatorStateMachine.sConnectTimeoutMs * 2).times(2))
                 .sendBroadcast(intentArgument2.capture(), anyString());
-        Assert.assertEquals(BluetoothProfile.STATE_DISCONNECTED,
+        Assert.assertEquals(STATE_DISCONNECTED,
                 intentArgument2.getValue().getIntExtra(BluetoothProfile.EXTRA_STATE, -1));
 
         // Check that we are in Disconnected state
@@ -236,7 +241,7 @@
         ArgumentCaptor<Intent> intentArgument1 = ArgumentCaptor.forClass(Intent.class);
         verify(mService, timeout(TIMEOUT_MS).times(1))
                 .sendBroadcast(intentArgument1.capture(), anyString());
-        Assert.assertEquals(BluetoothProfile.STATE_CONNECTING,
+        Assert.assertEquals(STATE_CONNECTING,
                 intentArgument1.getValue().getIntExtra(BluetoothProfile.EXTRA_STATE, -1));
 
         // Check that we are in Connecting state
@@ -247,7 +252,7 @@
         ArgumentCaptor<Intent> intentArgument2 = ArgumentCaptor.forClass(Intent.class);
         verify(mService, timeout(CsipSetCoordinatorStateMachine.sConnectTimeoutMs * 2).times(2))
                 .sendBroadcast(intentArgument2.capture(), anyString());
-        Assert.assertEquals(BluetoothProfile.STATE_DISCONNECTED,
+        Assert.assertEquals(STATE_DISCONNECTED,
                 intentArgument2.getValue().getIntExtra(BluetoothProfile.EXTRA_STATE, -1));
 
         // Check that we are in Disconnected state
@@ -255,4 +260,427 @@
                 IsInstanceOf.instanceOf(CsipSetCoordinatorStateMachine.Disconnected.class));
         verify(mNativeInterface).disconnect(eq(mTestDevice));
     }
+
+    @Test
+    public void testGetDevice() {
+        Assert.assertEquals(mTestDevice, mStateMachine.getDevice());
+    }
+
+    @Test
+    public void testIsConnected() {
+        Assert.assertFalse(mStateMachine.isConnected());
+
+        initToConnectedState();
+        Assert.assertTrue(mStateMachine.isConnected());
+    }
+
+    @Test
+    public void testDumpDoesNotCrash() {
+        mStateMachine.dump(new StringBuilder());
+    }
+
+    @Test
+    public void testProcessDisconnectMessage_onDisconnectedState() {
+        mStateMachine.sendMessage(CsipSetCoordinatorStateMachine.DISCONNECT);
+        TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper());
+        Assert.assertEquals(STATE_DISCONNECTED, mStateMachine.getConnectionState());
+    }
+
+    @Test
+    public void testProcessConnectMessage_onDisconnectedState() {
+        allowConnection(false);
+        mStateMachine.sendMessage(CsipSetCoordinatorStateMachine.CONNECT);
+        TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper());
+        Assert.assertEquals(STATE_DISCONNECTED, mStateMachine.getConnectionState());
+
+        allowConnection(false);
+        mStateMachine.sendMessage(CsipSetCoordinatorStateMachine.CONNECT);
+        TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper());
+        Assert.assertEquals(STATE_DISCONNECTED, mStateMachine.getConnectionState());
+
+        allowConnection(true);
+        doReturn(true).when(mNativeInterface).connect(any(BluetoothDevice.class));
+        sendMessageAndVerifyTransition(
+                mStateMachine.obtainMessage(CsipSetCoordinatorStateMachine.CONNECT),
+                CsipSetCoordinatorStateMachine.Connecting.class);
+    }
+
+    @Test
+    public void testStackEvent_withoutStateChange_onDisconnectedState() {
+        allowConnection(false);
+
+        CsipSetCoordinatorStackEvent event = new CsipSetCoordinatorStackEvent(-1);
+        mStateMachine.sendMessage(CsipSetCoordinatorStateMachine.STACK_EVENT, event);
+        TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper());
+        Assert.assertEquals(STATE_DISCONNECTED, mStateMachine.getConnectionState());
+
+        event = new CsipSetCoordinatorStackEvent(
+                CsipSetCoordinatorStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED);
+        event.valueInt1 = CsipSetCoordinatorStackEvent.CONNECTION_STATE_DISCONNECTED;
+        mStateMachine.sendMessage(CsipSetCoordinatorStateMachine.STACK_EVENT, event);
+        TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper());
+        Assert.assertEquals(STATE_DISCONNECTED, mStateMachine.getConnectionState());
+
+        event = new CsipSetCoordinatorStackEvent(
+                CsipSetCoordinatorStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED);
+        event.valueInt1 = CsipSetCoordinatorStackEvent.CONNECTION_STATE_CONNECTING;
+        mStateMachine.sendMessage(CsipSetCoordinatorStateMachine.STACK_EVENT, event);
+        TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper());
+        Assert.assertEquals(STATE_DISCONNECTED, mStateMachine.getConnectionState());
+        verify(mNativeInterface).disconnect(mTestDevice);
+
+        Mockito.clearInvocations(mNativeInterface);
+        event = new CsipSetCoordinatorStackEvent(
+                CsipSetCoordinatorStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED);
+        event.valueInt1 = CsipSetCoordinatorStackEvent.CONNECTION_STATE_CONNECTED;
+        mStateMachine.sendMessage(CsipSetCoordinatorStateMachine.STACK_EVENT, event);
+        TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper());
+        Assert.assertEquals(STATE_DISCONNECTED, mStateMachine.getConnectionState());
+        verify(mNativeInterface).disconnect(mTestDevice);
+
+        event = new CsipSetCoordinatorStackEvent(
+                CsipSetCoordinatorStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED);
+        event.valueInt1 = CsipSetCoordinatorStackEvent.CONNECTION_STATE_DISCONNECTING;
+        mStateMachine.sendMessage(CsipSetCoordinatorStateMachine.STACK_EVENT, event);
+        TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper());
+        Assert.assertEquals(STATE_DISCONNECTED, mStateMachine.getConnectionState());
+
+        event = new CsipSetCoordinatorStackEvent(
+                CsipSetCoordinatorStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED);
+        event.valueInt1 = -1;
+        mStateMachine.sendMessage(CsipSetCoordinatorStateMachine.STACK_EVENT, event);
+        TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper());
+        Assert.assertEquals(STATE_DISCONNECTED, mStateMachine.getConnectionState());
+    }
+
+    @Test
+    public void testStackEvent_toConnectingState_onDisconnectedState() {
+        allowConnection(true);
+        CsipSetCoordinatorStackEvent event = new CsipSetCoordinatorStackEvent(
+                CsipSetCoordinatorStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED);
+        event.valueInt1 = CsipSetCoordinatorStackEvent.CONNECTION_STATE_CONNECTING;
+        sendMessageAndVerifyTransition(
+                mStateMachine.obtainMessage(CsipSetCoordinatorStateMachine.STACK_EVENT, event),
+                CsipSetCoordinatorStateMachine.Connecting.class);
+    }
+
+    @Test
+    public void testStackEvent_toConnectedState_onDisconnectedState() {
+        allowConnection(true);
+        CsipSetCoordinatorStackEvent event = new CsipSetCoordinatorStackEvent(
+                CsipSetCoordinatorStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED);
+        event.valueInt1 = CsipSetCoordinatorStackEvent.CONNECTION_STATE_CONNECTED;
+        sendMessageAndVerifyTransition(
+                mStateMachine.obtainMessage(CsipSetCoordinatorStateMachine.STACK_EVENT, event),
+                CsipSetCoordinatorStateMachine.Connected.class);
+    }
+
+    @Test
+    public void testProcessConnectMessage_onConnectingState() {
+        initToConnectingState();
+        mStateMachine.sendMessage(CsipSetCoordinatorStateMachine.CONNECT);
+        TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper());
+        Assert.assertTrue(mStateMachine.doesSuperHaveDeferredMessages(
+                CsipSetCoordinatorStateMachine.CONNECT));
+    }
+
+    @Test
+    public void testProcessConnectTimeoutMessage_onConnectingState() {
+        initToConnectingState();
+        Message msg = mStateMachine.obtainMessage(CsipSetCoordinatorStateMachine.CONNECT_TIMEOUT);
+        sendMessageAndVerifyTransition(msg, CsipSetCoordinatorStateMachine.Disconnected.class);
+    }
+
+    @Test
+    public void testProcessDisconnectMessage_onConnectingState() {
+        initToConnectingState();
+        Message msg = mStateMachine.obtainMessage(CsipSetCoordinatorStateMachine.DISCONNECT);
+        sendMessageAndVerifyTransition(msg, CsipSetCoordinatorStateMachine.Disconnected.class);
+    }
+
+    @Test
+    public void testStackEvent_withoutStateChange_onConnectingState() {
+        initToConnectingState();
+        CsipSetCoordinatorStackEvent event = new CsipSetCoordinatorStackEvent(-1);
+        mStateMachine.sendMessage(CsipSetCoordinatorStateMachine.STACK_EVENT, event);
+        TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper());
+        Assert.assertEquals(STATE_CONNECTING, mStateMachine.getConnectionState());
+
+        event = new CsipSetCoordinatorStackEvent(
+                CsipSetCoordinatorStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED);
+        event.valueInt1 = CsipSetCoordinatorStackEvent.CONNECTION_STATE_CONNECTING;
+        mStateMachine.sendMessage(CsipSetCoordinatorStateMachine.STACK_EVENT, event);
+        TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper());
+        Assert.assertEquals(STATE_CONNECTING, mStateMachine.getConnectionState());
+
+        event = new CsipSetCoordinatorStackEvent(
+                CsipSetCoordinatorStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED);
+        event.valueInt1 = 10000;
+        mStateMachine.sendMessage(CsipSetCoordinatorStateMachine.STACK_EVENT, event);
+        TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper());
+        Assert.assertEquals(STATE_CONNECTING, mStateMachine.getConnectionState());
+    }
+
+    @Test
+    public void testStackEvent_toDisconnectedState_onConnectingState() {
+        initToConnectingState();
+        CsipSetCoordinatorStackEvent event = new CsipSetCoordinatorStackEvent(
+                CsipSetCoordinatorStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED);
+        event.valueInt1 = CsipSetCoordinatorStackEvent.CONNECTION_STATE_DISCONNECTED;
+        sendMessageAndVerifyTransition(
+                mStateMachine.obtainMessage(CsipSetCoordinatorStateMachine.STACK_EVENT, event),
+                CsipSetCoordinatorStateMachine.Disconnected.class);
+    }
+
+    @Test
+    public void testStackEvent_toConnectedState_onConnectingState() {
+        initToConnectingState();
+        CsipSetCoordinatorStackEvent event = new CsipSetCoordinatorStackEvent(
+                CsipSetCoordinatorStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED);
+        event.valueInt1 = CsipSetCoordinatorStackEvent.CONNECTION_STATE_CONNECTED;
+        sendMessageAndVerifyTransition(
+                mStateMachine.obtainMessage(CsipSetCoordinatorStateMachine.STACK_EVENT, event),
+                CsipSetCoordinatorStateMachine.Connected.class);
+    }
+
+    @Test
+    public void testStackEvent_toDisconnectingState_onConnectingState() {
+        initToConnectingState();
+        CsipSetCoordinatorStackEvent event = new CsipSetCoordinatorStackEvent(
+                CsipSetCoordinatorStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED);
+        event.valueInt1 = CsipSetCoordinatorStackEvent.CONNECTION_STATE_DISCONNECTING;
+        sendMessageAndVerifyTransition(
+                mStateMachine.obtainMessage(CsipSetCoordinatorStateMachine.STACK_EVENT, event),
+                CsipSetCoordinatorStateMachine.Disconnecting.class);
+    }
+
+    @Test
+    public void testProcessConnectMessage_onConnectedState() {
+        initToConnectedState();
+        mStateMachine.sendMessage(CsipSetCoordinatorStateMachine.CONNECT);
+        TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper());
+        Assert.assertEquals(STATE_CONNECTED, mStateMachine.getConnectionState());
+    }
+
+    @Test
+    public void testProcessDisconnectMessage_onConnectedState() {
+        initToConnectedState();
+        doReturn(true).when(mNativeInterface).disconnect(any(BluetoothDevice.class));
+        sendMessageAndVerifyTransition(
+                mStateMachine.obtainMessage(CsipSetCoordinatorStateMachine.DISCONNECT),
+                CsipSetCoordinatorStateMachine.Disconnecting.class);
+    }
+
+    @Test
+    public void testProcessDisconnectMessage_onConnectedState_withNativeError() {
+        initToConnectedState();
+        doReturn(false).when(mNativeInterface).disconnect(any(BluetoothDevice.class));
+        sendMessageAndVerifyTransition(
+                mStateMachine.obtainMessage(CsipSetCoordinatorStateMachine.DISCONNECT),
+                CsipSetCoordinatorStateMachine.Disconnected.class);
+    }
+
+    @Test
+    public void testStackEvent_withoutStateChange_onConnectedState() {
+        initToConnectedState();
+        CsipSetCoordinatorStackEvent event = new CsipSetCoordinatorStackEvent(-1);
+        mStateMachine.sendMessage(CsipSetCoordinatorStateMachine.STACK_EVENT, event);
+        TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper());
+        Assert.assertEquals(STATE_CONNECTED, mStateMachine.getConnectionState());
+
+        event = new CsipSetCoordinatorStackEvent(
+                CsipSetCoordinatorStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED);
+        event.valueInt1 = CsipSetCoordinatorStackEvent.CONNECTION_STATE_CONNECTING;
+        mStateMachine.sendMessage(CsipSetCoordinatorStateMachine.STACK_EVENT, event);
+        TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper());
+        Assert.assertEquals(STATE_CONNECTED, mStateMachine.getConnectionState());
+    }
+
+    @Test
+    public void testStackEvent_toDisconnectedState_onConnectedState() {
+        initToConnectedState();
+        CsipSetCoordinatorStackEvent event = new CsipSetCoordinatorStackEvent(
+                CsipSetCoordinatorStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED);
+        event.valueInt1 = CsipSetCoordinatorStackEvent.CONNECTION_STATE_DISCONNECTED;
+        sendMessageAndVerifyTransition(
+                mStateMachine.obtainMessage(CsipSetCoordinatorStateMachine.STACK_EVENT, event),
+                CsipSetCoordinatorStateMachine.Disconnected.class);
+    }
+
+    @Test
+    public void testStackEvent_toDisconnectingState_onConnectedState() {
+        initToConnectedState();
+        CsipSetCoordinatorStackEvent event = new CsipSetCoordinatorStackEvent(
+                CsipSetCoordinatorStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED);
+        event.valueInt1 = CsipSetCoordinatorStackEvent.CONNECTION_STATE_DISCONNECTING;
+        sendMessageAndVerifyTransition(
+                mStateMachine.obtainMessage(CsipSetCoordinatorStateMachine.STACK_EVENT, event),
+                CsipSetCoordinatorStateMachine.Disconnecting.class);
+    }
+
+    @Test
+    public void testProcessConnectMessage_onDisconnectingState() {
+        initToDisconnectingState();
+        Message msg = mStateMachine.obtainMessage(CsipSetCoordinatorStateMachine.CONNECT);
+        mStateMachine.sendMessage(msg);
+        TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper());
+        verify(mStateMachine).deferMessage(msg);
+    }
+
+    @Test
+    public void testProcessConnectTimeoutMessage_onDisconnectingState() {
+        initToConnectingState();
+        Message msg = mStateMachine.obtainMessage(CsipSetCoordinatorStateMachine.CONNECT_TIMEOUT);
+        sendMessageAndVerifyTransition(msg, CsipSetCoordinatorStateMachine.Disconnected.class);
+    }
+
+    @Test
+    public void testProcessDisconnectMessage_onDisconnectingState() {
+        initToDisconnectingState();
+        Message msg = mStateMachine.obtainMessage(CsipSetCoordinatorStateMachine.DISCONNECT);
+        mStateMachine.sendMessage(msg);
+        TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper());
+        verify(mStateMachine).deferMessage(msg);
+    }
+
+    @Test
+    public void testStackEvent_withoutStateChange_onDisconnectingState() {
+        initToDisconnectingState();
+        CsipSetCoordinatorStackEvent event = new CsipSetCoordinatorStackEvent(-1);
+        mStateMachine.sendMessage(CsipSetCoordinatorStateMachine.STACK_EVENT, event);
+        TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper());
+        Assert.assertEquals(STATE_DISCONNECTING, mStateMachine.getConnectionState());
+
+        allowConnection(false);
+        event = new CsipSetCoordinatorStackEvent(
+                CsipSetCoordinatorStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED);
+        event.valueInt1 = CsipSetCoordinatorStackEvent.CONNECTION_STATE_CONNECTED;
+        mStateMachine.sendMessage(CsipSetCoordinatorStateMachine.STACK_EVENT, event);
+        TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper());
+        verify(mNativeInterface).disconnect(any());
+
+        Mockito.clearInvocations(mNativeInterface);
+        event = new CsipSetCoordinatorStackEvent(
+                CsipSetCoordinatorStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED);
+        event.valueInt1 = CsipSetCoordinatorStackEvent.CONNECTION_STATE_CONNECTING;
+        mStateMachine.sendMessage(CsipSetCoordinatorStateMachine.STACK_EVENT, event);
+        TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper());
+        verify(mNativeInterface).disconnect(any());
+
+        event = new CsipSetCoordinatorStackEvent(
+                CsipSetCoordinatorStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED);
+        event.valueInt1 = CsipSetCoordinatorStackEvent.CONNECTION_STATE_DISCONNECTING;
+        mStateMachine.sendMessage(CsipSetCoordinatorStateMachine.STACK_EVENT, event);
+        TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper());
+        Assert.assertEquals(STATE_DISCONNECTING, mStateMachine.getConnectionState());
+
+        event = new CsipSetCoordinatorStackEvent(
+                CsipSetCoordinatorStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED);
+        event.valueInt1 = 10000;
+        mStateMachine.sendMessage(CsipSetCoordinatorStateMachine.STACK_EVENT, event);
+        TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper());
+        Assert.assertEquals(STATE_DISCONNECTING, mStateMachine.getConnectionState());
+    }
+
+    @Test
+    public void testStackEvent_toConnectedState_onDisconnectingState() {
+        initToDisconnectingState();
+        allowConnection(true);
+        CsipSetCoordinatorStackEvent event = new CsipSetCoordinatorStackEvent(
+                CsipSetCoordinatorStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED);
+        event.valueInt1 = CsipSetCoordinatorStackEvent.CONNECTION_STATE_CONNECTED;
+        sendMessageAndVerifyTransition(
+                mStateMachine.obtainMessage(CsipSetCoordinatorStateMachine.STACK_EVENT, event),
+                CsipSetCoordinatorStateMachine.Connected.class);
+    }
+
+    @Test
+    public void testStackEvent_toConnectedState_butNotAllowed_onDisconnectingState() {
+        initToDisconnectingState();
+        allowConnection(false);
+        CsipSetCoordinatorStackEvent event = new CsipSetCoordinatorStackEvent(
+                CsipSetCoordinatorStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED);
+        event.valueInt1 = CsipSetCoordinatorStackEvent.CONNECTION_STATE_CONNECTED;
+        mStateMachine.sendMessage(CsipSetCoordinatorStateMachine.STACK_EVENT, event);
+        TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper());
+        verify(mNativeInterface).disconnect(any());
+    }
+
+    @Test
+    public void testStackEvent_toConnectingState_onDisconnectingState() {
+        initToDisconnectingState();
+        allowConnection(true);
+        CsipSetCoordinatorStackEvent event = new CsipSetCoordinatorStackEvent(
+                CsipSetCoordinatorStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED);
+        event.valueInt1 = CsipSetCoordinatorStackEvent.CONNECTION_STATE_CONNECTING;
+        sendMessageAndVerifyTransition(
+                mStateMachine.obtainMessage(CsipSetCoordinatorStateMachine.STACK_EVENT, event),
+                CsipSetCoordinatorStateMachine.Connecting.class);
+    }
+
+    @Test
+    public void testStackEvent_toConnectingState_butNotAllowed_onDisconnectingState() {
+        initToDisconnectingState();
+        allowConnection(false);
+        CsipSetCoordinatorStackEvent event = new CsipSetCoordinatorStackEvent(
+                CsipSetCoordinatorStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED);
+        event.valueInt1 = CsipSetCoordinatorStackEvent.CONNECTION_STATE_CONNECTED;
+        mStateMachine.sendMessage(CsipSetCoordinatorStateMachine.STACK_EVENT, event);
+        TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper());
+        verify(mNativeInterface).disconnect(any());
+    }
+
+    private void initToConnectingState() {
+        allowConnection(true);
+        CsipSetCoordinatorStackEvent event = new CsipSetCoordinatorStackEvent(
+                CsipSetCoordinatorStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED);
+        event.valueInt1 = CsipSetCoordinatorStackEvent.CONNECTION_STATE_CONNECTING;
+        sendMessageAndVerifyTransition(
+                mStateMachine.obtainMessage(CsipSetCoordinatorStateMachine.STACK_EVENT, event),
+                CsipSetCoordinatorStateMachine.Connecting.class);
+        allowConnection(false);
+    }
+
+    private void initToConnectedState() {
+        allowConnection(true);
+        CsipSetCoordinatorStackEvent event = new CsipSetCoordinatorStackEvent(
+                CsipSetCoordinatorStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED);
+        event.valueInt1 = CsipSetCoordinatorStackEvent.CONNECTION_STATE_CONNECTED;
+        sendMessageAndVerifyTransition(
+                mStateMachine.obtainMessage(CsipSetCoordinatorStateMachine.STACK_EVENT, event),
+                CsipSetCoordinatorStateMachine.Connected.class);
+        allowConnection(false);
+    }
+
+    private void initToDisconnectingState() {
+        initToConnectingState();
+        CsipSetCoordinatorStackEvent event = new CsipSetCoordinatorStackEvent(
+                CsipSetCoordinatorStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED);
+        event.valueInt1 = CsipSetCoordinatorStackEvent.CONNECTION_STATE_DISCONNECTING;
+        sendMessageAndVerifyTransition(
+                mStateMachine.obtainMessage(CsipSetCoordinatorStateMachine.STACK_EVENT, event),
+                CsipSetCoordinatorStateMachine.Disconnecting.class);
+    }
+
+    private <T> void sendMessageAndVerifyTransition(Message msg, Class<T> type) {
+        Mockito.clearInvocations(mService);
+        mStateMachine.sendMessage(msg);
+        // Verify that one connection state broadcast is executed
+        verify(mService, timeout(TIMEOUT_MS)).sendBroadcast(any(Intent.class), anyString());
+        Assert.assertThat(mStateMachine.getCurrentState(), IsInstanceOf.instanceOf(type));
+    }
+
+    public static class CsipSetCoordinatorStateMachineWrapper
+            extends CsipSetCoordinatorStateMachine {
+
+        CsipSetCoordinatorStateMachineWrapper(BluetoothDevice device,
+                CsipSetCoordinatorService svc,
+                CsipSetCoordinatorNativeInterface nativeInterface, Looper looper) {
+            super(device, svc, nativeInterface, looper);
+        }
+
+        public boolean doesSuperHaveDeferredMessages(int what) {
+            return super.hasDeferredMessages(what);
+        }
+    }
 }
diff --git a/android/app/tests/unit/src/com/android/bluetooth/gatt/GattServiceTest.java b/android/app/tests/unit/src/com/android/bluetooth/gatt/GattServiceTest.java
index 271e892..8cdc684 100644
--- a/android/app/tests/unit/src/com/android/bluetooth/gatt/GattServiceTest.java
+++ b/android/app/tests/unit/src/com/android/bluetooth/gatt/GattServiceTest.java
@@ -19,11 +19,32 @@
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.mockito.Mockito.*;
+import static org.mockito.Mockito.verify;
 
 import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
 import android.bluetooth.BluetoothGatt;
+import android.bluetooth.BluetoothGattService;
+import android.bluetooth.BluetoothProfile;
+import android.bluetooth.BluetoothStatusCodes;
+import android.bluetooth.IBluetoothGattCallback;
+import android.bluetooth.IBluetoothGattServerCallback;
+import android.bluetooth.le.AdvertiseData;
+import android.bluetooth.le.AdvertisingSetParameters;
+import android.bluetooth.le.IAdvertisingSetCallback;
+import android.bluetooth.le.IPeriodicAdvertisingCallback;
+import android.bluetooth.le.IScannerCallback;
+import android.bluetooth.le.PeriodicAdvertisingParameters;
+import android.bluetooth.le.ScanFilter;
+import android.bluetooth.le.ScanResult;
+import android.bluetooth.le.ScanSettings;
 import android.content.AttributionSource;
 import android.content.Context;
+import android.content.res.Resources;
+import android.os.Binder;
+import android.os.ParcelUuid;
+import android.os.RemoteException;
+import android.os.WorkSource;
 
 import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.SmallTest;
@@ -33,17 +54,27 @@
 import com.android.bluetooth.R;
 import com.android.bluetooth.TestUtils;
 import com.android.bluetooth.btservice.AdapterService;
+import com.android.bluetooth.btservice.CompanionManager;
 
 import org.junit.After;
 import org.junit.Assert;
 import org.junit.Assume;
 import org.junit.Before;
+import org.junit.Ignore;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.UUID;
+
 /**
  * Test cases for {@link GattService}.
  */
@@ -58,13 +89,21 @@
     private GattService mService;
     @Mock private GattService.ClientMap mClientMap;
     @Mock private GattService.ScannerMap mScannerMap;
+    @Mock private GattService.ScannerMap.App mApp;
+    @Mock private GattService.PendingIntentInfo mPiInfo;
+    @Mock private ScanManager mScanManager;
+    @Mock private Set<String> mReliableQueue;
+    @Mock private GattService.ServerMap mServerMap;
 
     @Rule public final ServiceTestRule mServiceRule = new ServiceTestRule();
 
+    private BluetoothDevice mDevice;
     private BluetoothAdapter mAdapter;
     private AttributionSource mAttributionSource;
 
+    @Mock private Resources mResources;
     @Mock private AdapterService mAdapterService;
+    private CompanionManager mBtCompanionManager;
 
     @Before
     public void setUp() throws Exception {
@@ -76,6 +115,16 @@
 
         mAdapter = BluetoothAdapter.getDefaultAdapter();
         mAttributionSource = mAdapter.getAttributionSource();
+        mDevice = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(REMOTE_DEVICE_ADDRESS);
+
+        when(mAdapterService.getResources()).thenReturn(mResources);
+        when(mResources.getInteger(anyInt())).thenReturn(0);
+        when(mAdapterService.getSharedPreferences(anyString(), anyInt()))
+                .thenReturn(InstrumentationRegistry.getTargetContext()
+                        .getSharedPreferences("GattServiceTestPrefs", Context.MODE_PRIVATE));
+
+        mBtCompanionManager = new CompanionManager(mAdapterService, null);
+        doReturn(mBtCompanionManager).when(mAdapterService).getCompanionManager();
 
         TestUtils.startService(mServiceRule, GattService.class);
         mService = GattService.getGattService();
@@ -83,6 +132,9 @@
 
         mService.mClientMap = mClientMap;
         mService.mScannerMap = mScannerMap;
+        mService.mScanManager = mScanManager;
+        mService.mReliableQueue = mReliableQueue;
+        mService.mServerMap = mServerMap;
     }
 
     @After
@@ -183,4 +235,467 @@
     public void testDumpDoesNotCrash() {
         mService.dump(new StringBuilder());
     }
+
+    @Test
+    public void continuePiStartScan() {
+        int scannerId = 1;
+
+        mPiInfo.settings = new ScanSettings.Builder().build();
+        mApp.info = mPiInfo;
+
+        AppScanStats appScanStats = mock(AppScanStats.class);
+        doReturn(appScanStats).when(mScannerMap).getAppScanStatsById(scannerId);
+
+        mService.continuePiStartScan(scannerId, mApp);
+
+        verify(appScanStats).recordScanStart(
+                mPiInfo.settings, mPiInfo.filters, false, false, scannerId);
+        verify(mScanManager).startScan(any());
+    }
+
+    @Test
+    public void onBatchScanReportsInternal_deliverBatchScan() throws RemoteException {
+        int status = 1;
+        int scannerId = 2;
+        int reportType = ScanManager.SCAN_RESULT_TYPE_FULL;
+        int numRecords = 1;
+        byte[] recordData = new byte[]{0x01, 0x02, 0x03, 0x04, 0x05,
+                0x06, 0x07, 0x08, 0x09, 0x00, 0x00, 0x00, 0x00};
+
+        Set<ScanClient> scanClientSet = new HashSet<>();
+        ScanClient scanClient = new ScanClient(scannerId);
+        scanClient.associatedDevices = new ArrayList<>();
+        scanClient.associatedDevices.add("02:00:00:00:00:00");
+        scanClient.scannerId = scannerId;
+        scanClientSet.add(scanClient);
+        doReturn(scanClientSet).when(mScanManager).getFullBatchScanQueue();
+        doReturn(mApp).when(mScannerMap).getById(scanClient.scannerId);
+
+        mService.onBatchScanReportsInternal(status, scannerId, reportType, numRecords, recordData);
+        verify(mScanManager).callbackDone(scannerId, status);
+
+        reportType = ScanManager.SCAN_RESULT_TYPE_TRUNCATED;
+        recordData = new byte[]{0x00, 0x00, 0x00, 0x00, 0x00, 0x02,
+                0x06, 0x04, 0x02, 0x02, 0x00, 0x00, 0x02};
+        doReturn(scanClientSet).when(mScanManager).getBatchScanQueue();
+        IScannerCallback callback = mock(IScannerCallback.class);
+        mApp.callback = callback;
+
+        mService.onBatchScanReportsInternal(status, scannerId, reportType, numRecords, recordData);
+        verify(callback).onBatchScanResults(any());
+    }
+
+    @Test
+    public void disconnectAll() {
+        Map<Integer, String> connMap = new HashMap<>();
+        int clientIf = 1;
+        String address = "02:00:00:00:00:00";
+        connMap.put(clientIf, address);
+        doReturn(connMap).when(mClientMap).getConnectedMap();
+        Integer connId = 1;
+        doReturn(connId).when(mClientMap).connIdByAddress(clientIf, address);
+
+        mService.disconnectAll(mAttributionSource);
+    }
+
+    @Test
+    public void enforceReportDelayFloor() {
+        long reportDelayFloorHigher = GattService.DEFAULT_REPORT_DELAY_FLOOR + 1;
+        ScanSettings scanSettings = new ScanSettings.Builder()
+                .setReportDelay(reportDelayFloorHigher)
+                .build();
+
+        ScanSettings newScanSettings = mService.enforceReportDelayFloor(scanSettings);
+
+        assertThat(newScanSettings.getReportDelayMillis())
+                .isEqualTo(scanSettings.getReportDelayMillis());
+
+        ScanSettings scanSettingsFloor = new ScanSettings.Builder()
+                .setReportDelay(1)
+                .build();
+
+        ScanSettings newScanSettingsFloor = mService.enforceReportDelayFloor(scanSettingsFloor);
+
+        assertThat(newScanSettingsFloor.getReportDelayMillis())
+                .isEqualTo(GattService.DEFAULT_REPORT_DELAY_FLOOR);
+    }
+
+    @Test
+    public void setAdvertisingData() {
+        int advertiserId = 1;
+        AdvertiseData data = new AdvertiseData.Builder().build();
+
+        mService.setAdvertisingData(advertiserId, data, mAttributionSource);
+    }
+
+    @Test
+    public void setAdvertisingParameters() {
+        int advertiserId = 1;
+        AdvertisingSetParameters parameters = new AdvertisingSetParameters.Builder().build();
+
+        mService.setAdvertisingParameters(advertiserId, parameters, mAttributionSource);
+    }
+
+    @Test
+    public void setPeriodicAdvertisingData() {
+        int advertiserId = 1;
+        AdvertiseData data = new AdvertiseData.Builder().build();
+
+        mService.setPeriodicAdvertisingData(advertiserId, data, mAttributionSource);
+    }
+
+    @Test
+    public void setPeriodicAdvertisingEnable() {
+        int advertiserId = 1;
+        boolean enable = true;
+
+        mService.setPeriodicAdvertisingEnable(advertiserId, enable, mAttributionSource);
+    }
+
+    @Test
+    public void setPeriodicAdvertisingParameters() {
+        int advertiserId = 1;
+        PeriodicAdvertisingParameters parameters =
+                new PeriodicAdvertisingParameters.Builder().build();
+
+        mService.setPeriodicAdvertisingParameters(advertiserId, parameters, mAttributionSource);
+    }
+
+    @Test
+    public void setScanResponseData() {
+        int advertiserId = 1;
+        AdvertiseData data = new AdvertiseData.Builder().build();
+
+        mService.setScanResponseData(advertiserId, data, mAttributionSource);
+    }
+
+    @Test
+    public void getDevicesMatchingConnectionStates() {
+        int[] states = new int[] {BluetoothProfile.STATE_CONNECTED};
+
+        BluetoothDevice testDevice = mAdapter.getRemoteDevice("00:01:02:03:04:05");
+        BluetoothDevice[] bluetoothDevices = new BluetoothDevice[]{testDevice};
+        doReturn(bluetoothDevices).when(mAdapterService).getBondedDevices();
+
+        Set<String> connectedDevices = new HashSet<>();
+        String address = "02:00:00:00:00:00";
+        connectedDevices.add(address);
+        doReturn(connectedDevices).when(mClientMap).getConnectedDevices();
+
+        List<BluetoothDevice> deviceList =
+                mService.getDevicesMatchingConnectionStates(states, mAttributionSource);
+
+        int expectedSize = 1;
+        assertThat(deviceList.size()).isEqualTo(expectedSize);
+
+        BluetoothDevice bluetoothDevice = deviceList.get(0);
+        assertThat(bluetoothDevice.getAddress()).isEqualTo(address);
+    }
+
+    @Test
+    public void registerClient() {
+        UUID uuid = UUID.randomUUID();
+        IBluetoothGattCallback callback = mock(IBluetoothGattCallback.class);
+        boolean eattSupport = true;
+
+        mService.registerClient(uuid, callback, eattSupport, mAttributionSource);
+    }
+
+    @Test
+    public void unregisterClient() {
+        int clientIf = 3;
+
+        mService.unregisterClient(clientIf, mAttributionSource);
+        verify(mClientMap).remove(clientIf);
+    }
+
+    @Test
+    public void registerScanner() throws Exception {
+        IScannerCallback callback = mock(IScannerCallback.class);
+        WorkSource workSource = mock(WorkSource.class);
+
+        AppScanStats appScanStats = mock(AppScanStats.class);
+        doReturn(appScanStats).when(mScannerMap).getAppScanStatsByUid(Binder.getCallingUid());
+
+        mService.registerScanner(callback, workSource, mAttributionSource);
+        verify(mScannerMap).add(any(), eq(workSource), eq(callback), eq(null), eq(mService));
+        verify(mScanManager).registerScanner(any());
+    }
+
+    @Test
+    public void flushPendingBatchResults() {
+        int scannerId = 3;
+
+        mService.flushPendingBatchResults(scannerId, mAttributionSource);
+        verify(mScanManager).flushBatchScanResults(new ScanClient(scannerId));
+    }
+
+    @Test
+    public void readCharacteristic() {
+        int clientIf = 1;
+        String address = REMOTE_DEVICE_ADDRESS;
+        int handle = 2;
+        int authReq = 3;
+
+        Integer connId = 1;
+        doReturn(connId).when(mClientMap).connIdByAddress(clientIf, address);
+
+        mService.readCharacteristic(clientIf, address, handle, authReq, mAttributionSource);
+    }
+
+    @Test
+    public void readUsingCharacteristicUuid() {
+        int clientIf = 1;
+        String address = REMOTE_DEVICE_ADDRESS;
+        UUID uuid = UUID.randomUUID();
+        int startHandle = 2;
+        int endHandle = 3;
+        int authReq = 4;
+
+        Integer connId = 1;
+        doReturn(connId).when(mClientMap).connIdByAddress(clientIf, address);
+
+        mService.readUsingCharacteristicUuid(clientIf, address, uuid, startHandle, endHandle,
+                authReq, mAttributionSource);
+    }
+
+    @Test
+    public void writeCharacteristic() {
+        int clientIf = 1;
+        String address = REMOTE_DEVICE_ADDRESS;
+        int handle = 2;
+        int writeType = 3;
+        int authReq = 4;
+        byte[] value = new byte[] {5, 6};
+
+        Integer connId = 1;
+        doReturn(connId).when(mClientMap).connIdByAddress(clientIf, address);
+
+        int writeCharacteristicResult = mService.writeCharacteristic(clientIf, address, handle,
+                writeType, authReq, value, mAttributionSource);
+        assertThat(writeCharacteristicResult)
+                .isEqualTo(BluetoothStatusCodes.ERROR_DEVICE_NOT_CONNECTED);
+    }
+
+    @Test
+    public void readDescriptor() throws Exception {
+        int clientIf = 1;
+        String address = REMOTE_DEVICE_ADDRESS;
+        int handle = 2;
+        int authReq = 3;
+
+        Integer connId = 1;
+        doReturn(connId).when(mClientMap).connIdByAddress(clientIf, address);
+
+        mService.readDescriptor(clientIf, address, handle, authReq, mAttributionSource);
+    }
+
+    @Test
+    public void beginReliableWrite() {
+        int clientIf = 1;
+        String address = REMOTE_DEVICE_ADDRESS;
+
+        mService.beginReliableWrite(clientIf, address, mAttributionSource);
+        verify(mReliableQueue).add(address);
+    }
+
+    @Test
+    public void endReliableWrite() {
+        int clientIf = 1;
+        String address = REMOTE_DEVICE_ADDRESS;
+        boolean execute = true;
+
+        Integer connId = 1;
+        doReturn(connId).when(mClientMap).connIdByAddress(clientIf, address);
+
+        mService.endReliableWrite(clientIf, address, execute, mAttributionSource);
+        verify(mReliableQueue).remove(address);
+    }
+
+    @Test
+    public void registerForNotification() throws Exception {
+        int clientIf = 1;
+        String address = REMOTE_DEVICE_ADDRESS;
+        int handle = 2;
+        boolean enable = true;
+
+        Integer connId = 1;
+        doReturn(connId).when(mClientMap).connIdByAddress(clientIf, address);
+
+        mService.registerForNotification(clientIf, address, handle, enable, mAttributionSource);
+    }
+
+    @Test
+    public void readRemoteRssi() {
+        int clientIf = 1;
+        String address = REMOTE_DEVICE_ADDRESS;
+
+        mService.readRemoteRssi(clientIf, address, mAttributionSource);
+    }
+
+    @Test
+    public void configureMTU() {
+        int clientIf = 1;
+        String address = REMOTE_DEVICE_ADDRESS;
+        int mtu = 2;
+
+        Integer connId = 1;
+        doReturn(connId).when(mClientMap).connIdByAddress(clientIf, address);
+
+        mService.configureMTU(clientIf, address, mtu, mAttributionSource);
+    }
+
+    @Test
+    public void leConnectionUpdate() throws Exception {
+        int clientIf = 1;
+        String address = REMOTE_DEVICE_ADDRESS;
+        int minInterval = 3;
+        int maxInterval = 4;
+        int peripheralLatency = 5;
+        int supervisionTimeout = 6;
+        int minConnectionEventLen = 7;
+        int maxConnectionEventLen = 8;
+
+        mService.leConnectionUpdate(clientIf, address, minInterval, maxInterval,
+                peripheralLatency, supervisionTimeout, minConnectionEventLen,
+                maxConnectionEventLen, mAttributionSource);
+    }
+
+    @Test
+    public void serverConnect() {
+        int serverIf = 1;
+        String address = REMOTE_DEVICE_ADDRESS;
+        boolean isDirect = true;
+        int transport = 2;
+
+        mService.serverConnect(serverIf, address, isDirect, transport, mAttributionSource);
+    }
+
+    @Test
+    public void serverDisconnect() {
+        int serverIf = 1;
+        String address = REMOTE_DEVICE_ADDRESS;
+
+        Integer connId = 1;
+        doReturn(connId).when(mServerMap).connIdByAddress(serverIf, address);
+
+        mService.serverDisconnect(serverIf, address, mAttributionSource);
+    }
+
+    @Test
+    public void serverSetPreferredPhy() throws Exception {
+        int serverIf = 1;
+        String address = REMOTE_DEVICE_ADDRESS;
+        int txPhy = 2;
+        int rxPhy = 1;
+        int phyOptions = 3;
+
+        mService.serverSetPreferredPhy(serverIf, address, txPhy, rxPhy, phyOptions,
+                mAttributionSource);
+    }
+
+    @Test
+    public void serverReadPhy() {
+        int serverIf = 1;
+        String address = REMOTE_DEVICE_ADDRESS;
+
+        mService.serverReadPhy(serverIf, address, mAttributionSource);
+    }
+
+    @Test
+    public void sendNotification() throws Exception {
+        int serverIf = 1;
+        String address = REMOTE_DEVICE_ADDRESS;
+        int handle = 2;
+        boolean confirm = true;
+        byte[] value = new byte[] {5, 6};;
+
+        Integer connId = 1;
+        doReturn(connId).when(mServerMap).connIdByAddress(serverIf, address);
+
+        mService.sendNotification(serverIf, address, handle, confirm, value, mAttributionSource);
+
+        confirm = false;
+
+        mService.sendNotification(serverIf, address, handle, confirm, value, mAttributionSource);
+    }
+
+    @Test
+    public void getOwnAddress() throws Exception {
+        int advertiserId = 1;
+
+        mService.getOwnAddress(advertiserId, mAttributionSource);
+    }
+
+    @Test
+    public void enableAdvertisingSet() throws Exception {
+        int advertiserId = 1;
+        boolean enable = true;
+        int duration = 3;
+        int maxExtAdvEvents = 4;
+
+        mService.enableAdvertisingSet(advertiserId, enable, duration, maxExtAdvEvents,
+                mAttributionSource);
+    }
+
+    @Ignore("b/265327402")
+    @Test
+    public void registerSync() {
+        ScanResult scanResult = new ScanResult(mDevice, 1, 2, 3, 4, 5, 6, 7, null, 8);
+        int skip = 1;
+        int timeout = 2;
+        IPeriodicAdvertisingCallback callback = mock(IPeriodicAdvertisingCallback.class);
+
+        mService.registerSync(scanResult, skip, timeout, callback, mAttributionSource);
+    }
+
+    @Test
+    public void transferSync() {
+        int serviceData = 1;
+        int syncHandle = 2;
+
+        mService.transferSync(mDevice, serviceData, syncHandle, mAttributionSource);
+    }
+
+    @Ignore("b/265327402")
+    @Test
+    public void transferSetInfo() {
+        int serviceData = 1;
+        int advHandle = 2;
+        IPeriodicAdvertisingCallback callback = mock(IPeriodicAdvertisingCallback.class);
+
+        mService.transferSetInfo(mDevice, serviceData, advHandle, callback,
+                mAttributionSource);
+    }
+
+    @Ignore("b/265327402")
+    @Test
+    public void unregisterSync() {
+        IPeriodicAdvertisingCallback callback = mock(IPeriodicAdvertisingCallback.class);
+
+        mService.unregisterSync(callback, mAttributionSource);
+    }
+
+    @Test
+    public void unregAll() throws Exception {
+        int appId = 1;
+        List<Integer> appIds = new ArrayList<>();
+        appIds.add(appId);
+        doReturn(appIds).when(mClientMap).getAllAppsIds();
+
+        mService.unregAll(mAttributionSource);
+        verify(mClientMap).remove(appId);
+    }
+
+    @Test
+    public void numHwTrackFiltersAvailable() {
+        mService.numHwTrackFiltersAvailable(mAttributionSource);
+        verify(mScanManager).getCurrentUsedTrackingAdvertisement();
+    }
+
+    @Test
+    public void cleanUp_doesNotCrash() {
+        mService.cleanup();
+    }
 }
+
diff --git a/android/app/tests/unit/src/com/android/bluetooth/hfp/HeadsetServiceAndStateMachineTest.java b/android/app/tests/unit/src/com/android/bluetooth/hfp/HeadsetServiceAndStateMachineTest.java
index c89e135..c41cbce 100644
--- a/android/app/tests/unit/src/com/android/bluetooth/hfp/HeadsetServiceAndStateMachineTest.java
+++ b/android/app/tests/unit/src/com/android/bluetooth/hfp/HeadsetServiceAndStateMachineTest.java
@@ -26,6 +26,7 @@
 import android.app.Activity;
 import android.app.Instrumentation;
 import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothSinkAudioPolicy;
 import android.bluetooth.BluetoothDevice;
 import android.bluetooth.BluetoothHeadset;
 import android.bluetooth.BluetoothProfile;
@@ -66,6 +67,7 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 import org.mockito.Spy;
+import org.mockito.InOrder;
 
 import java.lang.reflect.Method;
 import java.util.Collections;
@@ -181,6 +183,8 @@
                 .getBondState(any(BluetoothDevice.class));
         doAnswer(invocation -> mBondedDevices.toArray(new BluetoothDevice[]{})).when(
                 mAdapterService).getBondedDevices();
+        doReturn(new BluetoothSinkAudioPolicy.Builder().build()).when(mAdapterService)
+                .getRequestedAudioPolicyAsSink(any(BluetoothDevice.class));
         // Mock system interface
         doNothing().when(mSystemInterface).stop();
         when(mSystemInterface.getHeadsetPhoneState()).thenReturn(mPhoneState);
@@ -663,6 +667,7 @@
         Assert.assertTrue(mHeadsetService.setActiveDevice(device));
         verify(mNativeInterface).setActiveDevice(device);
         Assert.assertEquals(device, mHeadsetService.getActiveDevice());
+        verify(mNativeInterface).sendBsir(eq(device), eq(true));
         // Start voice recognition
         startVoiceRecognitionFromHf(device);
     }
@@ -686,6 +691,7 @@
         Assert.assertTrue(mHeadsetService.setActiveDevice(device));
         verify(mNativeInterface).setActiveDevice(device);
         Assert.assertEquals(device, mHeadsetService.getActiveDevice());
+        verify(mNativeInterface).sendBsir(eq(device), eq(true));
         // Start voice recognition
         startVoiceRecognitionFromHf(device);
         // Stop voice recognition
@@ -720,6 +726,7 @@
         Assert.assertTrue(mHeadsetService.setActiveDevice(device));
         verify(mNativeInterface).setActiveDevice(device);
         Assert.assertEquals(device, mHeadsetService.getActiveDevice());
+        verify(mNativeInterface).sendBsir(eq(device), eq(true));
         // Start voice recognition
         HeadsetStackEvent startVrEvent =
                 new HeadsetStackEvent(HeadsetStackEvent.EVENT_TYPE_VR_STATE_CHANGED,
@@ -752,6 +759,7 @@
         Assert.assertTrue(mHeadsetService.setActiveDevice(device));
         verify(mNativeInterface).setActiveDevice(device);
         Assert.assertEquals(device, mHeadsetService.getActiveDevice());
+        verify(mNativeInterface).sendBsir(eq(device), eq(true));
         // Start voice recognition
         HeadsetStackEvent startVrEvent =
                 new HeadsetStackEvent(HeadsetStackEvent.EVENT_TYPE_VR_STATE_CHANGED,
@@ -784,6 +792,7 @@
         Assert.assertTrue(mHeadsetService.setActiveDevice(device));
         verify(mNativeInterface).setActiveDevice(device);
         Assert.assertEquals(device, mHeadsetService.getActiveDevice());
+        verify(mNativeInterface).sendBsir(eq(device), eq(true));
         // Start voice recognition
         startVoiceRecognitionFromAg();
     }
@@ -857,6 +866,7 @@
         Assert.assertTrue(mHeadsetService.setActiveDevice(device));
         verify(mNativeInterface).setActiveDevice(device);
         Assert.assertEquals(device, mHeadsetService.getActiveDevice());
+        verify(mNativeInterface).sendBsir(eq(device), eq(true));
         // Start voice recognition
         startVoiceRecognitionFromAg();
         // Stop voice recognition
@@ -905,8 +915,10 @@
         connectTestDevice(deviceA);
         BluetoothDevice deviceB = TestUtils.getTestDevice(mAdapter, 1);
         connectTestDevice(deviceB);
-        verify(mNativeInterface, timeout(ASYNC_CALL_TIMEOUT_MILLIS)).sendBsir(deviceA, false);
-        verify(mNativeInterface, timeout(ASYNC_CALL_TIMEOUT_MILLIS)).sendBsir(deviceB, false);
+        InOrder inOrder = inOrder(mNativeInterface);
+        inOrder.verify(mNativeInterface).sendBsir(eq(deviceA), eq(true));
+        inOrder.verify(mNativeInterface).sendBsir(eq(deviceB), eq(false));
+        inOrder.verify(mNativeInterface).sendBsir(eq(deviceA), eq(false));
         // Set active device to device B
         Assert.assertTrue(mHeadsetService.setActiveDevice(deviceB));
         verify(mNativeInterface).setActiveDevice(deviceB);
@@ -957,8 +969,10 @@
         connectTestDevice(deviceA);
         BluetoothDevice deviceB = TestUtils.getTestDevice(mAdapter, 1);
         connectTestDevice(deviceB);
-        verify(mNativeInterface, timeout(ASYNC_CALL_TIMEOUT_MILLIS)).sendBsir(deviceA, false);
-        verify(mNativeInterface, timeout(ASYNC_CALL_TIMEOUT_MILLIS)).sendBsir(deviceB, false);
+        InOrder inOrder = inOrder(mNativeInterface);
+        inOrder.verify(mNativeInterface).sendBsir(eq(deviceA), eq(true));
+        inOrder.verify(mNativeInterface).sendBsir(eq(deviceB), eq(false));
+        inOrder.verify(mNativeInterface).sendBsir(eq(deviceA), eq(false));
         // Set active device to device B
         Assert.assertTrue(mHeadsetService.setActiveDevice(deviceB));
         verify(mNativeInterface).setActiveDevice(deviceB);
@@ -1009,8 +1023,10 @@
         connectTestDevice(deviceA);
         BluetoothDevice deviceB = TestUtils.getTestDevice(mAdapter, 1);
         connectTestDevice(deviceB);
-        verify(mNativeInterface, timeout(ASYNC_CALL_TIMEOUT_MILLIS)).sendBsir(deviceA, false);
-        verify(mNativeInterface, timeout(ASYNC_CALL_TIMEOUT_MILLIS)).sendBsir(deviceB, false);
+        InOrder inOrder = inOrder(mNativeInterface);
+        inOrder.verify(mNativeInterface).sendBsir(eq(deviceA), eq(true));
+        inOrder.verify(mNativeInterface).sendBsir(eq(deviceB), eq(false));
+        inOrder.verify(mNativeInterface).sendBsir(eq(deviceA), eq(false));
         // Set active device to device B
         Assert.assertTrue(mHeadsetService.setActiveDevice(deviceB));
         verify(mNativeInterface).setActiveDevice(deviceB);
@@ -1048,8 +1064,10 @@
         connectTestDevice(deviceA);
         BluetoothDevice deviceB = TestUtils.getTestDevice(mAdapter, 1);
         connectTestDevice(deviceB);
-        verify(mNativeInterface, timeout(ASYNC_CALL_TIMEOUT_MILLIS)).sendBsir(deviceA, false);
-        verify(mNativeInterface, timeout(ASYNC_CALL_TIMEOUT_MILLIS)).sendBsir(deviceB, false);
+        InOrder inOrder = inOrder(mNativeInterface);
+        inOrder.verify(mNativeInterface).sendBsir(eq(deviceA), eq(true));
+        inOrder.verify(mNativeInterface).sendBsir(eq(deviceB), eq(false));
+        inOrder.verify(mNativeInterface).sendBsir(eq(deviceA), eq(false));
         // Set active device to device B
         Assert.assertTrue(mHeadsetService.setActiveDevice(deviceB));
         verify(mNativeInterface).setActiveDevice(deviceB);
diff --git a/android/app/tests/unit/src/com/android/bluetooth/hfp/HeadsetServiceTest.java b/android/app/tests/unit/src/com/android/bluetooth/hfp/HeadsetServiceTest.java
index e071563..09fe7cc 100644
--- a/android/app/tests/unit/src/com/android/bluetooth/hfp/HeadsetServiceTest.java
+++ b/android/app/tests/unit/src/com/android/bluetooth/hfp/HeadsetServiceTest.java
@@ -19,6 +19,7 @@
 import static org.mockito.Mockito.*;
 
 import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothSinkAudioPolicy;
 import android.bluetooth.BluetoothDevice;
 import android.bluetooth.BluetoothHeadset;
 import android.bluetooth.BluetoothProfile;
@@ -116,6 +117,8 @@
             Set<BluetoothDevice> keys = mStateMachines.keySet();
             return keys.toArray(new BluetoothDevice[keys.size()]);
         }).when(mAdapterService).getBondedDevices();
+        doReturn(new BluetoothSinkAudioPolicy.Builder().build()).when(mAdapterService)
+                .getRequestedAudioPolicyAsSink(any(BluetoothDevice.class));
         // Mock system interface
         doNothing().when(mSystemInterface).stop();
         when(mSystemInterface.getHeadsetPhoneState()).thenReturn(mPhoneState);
@@ -991,6 +994,44 @@
         mHeadsetService.dump(sb);
     }
 
+    @Test
+    public void testConnectDeviceNotAllowedInbandRingPolicy_InbandRingStatus() {
+        when(mDatabaseManager.getProfileConnectionPolicy(any(BluetoothDevice.class),
+                eq(BluetoothProfile.HEADSET)))
+                .thenReturn(BluetoothProfile.CONNECTION_POLICY_UNKNOWN);
+        mCurrentDevice = TestUtils.getTestDevice(mAdapter, 0);
+        Assert.assertTrue(mHeadsetService.connect(mCurrentDevice));
+        when(mStateMachines.get(mCurrentDevice).getDevice()).thenReturn(mCurrentDevice);
+        when(mStateMachines.get(mCurrentDevice).getConnectionState()).thenReturn(
+                BluetoothProfile.STATE_CONNECTED);
+        when(mStateMachines.get(mCurrentDevice).getConnectingTimestampMs()).thenReturn(
+                SystemClock.uptimeMillis());
+        Assert.assertEquals(Collections.singletonList(mCurrentDevice),
+                mHeadsetService.getConnectedDevices());
+        mHeadsetService.onConnectionStateChangedFromStateMachine(mCurrentDevice,
+                BluetoothProfile.STATE_DISCONNECTED, BluetoothProfile.STATE_CONNECTED);
+
+        when(mStateMachines.get(mCurrentDevice).getHfpCallAudioPolicy()).thenReturn(
+                new BluetoothSinkAudioPolicy.Builder()
+                        .setCallEstablishPolicy(BluetoothSinkAudioPolicy.POLICY_ALLOWED)
+                        .setActiveDevicePolicyAfterConnection(
+                                BluetoothSinkAudioPolicy.POLICY_ALLOWED)
+                        .setInBandRingtonePolicy(BluetoothSinkAudioPolicy.POLICY_ALLOWED)
+                        .build()
+        );
+        Assert.assertEquals(true, mHeadsetService.isInbandRingingEnabled());
+
+        when(mStateMachines.get(mCurrentDevice).getHfpCallAudioPolicy()).thenReturn(
+                new BluetoothSinkAudioPolicy.Builder()
+                        .setCallEstablishPolicy(BluetoothSinkAudioPolicy.POLICY_ALLOWED)
+                        .setActiveDevicePolicyAfterConnection(
+                                BluetoothSinkAudioPolicy.POLICY_ALLOWED)
+                        .setInBandRingtonePolicy(BluetoothSinkAudioPolicy.POLICY_NOT_ALLOWED)
+                        .build()
+        );
+        Assert.assertEquals(false, mHeadsetService.isInbandRingingEnabled());
+    }
+
     private void addConnectedDeviceHelper(BluetoothDevice device) {
         mCurrentDevice = device;
         when(mDatabaseManager.getProfileConnectionPolicy(any(BluetoothDevice.class),
diff --git a/android/app/tests/unit/src/com/android/bluetooth/hfp/HeadsetStateMachineTest.java b/android/app/tests/unit/src/com/android/bluetooth/hfp/HeadsetStateMachineTest.java
index 971e53b..025fe05 100644
--- a/android/app/tests/unit/src/com/android/bluetooth/hfp/HeadsetStateMachineTest.java
+++ b/android/app/tests/unit/src/com/android/bluetooth/hfp/HeadsetStateMachineTest.java
@@ -51,12 +51,14 @@
 import com.android.bluetooth.R;
 import com.android.bluetooth.TestUtils;
 import com.android.bluetooth.btservice.AdapterService;
+import com.android.bluetooth.btservice.storage.DatabaseManager;
 
 import org.hamcrest.core.IsInstanceOf;
 import org.junit.After;
 import org.junit.Assert;
 import org.junit.Assume;
 import org.junit.Before;
+import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
@@ -85,6 +87,7 @@
     private ArgumentCaptor<Intent> mIntentArgument = ArgumentCaptor.forClass(Intent.class);
 
     @Mock private AdapterService mAdapterService;
+    @Mock private DatabaseManager mDatabaseManager;
     @Mock private HeadsetService mHeadsetService;
     @Mock private HeadsetSystemInterface mSystemInterface;
     @Mock private AudioManager mAudioManager;
@@ -108,6 +111,9 @@
         mAdapter = BluetoothAdapter.getDefaultAdapter();
         // Get a device for testing
         mTestDevice = mAdapter.getRemoteDevice("00:01:02:03:04:05");
+        // Get a database
+        doReturn(mDatabaseManager).when(mAdapterService).getDatabase();
+        doReturn(true).when(mDatabaseManager).setAudioPolicyMetadata(anyObject(), anyObject());
         // Spy on native interface
         mNativeInterface = spy(HeadsetNativeInterface.getInstance());
         doNothing().when(mNativeInterface).init(anyInt(), anyBoolean());
@@ -1186,6 +1192,7 @@
         Assert.assertEquals(mHeadsetStateMachine.parseUnknownAt(atString), "A\"command\"");
     }
 
+    @Ignore("b/265556073")
     @Test
     public void testHandleAccessPermissionResult_withNoChangeInAtCommandResult() {
         when(mIntent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE)).thenReturn(null);
@@ -1435,6 +1442,22 @@
     }
 
     /**
+     * A test to validate received Android AT commands and processing
+     */
+    @Test
+    public void testProcessAndroidAt() {
+        setUpConnectedState();
+        // setup Audio Policy Feature
+        setUpAudioPolicy();
+        // receive and set android policy
+        mHeadsetStateMachine.sendMessage(HeadsetStateMachine.STACK_EVENT,
+                new HeadsetStackEvent(HeadsetStackEvent.EVENT_TYPE_UNKNOWN_AT,
+                        "+ANDROID=1,1,1,1", mTestDevice));
+        verify(mDatabaseManager, timeout(ASYNC_CALL_TIMEOUT_MILLIS))
+                .setAudioPolicyMetadata(anyObject(), anyObject());
+    }
+
+    /**
      * Setup Connecting State
      * @return number of times mHeadsetService.sendBroadcastAsUser() has been invoked
      */
@@ -1548,4 +1571,12 @@
                 IsInstanceOf.instanceOf(HeadsetStateMachine.Disconnecting.class));
         return numBroadcastsSent;
     }
+
+    private void setUpAudioPolicy() {
+        mHeadsetStateMachine.sendMessage(HeadsetStateMachine.STACK_EVENT,
+                new HeadsetStackEvent(HeadsetStackEvent.EVENT_TYPE_UNKNOWN_AT,
+                        "+ANDROID=?", mTestDevice));
+        verify(mNativeInterface, timeout(ASYNC_CALL_TIMEOUT_MILLIS)).atResponseString(
+                anyObject(), anyString());
+    }
 }
diff --git a/android/app/tests/unit/src/com/android/bluetooth/hfpclient/HeadsetClientServiceTest.java b/android/app/tests/unit/src/com/android/bluetooth/hfpclient/HeadsetClientServiceTest.java
index e85dc17..be9df4b 100644
--- a/android/app/tests/unit/src/com/android/bluetooth/hfpclient/HeadsetClientServiceTest.java
+++ b/android/app/tests/unit/src/com/android/bluetooth/hfpclient/HeadsetClientServiceTest.java
@@ -16,6 +16,7 @@
 
 package com.android.bluetooth.hfpclient;
 
+import static org.mockito.Mockito.any;
 import static org.mockito.Mockito.anyInt;
 import static org.mockito.Mockito.anyString;
 import static org.mockito.Mockito.doReturn;
@@ -25,6 +26,8 @@
 
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothHeadset;
+import android.bluetooth.BluetoothSinkAudioPolicy;
 import android.bluetooth.BluetoothManager;
 import android.content.Context;
 import android.content.Intent;
@@ -148,4 +151,17 @@
 
         mService.dump(new StringBuilder());
     }
+
+    @Test
+    public void testSetCallAudioPolicy() {
+        // Put mock state machine
+        BluetoothDevice device =
+                BluetoothAdapter.getDefaultAdapter().getRemoteDevice("00:01:02:03:04:05");
+        mService.getStateMachineMap().put(device, mStateMachine);
+
+        mService.setAudioPolicy(device, new BluetoothSinkAudioPolicy.Builder().build());
+
+        verify(mStateMachine, timeout(STANDARD_WAIT_MILLIS).times(1))
+                .setAudioPolicy(any(BluetoothSinkAudioPolicy.class));
+    }
 }
diff --git a/android/app/tests/unit/src/com/android/bluetooth/hfpclient/HeadsetClientStateMachineTest.java b/android/app/tests/unit/src/com/android/bluetooth/hfpclient/HeadsetClientStateMachineTest.java
index 0aa98f2..dc8683d 100644
--- a/android/app/tests/unit/src/com/android/bluetooth/hfpclient/HeadsetClientStateMachineTest.java
+++ b/android/app/tests/unit/src/com/android/bluetooth/hfpclient/HeadsetClientStateMachineTest.java
@@ -1,5 +1,26 @@
+/*
+ * Copyright 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
 package com.android.bluetooth.hfpclient;
 
+import static android.bluetooth.BluetoothProfile.STATE_CONNECTED;
+import static android.bluetooth.BluetoothProfile.STATE_CONNECTING;
+import static android.bluetooth.BluetoothProfile.STATE_DISCONNECTED;
+import static android.bluetooth.BluetoothProfile.STATE_DISCONNECTING;
+
 import static com.android.bluetooth.hfpclient.HeadsetClientStateMachine.AT_OK;
 import static com.android.bluetooth.hfpclient.HeadsetClientStateMachine.ENTER_PRIVATE_MODE;
 import static com.android.bluetooth.hfpclient.HeadsetClientStateMachine.EXPLICIT_CALL_TRANSFER;
@@ -13,6 +34,7 @@
 import android.bluetooth.BluetoothAssignedNumbers;
 import android.bluetooth.BluetoothDevice;
 import android.bluetooth.BluetoothHeadsetClient;
+import android.bluetooth.BluetoothSinkAudioPolicy;
 import android.bluetooth.BluetoothProfile;
 import android.content.Context;
 import android.content.Intent;
@@ -20,6 +42,7 @@
 import android.media.AudioManager;
 import android.os.Bundle;
 import android.os.HandlerThread;
+import android.os.Looper;
 import android.os.Message;
 import android.util.Pair;
 
@@ -35,6 +58,8 @@
 import com.android.bluetooth.TestUtils;
 import com.android.bluetooth.Utils;
 import com.android.bluetooth.btservice.AdapterService;
+import com.android.bluetooth.hfp.HeadsetService;
+import com.android.bluetooth.hfp.HeadsetStackEvent;
 
 import org.hamcrest.core.AllOf;
 import org.hamcrest.core.IsInstanceOf;
@@ -46,18 +71,22 @@
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
 import org.mockito.Mock;
+import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
 import org.mockito.hamcrest.MockitoHamcrest;
 
 import java.util.List;
 import java.util.Set;
 
+/**
+ * Test cases for {@link HeadsetClientStateMachine}.
+ */
 @LargeTest
 @RunWith(AndroidJUnit4.class)
 public class HeadsetClientStateMachineTest {
     private BluetoothAdapter mAdapter;
     private HandlerThread mHandlerThread;
-    private HeadsetClientStateMachine mHeadsetClientStateMachine;
+    private TestHeadsetClientStateMachine mHeadsetClientStateMachine;
     private BluetoothDevice mTestDevice;
     private Context mTargetContext;
 
@@ -66,6 +95,8 @@
     @Mock
     private Resources mMockHfpResources;
     @Mock
+    private HeadsetService mHeadsetService;
+    @Mock
     private HeadsetClientService mHeadsetClientService;
     @Mock
     private AudioManager mAudioManager;
@@ -76,6 +107,7 @@
     private static final int QUERY_CURRENT_CALLS_WAIT_MILLIS = 2000;
     private static final int QUERY_CURRENT_CALLS_TEST_WAIT_MILLIS = QUERY_CURRENT_CALLS_WAIT_MILLIS
             * 3 / 2;
+    private static final int TIMEOUT_MS = 1000;
 
     @Before
     public void setUp() throws Exception {
@@ -97,6 +129,7 @@
 
         TestUtils.setAdapterService(mAdapterService);
         mNativeInterface = spy(NativeInterface.getInstance());
+        doReturn(true).when(mNativeInterface).sendAndroidAt(anyObject(), anyString());
 
         // This line must be called to make sure relevant objects are initialized properly
         mAdapter = BluetoothAdapter.getDefaultAdapter();
@@ -107,9 +140,8 @@
         mHandlerThread = new HandlerThread("HeadsetClientStateMachineTestHandlerThread");
         mHandlerThread.start();
         // Manage looper execution in main test thread explicitly to guarantee timing consistency
-        mHeadsetClientStateMachine =
-                new HeadsetClientStateMachine(mHeadsetClientService, mHandlerThread.getLooper(),
-                                              mNativeInterface);
+        mHeadsetClientStateMachine = new TestHeadsetClientStateMachine(mHeadsetClientService,
+                mHeadsetService, mHandlerThread.getLooper(), mNativeInterface);
         mHeadsetClientStateMachine.start();
         TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper());
     }
@@ -120,9 +152,12 @@
             return;
         }
         TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper());
-        TestUtils.clearAdapterService(mAdapterService);
+        mHeadsetClientStateMachine.allowConnect = null;
         mHeadsetClientStateMachine.doQuit();
+        TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper());
         mHandlerThread.quit();
+        TestUtils.clearAdapterService(mAdapterService);
+        verifyNoMoreInteractions(mHeadsetService);
     }
 
     /**
@@ -201,6 +236,9 @@
         slcEvent.valueInt2 = HeadsetClientHalConstants.PEER_FEAT_ECS;
         slcEvent.device = mTestDevice;
         mHeadsetClientStateMachine.sendMessage(StackEvent.STACK_EVENT, slcEvent);
+        TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper());
+
+        setUpAndroidAt(false);
 
         // Verify that one connection state broadcast is executed
         ArgumentCaptor<Intent> intentArgument2 = ArgumentCaptor.forClass(Intent.class);
@@ -212,6 +250,7 @@
         // Check we are in connecting state now.
         Assert.assertThat(mHeadsetClientStateMachine.getCurrentState(),
                 IsInstanceOf.instanceOf(HeadsetClientStateMachine.Connected.class));
+        verify(mHeadsetService).updateInbandRinging(eq(mTestDevice), eq(true));
     }
 
     /**
@@ -254,6 +293,7 @@
         // Check we are in connecting state now.
         Assert.assertThat(mHeadsetClientStateMachine.getCurrentState(),
                 IsInstanceOf.instanceOf(HeadsetClientStateMachine.Disconnected.class));
+        verify(mHeadsetService).updateInbandRinging(eq(mTestDevice), eq(false));
     }
 
     /**
@@ -293,6 +333,9 @@
         slcEvent.valueInt2 = HeadsetClientHalConstants.PEER_FEAT_ECS;
         slcEvent.device = mTestDevice;
         mHeadsetClientStateMachine.sendMessage(StackEvent.STACK_EVENT, slcEvent);
+        TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper());
+
+        setUpAndroidAt(false);
 
         verify(mHeadsetClientService,
                 timeout(STANDARD_WAIT_MILLIS).times(expectedBroadcastMultiplePermissionsIndex++))
@@ -302,6 +345,8 @@
         Assert.assertEquals(BluetoothProfile.STATE_CONNECTED,
                 intentArgument.getValue().getIntExtra(BluetoothProfile.EXTRA_STATE, -1));
 
+        verify(mHeadsetService).updateInbandRinging(eq(mTestDevice), eq(true));
+
         StackEvent event = new StackEvent(StackEvent.EVENT_TYPE_IN_BAND_RINGTONE);
         event.valueInt = 0;
         event.device = mTestDevice;
@@ -396,6 +441,10 @@
 
     /* Utility function to simulate SLC connection. */
     private int setUpServiceLevelConnection(int startBroadcastIndex) {
+        return setUpServiceLevelConnection(startBroadcastIndex, false);
+    }
+
+    private int setUpServiceLevelConnection(int startBroadcastIndex, boolean androidAtSupported) {
         // Trigger SLC connection
         StackEvent slcEvent = new StackEvent(StackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED);
         slcEvent.valueInt = HeadsetClientHalConstants.CONNECTION_STATE_SLC_CONNECTED;
@@ -403,16 +452,47 @@
         slcEvent.valueInt2 |= HeadsetClientHalConstants.PEER_FEAT_HF_IND;
         slcEvent.device = mTestDevice;
         mHeadsetClientStateMachine.sendMessage(StackEvent.STACK_EVENT, slcEvent);
+        TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper());
+
+        setUpAndroidAt(androidAtSupported);
+
         ArgumentCaptor<Intent> intentArgument = ArgumentCaptor.forClass(Intent.class);
         verify(mHeadsetClientService, timeout(STANDARD_WAIT_MILLIS).times(startBroadcastIndex))
                 .sendBroadcastMultiplePermissions(intentArgument.capture(),
                                                   any(String[].class), any(BroadcastOptions.class));
         Assert.assertEquals(BluetoothProfile.STATE_CONNECTED,
                 intentArgument.getValue().getIntExtra(BluetoothProfile.EXTRA_STATE, -1));
+        Assert.assertThat(mHeadsetClientStateMachine.getCurrentState(),
+                IsInstanceOf.instanceOf(HeadsetClientStateMachine.Connected.class));
+        verify(mHeadsetService).updateInbandRinging(eq(mTestDevice), eq(true));
+
         startBroadcastIndex++;
         return startBroadcastIndex;
     }
 
+    /**
+     * Set up and verify AT Android related commands and events.
+     * Make sure this method is invoked after SLC is setup.
+     */
+    private void setUpAndroidAt(boolean androidAtSupported) {
+        verify(mNativeInterface).sendAndroidAt(mTestDevice, "+ANDROID=?");
+        if (androidAtSupported) {
+            StackEvent unknownEvt = new StackEvent(StackEvent.EVENT_TYPE_UNKNOWN_EVENT);
+            unknownEvt.valueString = "+ANDROID: 1";
+            unknownEvt.device = mTestDevice;
+            mHeadsetClientStateMachine.sendMessage(StackEvent.STACK_EVENT, unknownEvt);
+            TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper());
+            verify(mHeadsetClientService).setAudioPolicyRemoteSupported(mTestDevice, true);
+            mHeadsetClientStateMachine.setAudioPolicyRemoteSupported(true);
+        } else {
+            // receive CMD_RESULT CME_ERROR due to remote not supporting Android AT
+            StackEvent cmdResEvt = new StackEvent(StackEvent.EVENT_TYPE_CMD_RESULT);
+            cmdResEvt.valueInt = StackEvent.CMD_RESULT_TYPE_CME_ERROR;
+            cmdResEvt.device = mTestDevice;
+            mHeadsetClientStateMachine.sendMessage(StackEvent.STACK_EVENT, cmdResEvt);
+        }
+    }
+
     /* Utility function: supported AT command should lead to native call */
     private void runSupportedVendorAtCommand(String atCommand, int vendorId) {
         // Return true for priority.
@@ -991,4 +1071,361 @@
         Assert.assertEquals(HeadsetClientStateMachine.getMessageName(unknownMessageInt),
                 "UNKNOWN(" + unknownMessageInt + ")");
     }
+    /**
+     * Tests and verify behavior of the case where remote device doesn't support
+     * At Android but tries to send audio policy.
+     */
+    @Test
+    public void testAndroidAtRemoteNotSupported_StateTransition_setAudioPolicy() {
+        // 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);
+
+        BluetoothSinkAudioPolicy dummyAudioPolicy = new BluetoothSinkAudioPolicy.Builder().build();
+        mHeadsetClientStateMachine.setAudioPolicy(dummyAudioPolicy);
+        verify(mNativeInterface, never()).sendAndroidAt(mTestDevice, "+ANDROID:1,0,0,0");
+    }
+
+    @SmallTest
+    @Test
+    public void testSetGetCallAudioPolicy() {
+        // Return true for priority.
+        when(mHeadsetClientService.getConnectionPolicy(any(BluetoothDevice.class))).thenReturn(
+                BluetoothProfile.CONNECTION_POLICY_ALLOWED);
+
+        int expectedBroadcastIndex = 1;
+
+        expectedBroadcastIndex = setUpHfpClientConnection(expectedBroadcastIndex);
+        expectedBroadcastIndex = setUpServiceLevelConnection(expectedBroadcastIndex, true);
+
+        BluetoothSinkAudioPolicy dummyAudioPolicy = new BluetoothSinkAudioPolicy.Builder()
+                .setCallEstablishPolicy(BluetoothSinkAudioPolicy.POLICY_ALLOWED)
+                .setActiveDevicePolicyAfterConnection(BluetoothSinkAudioPolicy.POLICY_NOT_ALLOWED)
+                .setInBandRingtonePolicy(BluetoothSinkAudioPolicy.POLICY_ALLOWED)
+                .build();
+
+        mHeadsetClientStateMachine.setAudioPolicy(dummyAudioPolicy);
+        verify(mNativeInterface).sendAndroidAt(mTestDevice, "+ANDROID=1,1,2,1");
+    }
+
+    @Test
+    public void testDumpDoesNotCrash() {
+        mHeadsetClientStateMachine.dump(new StringBuilder());
+    }
+
+    @Test
+    public void testProcessDisconnectMessage_onDisconnectedState() {
+        mHeadsetClientStateMachine.sendMessage(HeadsetClientStateMachine.DISCONNECT);
+        TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper());
+        Assert.assertEquals(STATE_DISCONNECTED,
+                mHeadsetClientStateMachine.getConnectionState(mTestDevice));
+    }
+
+    @Test
+    public void testProcessConnectMessage_onDisconnectedState() {
+        doReturn(true).when(mNativeInterface).connect(any(BluetoothDevice.class));
+        sendMessageAndVerifyTransition(
+                mHeadsetClientStateMachine
+                        .obtainMessage(HeadsetClientStateMachine.CONNECT, mTestDevice),
+                HeadsetClientStateMachine.Connecting.class);
+    }
+
+    @Test
+    public void testStackEvent_toConnectingState_onDisconnectedState() {
+        allowConnection(true);
+        StackEvent event = new StackEvent(StackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED);
+        event.valueInt = HeadsetClientHalConstants.CONNECTION_STATE_CONNECTED;
+        event.device = mTestDevice;
+        sendMessageAndVerifyTransition(
+                mHeadsetClientStateMachine.obtainMessage(StackEvent.STACK_EVENT, event),
+                HeadsetClientStateMachine.Connecting.class);
+    }
+
+    @Test
+    public void testStackEvent_toConnectingState_disallowConnection_onDisconnectedState() {
+        allowConnection(false);
+        StackEvent event = new StackEvent(StackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED);
+        event.valueInt = HeadsetClientHalConstants.CONNECTION_STATE_CONNECTED;
+        event.device = mTestDevice;
+        sendMessageAndVerifyTransition(
+                mHeadsetClientStateMachine.obtainMessage(StackEvent.STACK_EVENT, event),
+                HeadsetClientStateMachine.Disconnected.class);
+    }
+
+    @Test
+    public void testProcessConnectMessage_onConnectingState() {
+        initToConnectingState();
+        mHeadsetClientStateMachine.sendMessage(HeadsetClientStateMachine.CONNECT);
+        TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper());
+        Assert.assertTrue(mHeadsetClientStateMachine.doesSuperHaveDeferredMessages(
+                HeadsetClientStateMachine.CONNECT));
+    }
+
+    @Test
+    public void testProcessStackEvent_ConnectionStateChanged_Disconnected_onConnectingState() {
+        initToConnectingState();
+        StackEvent event = new StackEvent(StackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED);
+        event.valueInt = HeadsetClientHalConstants.CONNECTION_STATE_DISCONNECTED;
+        event.device = mTestDevice;
+        sendMessageAndVerifyTransition(
+                mHeadsetClientStateMachine.obtainMessage(StackEvent.STACK_EVENT, event),
+                HeadsetClientStateMachine.Disconnected.class);
+        verify(mHeadsetService).updateInbandRinging(eq(mTestDevice), eq(false));
+    }
+
+    @Test
+    public void testProcessStackEvent_ConnectionStateChanged_Connected_onConnectingState() {
+        initToConnectingState();
+        StackEvent event = new StackEvent(StackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED);
+        event.valueInt = HeadsetClientHalConstants.CONNECTION_STATE_CONNECTED;
+        event.device = mTestDevice;
+        mHeadsetClientStateMachine.sendMessage(
+                mHeadsetClientStateMachine.obtainMessage(StackEvent.STACK_EVENT, event));
+        Assert.assertThat(mHeadsetClientStateMachine.getCurrentState(),
+                IsInstanceOf.instanceOf(HeadsetClientStateMachine.Connecting.class));
+    }
+
+    @Test
+    public void testProcessStackEvent_ConnectionStateChanged_Connecting_onConnectingState() {
+        initToConnectingState();
+        StackEvent event = new StackEvent(StackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED);
+        event.valueInt = HeadsetClientHalConstants.CONNECTION_STATE_CONNECTING;
+        event.device = mTestDevice;
+        mHeadsetClientStateMachine.sendMessage(
+                mHeadsetClientStateMachine.obtainMessage(StackEvent.STACK_EVENT, event));
+        Assert.assertThat(mHeadsetClientStateMachine.getCurrentState(),
+                IsInstanceOf.instanceOf(HeadsetClientStateMachine.Connecting.class));
+    }
+
+    @Test
+    public void testProcessStackEvent_Call_onConnectingState() {
+        initToConnectingState();
+        StackEvent event = new StackEvent(StackEvent.EVENT_TYPE_CALL);
+        event.valueInt = StackEvent.EVENT_TYPE_CALL;
+        event.device = mTestDevice;
+        mHeadsetClientStateMachine.sendMessage(
+                mHeadsetClientStateMachine.obtainMessage(StackEvent.STACK_EVENT, event));
+        TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper());
+        Assert.assertTrue(mHeadsetClientStateMachine.doesSuperHaveDeferredMessages(
+                StackEvent.STACK_EVENT));
+    }
+
+    @Test
+    public void testProcessStackEvent_CmdResultWithEmptyQueuedActions_onConnectingState() {
+        initToConnectingState();
+        StackEvent event = new StackEvent(StackEvent.EVENT_TYPE_CMD_RESULT);
+        event.valueInt = StackEvent.CMD_RESULT_TYPE_OK;
+        event.device = mTestDevice;
+        mHeadsetClientStateMachine.sendMessage(
+                mHeadsetClientStateMachine.obtainMessage(StackEvent.STACK_EVENT, event));
+        Assert.assertThat(mHeadsetClientStateMachine.getCurrentState(),
+                IsInstanceOf.instanceOf(HeadsetClientStateMachine.Connecting.class));
+    }
+
+    @Test
+    public void testProcessStackEvent_Unknown_onConnectingState() {
+        String atCommand = "+ANDROID: 1";
+
+        initToConnectingState();
+        StackEvent event = new StackEvent(StackEvent.EVENT_TYPE_UNKNOWN_EVENT);
+        event.valueString = atCommand;
+        event.device = mTestDevice;
+        mHeadsetClientStateMachine.sendMessage(
+                mHeadsetClientStateMachine.obtainMessage(StackEvent.STACK_EVENT, event));
+        TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper());
+        Assert.assertThat(mHeadsetClientStateMachine.getCurrentState(),
+                IsInstanceOf.instanceOf(HeadsetClientStateMachine.Connected.class));
+        verify(mHeadsetService).updateInbandRinging(eq(mTestDevice), eq(true));
+    }
+
+    @Test
+    public void testProcessConnectTimeoutMessage_onConnectingState() {
+        initToConnectingState();
+        Message msg = mHeadsetClientStateMachine
+                .obtainMessage(HeadsetClientStateMachine.CONNECTING_TIMEOUT);
+        sendMessageAndVerifyTransition(msg, HeadsetClientStateMachine.Disconnected.class);
+        verify(mHeadsetService).updateInbandRinging(eq(mTestDevice), eq(false));
+    }
+
+    @Test
+    public void testProcessConnectMessage_onConnectedState() {
+        initToConnectedState();
+        mHeadsetClientStateMachine.sendMessage(HeadsetClientStateMachine.CONNECT);
+        TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper());
+        Assert.assertThat(mHeadsetClientStateMachine.getCurrentState(),
+                IsInstanceOf.instanceOf(HeadsetClientStateMachine.Connected.class));
+    }
+
+    @Test
+    public void testProcessDisconnectMessage_onConnectedState() {
+        initToConnectedState();
+        mHeadsetClientStateMachine.sendMessage(HeadsetClientStateMachine.DISCONNECT, mTestDevice);
+        TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper());
+        verify(mNativeInterface).disconnect(any(BluetoothDevice.class));
+    }
+
+    @Test
+    public void testProcessConnectAudioMessage_onConnectedState() {
+        initToConnectedState();
+        mHeadsetClientStateMachine.sendMessage(HeadsetClientStateMachine.CONNECT_AUDIO);
+        TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper());
+        verify(mNativeInterface).connectAudio(any(BluetoothDevice.class));
+    }
+
+    @Test
+    public void testProcessDisconnectAudioMessage_onConnectedState() {
+        initToConnectedState();
+        mHeadsetClientStateMachine.sendMessage(HeadsetClientStateMachine.DISCONNECT_AUDIO);
+        TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper());
+        verify(mNativeInterface).disconnectAudio(any(BluetoothDevice.class));
+    }
+
+    @Test
+    public void testProcessVoiceRecognitionStartMessage_onConnectedState() {
+        initToConnectedState();
+        mHeadsetClientStateMachine.sendMessage(HeadsetClientStateMachine.VOICE_RECOGNITION_START);
+        TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper());
+        verify(mNativeInterface).startVoiceRecognition(any(BluetoothDevice.class));
+    }
+
+    @Test
+    public void testProcessDisconnectMessage_onAudioOnState() {
+        initToAudioOnState();
+        mHeadsetClientStateMachine.sendMessage(HeadsetClientStateMachine.DISCONNECT, mTestDevice);
+        TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper());
+        Assert.assertTrue(mHeadsetClientStateMachine.doesSuperHaveDeferredMessages(
+                HeadsetClientStateMachine.DISCONNECT));
+    }
+
+    @Test
+    public void testProcessDisconnectAudioMessage_onAudioOnState() {
+        initToAudioOnState();
+        mHeadsetClientStateMachine.sendMessage(HeadsetClientStateMachine.DISCONNECT_AUDIO,
+                mTestDevice);
+        TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper());
+        verify(mNativeInterface).disconnectAudio(any(BluetoothDevice.class));
+    }
+
+    @Test
+    public void testProcessHoldCall_onAudioOnState() {
+        initToAudioOnState();
+        HfpClientCall call = new HfpClientCall(mTestDevice, 0, HfpClientCall.CALL_STATE_ACTIVE,
+                "1", true, false, false);
+        mHeadsetClientStateMachine.mCalls.put(0, call);
+        int[] states = new int[1];
+        states[0] = HfpClientCall.CALL_STATE_ACTIVE;
+        mHeadsetClientStateMachine.sendMessage(HeadsetClientStateMachine.HOLD_CALL,
+                mTestDevice);
+        TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper());
+        verify(mNativeInterface).handleCallAction(any(BluetoothDevice.class), anyInt(), eq(0));
+    }
+
+    @Test
+    public void testProcessStackEvent_ConnectionStateChanged_onAudioOnState() {
+        initToAudioOnState();
+        Assert.assertThat(mHeadsetClientStateMachine.getCurrentState(),
+                IsInstanceOf.instanceOf(HeadsetClientStateMachine.AudioOn.class));
+        StackEvent event = new StackEvent(StackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED);
+        event.valueInt = HeadsetClientHalConstants.CONNECTION_STATE_DISCONNECTED;
+        event.device = mTestDevice;
+        mHeadsetClientStateMachine.sendMessage(
+                mHeadsetClientStateMachine.obtainMessage(StackEvent.STACK_EVENT, event));
+        TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper());
+        Assert.assertThat(mHeadsetClientStateMachine.getCurrentState(),
+                IsInstanceOf.instanceOf(HeadsetClientStateMachine.Disconnected.class));
+        verify(mHeadsetService).updateInbandRinging(eq(mTestDevice), eq(false));
+    }
+
+    @Test
+    public void testProcessStackEvent_AudioStateChanged_onAudioOnState() {
+        initToAudioOnState();
+        Assert.assertThat(mHeadsetClientStateMachine.getCurrentState(),
+                IsInstanceOf.instanceOf(HeadsetClientStateMachine.AudioOn.class));
+        StackEvent event = new StackEvent(StackEvent.EVENT_TYPE_AUDIO_STATE_CHANGED);
+        event.valueInt = HeadsetClientHalConstants.AUDIO_STATE_DISCONNECTED;
+        event.device = mTestDevice;
+        mHeadsetClientStateMachine.sendMessage(
+                mHeadsetClientStateMachine.obtainMessage(StackEvent.STACK_EVENT, event));
+        TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper());
+        Assert.assertThat(mHeadsetClientStateMachine.getCurrentState(),
+                IsInstanceOf.instanceOf(HeadsetClientStateMachine.Connected.class));
+    }
+
+    /**
+     * Allow/disallow connection to any device
+     *
+     * @param allow if true, connection is allowed
+     */
+    private void allowConnection(boolean allow) {
+        mHeadsetClientStateMachine.allowConnect = allow;
+    }
+
+    private void initToConnectingState() {
+        doReturn(true).when(mNativeInterface).connect(any(BluetoothDevice.class));
+        sendMessageAndVerifyTransition(
+                mHeadsetClientStateMachine
+                        .obtainMessage(HeadsetClientStateMachine.CONNECT, mTestDevice),
+                HeadsetClientStateMachine.Connecting.class);
+    }
+
+    private void initToConnectedState() {
+        String atCommand = "+ANDROID: 1";
+        initToConnectingState();
+        StackEvent event = new StackEvent(StackEvent.EVENT_TYPE_UNKNOWN_EVENT);
+        event.valueString = atCommand;
+        event.device = mTestDevice;
+        mHeadsetClientStateMachine.sendMessage(
+                mHeadsetClientStateMachine.obtainMessage(StackEvent.STACK_EVENT, event));
+        TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper());
+        Assert.assertThat(mHeadsetClientStateMachine.getCurrentState(),
+                IsInstanceOf.instanceOf(HeadsetClientStateMachine.Connected.class));
+        verify(mHeadsetService).updateInbandRinging(eq(mTestDevice), eq(true));
+    }
+
+    private void initToAudioOnState() {
+        mHeadsetClientStateMachine.setAudioRouteAllowed(true);
+        initToConnectedState();
+        StackEvent event = new StackEvent(StackEvent.EVENT_TYPE_AUDIO_STATE_CHANGED);
+        event.valueInt = HeadsetClientHalConstants.AUDIO_STATE_CONNECTED;
+        event.device = mTestDevice;
+        mHeadsetClientStateMachine.sendMessage(
+                mHeadsetClientStateMachine.obtainMessage(StackEvent.STACK_EVENT, event));
+        TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper());
+        Assert.assertThat(mHeadsetClientStateMachine.getCurrentState(),
+                IsInstanceOf.instanceOf(HeadsetClientStateMachine.AudioOn.class));
+    }
+
+    private <T> void sendMessageAndVerifyTransition(Message msg, Class<T> type) {
+        Mockito.clearInvocations(mHeadsetClientService);
+        mHeadsetClientStateMachine.sendMessage(msg);
+        // Verify that one connection state broadcast is executed
+        verify(mHeadsetClientService, timeout(TIMEOUT_MS)).sendBroadcastMultiplePermissions(
+                any(Intent.class), any(String[].class), any(BroadcastOptions.class)
+        );
+        Assert.assertThat(mHeadsetClientStateMachine.getCurrentState(),
+                IsInstanceOf.instanceOf(type));
+    }
+
+    public static class TestHeadsetClientStateMachine extends HeadsetClientStateMachine {
+
+        Boolean allowConnect = null;
+
+        TestHeadsetClientStateMachine(HeadsetClientService context, HeadsetService headsetService,
+                Looper looper, NativeInterface nativeInterface) {
+            super(context, headsetService, looper, nativeInterface);
+        }
+
+        public boolean doesSuperHaveDeferredMessages(int what) {
+            return super.hasDeferredMessages(what);
+        }
+
+        @Override
+        public boolean okToConnect(BluetoothDevice device) {
+            return allowConnect != null ? allowConnect : super.okToConnect(device);
+        }
+    }
 }
diff --git a/android/app/tests/unit/src/com/android/bluetooth/le_audio/LeAudioServiceTest.java b/android/app/tests/unit/src/com/android/bluetooth/le_audio/LeAudioServiceTest.java
index d2c7cd1..39c3cd7 100644
--- a/android/app/tests/unit/src/com/android/bluetooth/le_audio/LeAudioServiceTest.java
+++ b/android/app/tests/unit/src/com/android/bluetooth/le_audio/LeAudioServiceTest.java
@@ -643,6 +643,9 @@
         doReturn(true).when(mNativeInterface).connectLeAudio(any(BluetoothDevice.class));
         doReturn(true).when(mNativeInterface).disconnectLeAudio(any(BluetoothDevice.class));
 
+        // Create device descriptor with connect request
+        assertWithMessage("Connect failed").that(mService.connect(mLeftDevice)).isTrue();
+
         // Le Audio stack event: CONNECTION_STATE_CONNECTING - state machine should be created
         generateConnectionMessageFromNative(mLeftDevice, BluetoothProfile.STATE_CONNECTING,
                 BluetoothProfile.STATE_DISCONNECTED);
@@ -659,9 +662,15 @@
         mService.bondStateChanged(mLeftDevice, BluetoothDevice.BOND_NONE);
         assertThat(mService.getDevices().contains(mLeftDevice)).isFalse();
 
+        // Remove bond will remove also device descriptor. Device has to be connected again
+        assertWithMessage("Connect failed").that(mService.connect(mLeftDevice)).isTrue();
+        verifyConnectionStateIntent(LeAudioStateMachine.sConnectTimeoutMs * 2,
+                mLeftDevice, BluetoothProfile.STATE_CONNECTING,
+                BluetoothProfile.STATE_DISCONNECTED);
+
         // stack event: CONNECTION_STATE_CONNECTED - state machine should be created
         generateConnectionMessageFromNative(mLeftDevice, BluetoothProfile.STATE_CONNECTED,
-                BluetoothProfile.STATE_DISCONNECTED);
+                BluetoothProfile.STATE_CONNECTING);
         assertThat(BluetoothProfile.STATE_CONNECTED)
                 .isEqualTo(mService.getConnectionState(mLeftDevice));
         assertThat(mService.getDevices().contains(mLeftDevice)).isTrue();
@@ -710,6 +719,9 @@
         doReturn(true).when(mNativeInterface).connectLeAudio(any(BluetoothDevice.class));
         doReturn(true).when(mNativeInterface).disconnectLeAudio(any(BluetoothDevice.class));
 
+        // Create device descriptor with connect request
+        assertWithMessage("Connect failed").that(mService.connect(mLeftDevice)).isTrue();
+
         // LeAudio stack event: CONNECTION_STATE_CONNECTING - state machine should be created
         generateConnectionMessageFromNative(mLeftDevice, BluetoothProfile.STATE_CONNECTING,
                 BluetoothProfile.STATE_DISCONNECTED);
@@ -777,6 +789,9 @@
         doReturn(true).when(mNativeInterface).connectLeAudio(any(BluetoothDevice.class));
         doReturn(true).when(mNativeInterface).disconnectLeAudio(any(BluetoothDevice.class));
 
+        // Create device descriptor with connect request
+        assertWithMessage("Connect failed").that(mService.connect(mLeftDevice)).isTrue();
+
         // LeAudio stack event: CONNECTION_STATE_CONNECTING - state machine should be created
         generateConnectionMessageFromNative(mLeftDevice, BluetoothProfile.STATE_CONNECTING,
                 BluetoothProfile.STATE_DISCONNECTED);
@@ -1514,6 +1529,9 @@
 
     @Test
     public void testGetAudioLocation() {
+        doReturn(true).when(mNativeInterface).connectLeAudio(any(BluetoothDevice.class));
+        connectTestDevice(mSingleDevice, testGroupId);
+
         assertThat(mService.getAudioLocation(null))
                 .isEqualTo(BluetoothLeAudio.AUDIO_LOCATION_INVALID);
 
@@ -1619,6 +1637,10 @@
     public void testAuthorizeMcpServiceOnBluetoothEnableAndNodeRemoval() {
         int groupId = 1;
 
+        doReturn(true).when(mNativeInterface).connectLeAudio(any(BluetoothDevice.class));
+        connectTestDevice(mLeftDevice, groupId);
+        connectTestDevice(mRightDevice, groupId);
+
         generateGroupNodeAdded(mLeftDevice, groupId);
         generateGroupNodeAdded(mRightDevice, groupId);
 
diff --git a/android/app/tests/unit/src/com/android/bluetooth/map/BluetoothMapContentObserverTest.java b/android/app/tests/unit/src/com/android/bluetooth/map/BluetoothMapContentObserverTest.java
index d57aca6..b0f6bbe 100644
--- a/android/app/tests/unit/src/com/android/bluetooth/map/BluetoothMapContentObserverTest.java
+++ b/android/app/tests/unit/src/com/android/bluetooth/map/BluetoothMapContentObserverTest.java
@@ -18,9 +18,11 @@
 
 import static org.mockito.Mockito.*;
 
+import android.app.Activity;
 import android.content.ContentProviderClient;
 import android.content.ContentValues;
 import android.content.Context;
+import android.content.Intent;
 import android.database.Cursor;
 import android.database.MatrixCursor;
 import android.database.sqlite.SQLiteException;
@@ -29,6 +31,7 @@
 import android.os.Looper;
 import android.os.RemoteException;
 import android.os.UserManager;
+import android.provider.ContactsContract;
 import android.provider.Telephony;
 import android.provider.Telephony.Mms;
 import android.provider.Telephony.Sms;
@@ -43,6 +46,7 @@
 import com.android.bluetooth.SignedLongLong;
 import com.android.bluetooth.map.BluetoothMapUtils.TYPE;
 import com.android.bluetooth.mapapi.BluetoothMapContract;
+import com.android.bluetooth.mapapi.BluetoothMapContract.MessageColumns;
 import com.android.obex.ResponseCodes;
 
 import com.google.android.mms.pdu.PduHeaders;
@@ -60,9 +64,11 @@
 
 import java.io.IOException;
 import java.text.SimpleDateFormat;
+import java.util.Calendar;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Map;
+import java.util.concurrent.TimeUnit;
 
 @MediumTest
 @RunWith(AndroidJUnit4.class)
@@ -72,7 +78,7 @@
     static final int TEST_ID = 1;
     static final long TEST_HANDLE_ONE = 1;
     static final long TEST_HANDLE_TWO = 2;
-    static final String TEST_URI_STR = "test_uri_str";
+    static final String TEST_URI_STR = "http://www.google.com";
     static final int TEST_STATUS_VALUE = 1;
     static final int TEST_THREAD_ID = 1;
     static final long TEST_OLD_THREAD_ID = 2;
@@ -85,12 +91,32 @@
     static final long TEST_OLD_FOLDER_ID = 6;
     static final int TEST_READ_FLAG_ONE = 1;
     static final int TEST_READ_FLAG_ZERO = 0;
-    static final long TEST_DATE = 1;
+    static final long TEST_DATE_MS = Calendar.getInstance().getTimeInMillis();
+    static final long TEST_DATE_SEC = TimeUnit.MILLISECONDS.toSeconds(TEST_DATE_MS);
     static final String TEST_SUBJECT = "subject";
     static final int TEST_MMS_MTYPE = 1;
     static final int TEST_MMS_TYPE_ALL = Telephony.BaseMmsColumns.MESSAGE_BOX_ALL;
     static final int TEST_MMS_TYPE_INBOX = Telephony.BaseMmsColumns.MESSAGE_BOX_INBOX;
+    static final int TEST_SMS_TYPE_ALL = Telephony.TextBasedSmsColumns.MESSAGE_TYPE_ALL;
+    static final int TEST_SMS_TYPE_INBOX = Telephony.BaseMmsColumns.MESSAGE_BOX_INBOX;
     static final Uri TEST_URI = Mms.CONTENT_URI;
+    static final String TEST_AUTHORITY = "test_authority";
+
+    static final long TEST_CONVO_ID = 1;
+    static final String TEST_NAME = "col_name";
+    static final String TEST_DISPLAY_NAME = "col_nickname";
+    static final String TEST_BT_UID = "1111";
+    static final int TEST_CHAT_STATE = 1;
+    static final int TEST_CHAT_STATE_DIFFERENT = 2;
+    static final String TEST_UCI = "col_uci";
+    static final String TEST_UCI_DIFFERENT = "col_uci_different";
+    static final long TEST_LAST_ACTIVITY = 1;
+    static final int TEST_PRESENCE_STATE = 1;
+    static final int TEST_PRESENCE_STATE_DIFFERENT = 2;
+    static final String TEST_STATUS_TEXT = "col_status_text";
+    static final String TEST_STATUS_TEXT_DIFFERENT = "col_status_text_different";
+    static final int TEST_PRIORITY = 1;
+    static final int TEST_LAST_ONLINE = 1;
 
     @Mock
     private BluetoothMnsObexClient mClient;
@@ -106,6 +132,8 @@
     private ContentProviderClient mProviderClient;
     @Mock
     private BluetoothMapAccountItem mItem;
+    @Mock
+    private Intent mIntent;
     @Spy
     private BluetoothMethodProxy mMapMethodProxy = BluetoothMethodProxy.getInstance();
 
@@ -801,21 +829,75 @@
     }
 
     @Test
-    public void initContactsList() throws Exception {
-        long convoId = 1;
-        String name = "col_name";
-        String displayName = "col_nickname";
-        String btUid = "1111";
-        int chatState = 1;
-        String uci = "col_uci";
-        long lastActivity = 1;
-        int presenceState = 1;
-        String statusText = "col_status_text";
-        int priority = 1;
-        int lastOnline = 1;
+    public void initMsgList_withMsgSms() throws Exception {
+        MatrixCursor cursor = new MatrixCursor(new String[] {Sms._ID, Sms.TYPE, Sms.THREAD_ID,
+                Sms.READ});
+        cursor.addRow(new Object[] {(long) TEST_ID, TEST_SMS_TYPE_ALL, TEST_THREAD_ID,
+                TEST_READ_FLAG_ONE});
+        doReturn(cursor).when(mMapMethodProxy).contentResolverQuery(any(), any(),
+                eq(BluetoothMapContentObserver.SMS_PROJECTION_SHORT), any(), any(), any());
+        cursor.moveToFirst();
+        Map<Long, BluetoothMapContentObserver.Msg> map = new HashMap<>();
+        mObserver.setMsgListMsg(map, true);
 
+        mObserver.initMsgList();
+
+        BluetoothMapContentObserver.Msg msg = mObserver.getMsgListSms().get((long) TEST_ID);
+        Assert.assertEquals(msg.id, TEST_ID);
+        Assert.assertEquals(msg.type, TEST_SMS_TYPE_ALL);
+        Assert.assertEquals(msg.threadId, TEST_THREAD_ID);
+        Assert.assertEquals(msg.flagRead, TEST_READ_FLAG_ONE);
+    }
+
+    @Test
+    public void initMsgList_withMsgMms() throws Exception {
+        MatrixCursor cursor = new MatrixCursor(new String[] {Mms._ID, Mms.MESSAGE_BOX,
+                Mms.THREAD_ID, Mms.READ});
+        cursor.addRow(new Object[] {(long) TEST_ID, TEST_MMS_TYPE_ALL, TEST_THREAD_ID,
+                TEST_READ_FLAG_ZERO});
+        doReturn(null).when(mMapMethodProxy).contentResolverQuery(any(), any(),
+                eq(BluetoothMapContentObserver.SMS_PROJECTION_SHORT), any(), any(), any());
+        doReturn(cursor).when(mMapMethodProxy).contentResolverQuery(any(), any(),
+                eq(BluetoothMapContentObserver.MMS_PROJECTION_SHORT), any(), any(), any());
+        cursor.moveToFirst();
+        Map<Long, BluetoothMapContentObserver.Msg> map = new HashMap<>();
+        mObserver.setMsgListMsg(map, true);
+
+        mObserver.initMsgList();
+
+        BluetoothMapContentObserver.Msg msg = mObserver.getMsgListMms().get((long) TEST_ID);
+        Assert.assertEquals(msg.id, TEST_ID);
+        Assert.assertEquals(msg.type, TEST_MMS_TYPE_ALL);
+        Assert.assertEquals(msg.threadId, TEST_THREAD_ID);
+        Assert.assertEquals(msg.flagRead, TEST_READ_FLAG_ZERO);
+    }
+
+    @Test
+    public void initMsgList_withMsg() throws Exception {
+        MatrixCursor cursor = new MatrixCursor(new String[] {MessageColumns._ID,
+                MessageColumns.FOLDER_ID, MessageColumns.FLAG_READ});
+        cursor.addRow(new Object[] {(long) TEST_ID, TEST_INBOX_FOLDER_ID, TEST_READ_FLAG_ONE});
+        doReturn(null).when(mMapMethodProxy).contentResolverQuery(any(), any(),
+                eq(BluetoothMapContentObserver.SMS_PROJECTION_SHORT), any(), any(), any());
+        doReturn(null).when(mMapMethodProxy).contentResolverQuery(any(), any(),
+                eq(BluetoothMapContentObserver.MMS_PROJECTION_SHORT), any(), any(), any());
+        when(mProviderClient.query(any(), any(), any(), any(), any())).thenReturn(cursor);
+        cursor.moveToFirst();
+        Map<Long, BluetoothMapContentObserver.Msg> map = new HashMap<>();
+        mObserver.setMsgListMsg(map, true);
+
+        mObserver.initMsgList();
+
+        BluetoothMapContentObserver.Msg msg = mObserver.getMsgListMsg().get((long) TEST_ID);
+        Assert.assertEquals(msg.id, TEST_ID);
+        Assert.assertEquals(msg.folderId, TEST_INBOX_FOLDER_ID);
+        Assert.assertEquals(msg.flagRead, TEST_READ_FLAG_ONE);
+    }
+
+    @Test
+    public void initContactsList() throws Exception {
         MatrixCursor cursor = new MatrixCursor(
-                new String[]{BluetoothMapContract.ConvoContactColumns.CONVO_ID,
+                new String[] {BluetoothMapContract.ConvoContactColumns.CONVO_ID,
                         BluetoothMapContract.ConvoContactColumns.NAME,
                         BluetoothMapContract.ConvoContactColumns.NICKNAME,
                         BluetoothMapContract.ConvoContactColumns.X_BT_UID,
@@ -826,8 +908,9 @@
                         BluetoothMapContract.ConvoContactColumns.STATUS_TEXT,
                         BluetoothMapContract.ConvoContactColumns.PRIORITY,
                         BluetoothMapContract.ConvoContactColumns.LAST_ONLINE});
-        cursor.addRow(new Object[] {convoId, name, displayName, btUid, chatState, uci, lastActivity,
-        presenceState, statusText, priority, lastOnline});
+        cursor.addRow(new Object[] {TEST_CONVO_ID, TEST_NAME, TEST_DISPLAY_NAME, TEST_BT_UID,
+                TEST_CHAT_STATE, TEST_UCI, TEST_LAST_ACTIVITY, TEST_PRESENCE_STATE,
+                TEST_STATUS_TEXT, TEST_PRIORITY, TEST_LAST_ONLINE});
         doReturn(cursor).when(mMapMethodProxy).contentResolverQuery(any(), any(), any(), any(),
                 any(), any());
 
@@ -837,22 +920,23 @@
         Map<String, BluetoothMapConvoContactElement> map = new HashMap<>();
         mObserver.setContactList(map, true);
         mObserver.initContactsList();
-        BluetoothMapConvoContactElement contactElement = mObserver.getContactList().get(uci);
+        BluetoothMapConvoContactElement contactElement = mObserver.getContactList().get(TEST_UCI);
 
         final SimpleDateFormat format = new SimpleDateFormat("yyyyMMdd'T'HHmmss");
-        Assert.assertEquals(contactElement.getContactId(), uci);
-        Assert.assertEquals(contactElement.getName(), name);
-        Assert.assertEquals(contactElement.getDisplayName(), displayName);
-        Assert.assertEquals(contactElement.getBtUid(), btUid);
-        Assert.assertEquals(contactElement.getChatState(), chatState);
-        Assert.assertEquals(contactElement.getPresenceStatus(), statusText);
-        Assert.assertEquals(contactElement.getPresenceAvailability(), presenceState);
-        Assert.assertEquals(contactElement.getLastActivityString(), format.format(lastActivity));
-        Assert.assertEquals(contactElement.getPriority(), priority);
+        Assert.assertEquals(contactElement.getContactId(), TEST_UCI);
+        Assert.assertEquals(contactElement.getName(), TEST_NAME);
+        Assert.assertEquals(contactElement.getDisplayName(), TEST_DISPLAY_NAME);
+        Assert.assertEquals(contactElement.getBtUid(), TEST_BT_UID);
+        Assert.assertEquals(contactElement.getChatState(), TEST_CHAT_STATE);
+        Assert.assertEquals(contactElement.getPresenceStatus(), TEST_STATUS_TEXT);
+        Assert.assertEquals(contactElement.getPresenceAvailability(), TEST_PRESENCE_STATE);
+        Assert.assertEquals(contactElement.getLastActivityString(), format.format(
+                TEST_LAST_ACTIVITY));
+        Assert.assertEquals(contactElement.getPriority(), TEST_PRIORITY);
     }
 
     @Test
-    public void handleMsgListChangesMsg_withNonExistingMessage_andVersionEleven() throws Exception {
+    public void handleMsgListChangesMsg_withNonExistingMessage_andVersion11() throws Exception {
         MatrixCursor cursor = new MatrixCursor(new String[] {
                 BluetoothMapContract.MessageColumns._ID,
                 BluetoothMapContract.MessageColumns.FOLDER_ID,
@@ -862,7 +946,7 @@
                 BluetoothMapContract.MessageColumns.FROM_LIST,
                 BluetoothMapContract.MessageColumns.FLAG_HIGH_PRIORITY});
         cursor.addRow(new Object[] {TEST_HANDLE_ONE, TEST_INBOX_FOLDER_ID, TEST_READ_FLAG_ONE,
-                TEST_DATE, TEST_SUBJECT, TEST_ADDRESS, 1});
+                TEST_DATE_MS, TEST_SUBJECT, TEST_ADDRESS, 1});
         when(mProviderClient.query(any(), any(), any(), any(), any())).thenReturn(cursor);
 
         Map<Long, BluetoothMapContentObserver.Msg> map = new HashMap<>();
@@ -888,7 +972,7 @@
     }
 
     @Test
-    public void handleMsgListChangesMsg_withNonExistingMessage_andVersionTwelve() throws Exception {
+    public void handleMsgListChangesMsg_withNonExistingMessage_andVersion12() throws Exception {
         MatrixCursor cursor = new MatrixCursor(new String[] {
                 BluetoothMapContract.MessageColumns._ID,
                 BluetoothMapContract.MessageColumns.FOLDER_ID,
@@ -900,7 +984,7 @@
                 BluetoothMapContract.MessageColumns.THREAD_ID,
                 BluetoothMapContract.MessageColumns.THREAD_NAME});
         cursor.addRow(new Object[] {TEST_HANDLE_ONE, TEST_INBOX_FOLDER_ID, TEST_READ_FLAG_ONE,
-                TEST_DATE, TEST_SUBJECT, TEST_ADDRESS, 1, 1, "threadName"});
+                TEST_DATE_MS, TEST_SUBJECT, TEST_ADDRESS, 1, 1, "threadName"});
         when(mProviderClient.query(any(), any(), any(), any(), any())).thenReturn(cursor);
 
         Map<Long, BluetoothMapContentObserver.Msg> map = new HashMap<>();
@@ -924,7 +1008,7 @@
     }
 
     @Test
-    public void handleMsgListChangesMsg_withNonExistingMessage_andVersionTen() throws Exception {
+    public void handleMsgListChangesMsg_withNonExistingMessage_andVersion10() throws Exception {
         MatrixCursor cursor = new MatrixCursor(new String[] {
                 BluetoothMapContract.MessageColumns._ID,
                 BluetoothMapContract.MessageColumns.FOLDER_ID,
@@ -1088,12 +1172,12 @@
     }
 
     @Test
-    public void handleMsgListChangesMms_withNonExistingMessage_andVersionEleven() {
+    public void handleMsgListChangesMms_withNonExistingMessage_andVersion11() {
         MatrixCursor cursor = new MatrixCursor(new String[] {Mms._ID, Mms.MESSAGE_BOX,
                 Mms.MESSAGE_TYPE, Mms.THREAD_ID, Mms.READ, Mms.DATE, Mms.SUBJECT,
-        Mms.PRIORITY, Mms.Addr.ADDRESS});
+                Mms.PRIORITY, Mms.Addr.ADDRESS});
         cursor.addRow(new Object[] {TEST_HANDLE_ONE, TEST_MMS_TYPE_ALL, TEST_MMS_MTYPE,
-                TEST_THREAD_ID, TEST_READ_FLAG_ONE, TEST_DATE, TEST_SUBJECT,
+                TEST_THREAD_ID, TEST_READ_FLAG_ONE, TEST_DATE_SEC, TEST_SUBJECT,
                 PduHeaders.PRIORITY_HIGH, null});
         doReturn(cursor).when(mMapMethodProxy).contentResolverQuery(any(), any(), any(), any(),
                 any(), any());
@@ -1118,12 +1202,12 @@
     }
 
     @Test
-    public void handleMsgListChangesMms_withNonExistingMessage_andVersionTwelve() {
+    public void handleMsgListChangesMms_withNonExistingMessage_andVersion12() {
         MatrixCursor cursor = new MatrixCursor(new String[] {Mms._ID, Mms.MESSAGE_BOX,
                 Mms.MESSAGE_TYPE, Mms.THREAD_ID, Mms.READ, Mms.DATE, Mms.SUBJECT,
                 Mms.PRIORITY, Mms.Addr.ADDRESS});
         cursor.addRow(new Object[] {TEST_HANDLE_ONE, TEST_MMS_TYPE_ALL, TEST_MMS_MTYPE,
-                TEST_THREAD_ID, TEST_READ_FLAG_ONE, TEST_DATE, TEST_SUBJECT,
+                TEST_THREAD_ID, TEST_READ_FLAG_ONE, TEST_DATE_SEC, TEST_SUBJECT,
                 PduHeaders.PRIORITY_HIGH, null});
         doReturn(cursor).when(mMapMethodProxy).contentResolverQuery(any(), any(), any(), any(),
                 any(), any());
@@ -1148,7 +1232,37 @@
     }
 
     @Test
-    public void handleMsgListChangesMms_withNonExistingMessage_andVersionTen() {
+    public void handleMsgListChangesMms_withNonExistingOldMessage_andVersion12() {
+        Calendar cal = Calendar.getInstance();
+        cal.add(Calendar.YEAR, -1);
+        cal.add(Calendar.DATE, -1);
+        long timestampSec = TimeUnit.MILLISECONDS.toSeconds(cal.getTimeInMillis());
+
+        MatrixCursor cursor = new MatrixCursor(new String[] {Mms._ID, Mms.MESSAGE_BOX,
+            Mms.MESSAGE_TYPE, Mms.THREAD_ID, Mms.READ, Mms.DATE, Mms.SUBJECT,
+            Mms.PRIORITY, Mms.Addr.ADDRESS});
+        cursor.addRow(new Object[] {TEST_HANDLE_ONE, TEST_MMS_TYPE_ALL, TEST_MMS_MTYPE,
+            TEST_THREAD_ID, TEST_READ_FLAG_ONE, timestampSec, TEST_SUBJECT,
+            PduHeaders.PRIORITY_HIGH, null});
+        doReturn(cursor).when(mMapMethodProxy).contentResolverQuery(any(), any(), any(), any(),
+            any(), any());
+
+        Map<Long, BluetoothMapContentObserver.Msg> map = new HashMap<>();
+        // Giving a different handle for msg below and cursor above makes handleMsgListChangesMms()
+        // function for a non-existing message
+        BluetoothMapContentObserver.Msg msg = new BluetoothMapContentObserver.Msg(TEST_HANDLE_TWO,
+            TEST_INBOX_FOLDER_ID, TEST_READ_FLAG_ONE);
+        map.put(TEST_HANDLE_TWO, msg);
+        mObserver.setMsgListMms(map, true);
+        mObserver.mMapEventReportVersion = BluetoothMapUtils.MAP_EVENT_REPORT_V12;
+
+        mObserver.handleMsgListChangesMms();
+
+        Assert.assertEquals(mObserver.getMsgListMms().get(TEST_HANDLE_ONE), null);
+    }
+
+    @Test
+    public void handleMsgListChangesMms_withNonExistingMessage_andVersion10() {
         MatrixCursor cursor = new MatrixCursor(new String[] {Mms._ID, Mms.MESSAGE_BOX,
                 Mms.MESSAGE_TYPE, Mms.THREAD_ID, Mms.READ});
         cursor.addRow(new Object[] {TEST_HANDLE_ONE, TEST_MMS_TYPE_ALL, TEST_MMS_MTYPE,
@@ -1292,6 +1406,539 @@
                 TEST_READ_FLAG_ONE);
     }
 
+    @Test
+    public void handleMsgListChangesSms_withNonExistingMessage_andVersion11() {
+        MatrixCursor cursor = new MatrixCursor(new String[] {Sms._ID, Sms.TYPE, Sms.THREAD_ID,
+                Sms.READ, Sms.DATE, Sms.BODY, Sms.ADDRESS, ContactsContract.Contacts.DISPLAY_NAME});
+        cursor.addRow(new Object[] {TEST_HANDLE_ONE, TEST_SMS_TYPE_INBOX, TEST_THREAD_ID,
+                TEST_READ_FLAG_ONE, TEST_DATE_MS, TEST_SUBJECT, TEST_ADDRESS, null});
+        doReturn(cursor).when(mMapMethodProxy).contentResolverQuery(any(), any(), any(), any(),
+                any(), any());
+
+        Map<Long, BluetoothMapContentObserver.Msg> map = new HashMap<>();
+        // Giving a different handle for msg below and cursor above makes handleMsgListChangesSms()
+        // function for a non-existing message
+        BluetoothMapContentObserver.Msg msg = new BluetoothMapContentObserver.Msg(TEST_HANDLE_TWO,
+                TEST_SMS_TYPE_ALL, TEST_READ_FLAG_ONE);
+        map.put(TEST_HANDLE_TWO, msg);
+        mObserver.setMsgListSms(map, true);
+        mObserver.mMapEventReportVersion = BluetoothMapUtils.MAP_EVENT_REPORT_V11;
+
+        mObserver.handleMsgListChangesSms();
+
+        Assert.assertEquals(mObserver.getMsgListSms().get(TEST_HANDLE_ONE).id, TEST_HANDLE_ONE);
+        Assert.assertEquals(mObserver.getMsgListSms().get(TEST_HANDLE_ONE).type,
+                TEST_SMS_TYPE_INBOX);
+        Assert.assertEquals(mObserver.getMsgListSms().get(TEST_HANDLE_ONE).threadId,
+                TEST_THREAD_ID);
+        Assert.assertEquals(mObserver.getMsgListSms().get(TEST_HANDLE_ONE).flagRead,
+                TEST_READ_FLAG_ONE);
+    }
+
+    @Test
+    public void handleMsgListChangesSms_withNonExistingMessage_andVersion12() {
+        MatrixCursor cursor = new MatrixCursor(new String[] {Sms._ID, Sms.TYPE, Sms.THREAD_ID,
+                Sms.READ, Sms.DATE, Sms.BODY, Sms.ADDRESS});
+        cursor.addRow(new Object[] {TEST_HANDLE_ONE, TEST_SMS_TYPE_ALL, TEST_THREAD_ID,
+                TEST_READ_FLAG_ONE, TEST_DATE_MS, "", null});
+        doReturn(cursor).when(mMapMethodProxy).contentResolverQuery(any(), any(), any(), any(),
+                any(), any());
+
+        Map<Long, BluetoothMapContentObserver.Msg> map = new HashMap<>();
+        // Giving a different handle for msg below and cursor above makes handleMsgListChangesSms()
+        // function for a non-existing message
+        BluetoothMapContentObserver.Msg msg = new BluetoothMapContentObserver.Msg(TEST_HANDLE_TWO,
+                TEST_SMS_TYPE_INBOX, TEST_READ_FLAG_ONE);
+        map.put(TEST_HANDLE_TWO, msg);
+        mObserver.setMsgListSms(map, true);
+        mObserver.mMapEventReportVersion = BluetoothMapUtils.MAP_EVENT_REPORT_V12;
+
+        mObserver.handleMsgListChangesSms();
+
+        Assert.assertEquals(mObserver.getMsgListSms().get(TEST_HANDLE_ONE).id, TEST_HANDLE_ONE);
+        Assert.assertEquals(mObserver.getMsgListSms().get(TEST_HANDLE_ONE).type,
+                TEST_SMS_TYPE_ALL);
+        Assert.assertEquals(mObserver.getMsgListSms().get(TEST_HANDLE_ONE).threadId,
+                TEST_THREAD_ID);
+        Assert.assertEquals(mObserver.getMsgListSms().get(TEST_HANDLE_ONE).flagRead,
+                TEST_READ_FLAG_ONE);
+    }
+
+    @Test
+    public void handleMsgListChangesSms_withNonExistingOldMessage_andVersion12() {
+        Calendar cal = Calendar.getInstance();
+        cal.add(Calendar.YEAR, -1);
+        cal.add(Calendar.DATE, -1);
+
+        MatrixCursor cursor = new MatrixCursor(new String[] {Sms._ID, Sms.TYPE, Sms.THREAD_ID,
+            Sms.READ, Sms.DATE, Sms.BODY, Sms.ADDRESS});
+        cursor.addRow(new Object[] {TEST_HANDLE_ONE, TEST_SMS_TYPE_ALL, TEST_THREAD_ID,
+            TEST_READ_FLAG_ONE, cal.getTimeInMillis(), "", null});
+        doReturn(cursor).when(mMapMethodProxy).contentResolverQuery(any(), any(), any(), any(),
+            any(), any());
+
+        Map<Long, BluetoothMapContentObserver.Msg> map = new HashMap<>();
+        // Giving a different handle for msg below and cursor above makes handleMsgListChangesMms()
+        // function for a non-existing message
+        BluetoothMapContentObserver.Msg msg = new BluetoothMapContentObserver.Msg(TEST_HANDLE_TWO,
+            TEST_SMS_TYPE_INBOX, TEST_READ_FLAG_ONE);
+        map.put(TEST_HANDLE_TWO, msg);
+        mObserver.setMsgListSms(map, true);
+        mObserver.mMapEventReportVersion = BluetoothMapUtils.MAP_EVENT_REPORT_V12;
+
+        mObserver.handleMsgListChangesSms();
+
+        Assert.assertEquals(mObserver.getMsgListSms().get(TEST_HANDLE_ONE), null);
+    }
+
+    @Test
+    public void handleMsgListChangesSms_withNonExistingMessage_andVersion10() {
+        MatrixCursor cursor = new MatrixCursor(new String[] {Sms._ID, Sms.TYPE, Sms.THREAD_ID,
+                Sms.READ});
+        cursor.addRow(new Object[] {TEST_HANDLE_ONE, TEST_SMS_TYPE_ALL, TEST_THREAD_ID,
+                TEST_READ_FLAG_ONE});
+        doReturn(cursor).when(mMapMethodProxy).contentResolverQuery(any(), any(), any(), any(),
+                any(), any());
+
+        Map<Long, BluetoothMapContentObserver.Msg> map = new HashMap<>();
+        // Giving a different handle for msg below and cursor above makes handleMsgListChangesSms()
+        // function for a non-existing message
+        BluetoothMapContentObserver.Msg msg = new BluetoothMapContentObserver.Msg(TEST_HANDLE_TWO,
+                TEST_SMS_TYPE_INBOX, TEST_READ_FLAG_ONE);
+        map.put(TEST_HANDLE_TWO, msg);
+        mObserver.setMsgListSms(map, true);
+        mObserver.mMapEventReportVersion = BluetoothMapUtils.MAP_EVENT_REPORT_V10;
+
+        mObserver.handleMsgListChangesSms();
+
+        Assert.assertEquals(mObserver.getMsgListSms().get(TEST_HANDLE_ONE).id, TEST_HANDLE_ONE);
+        Assert.assertEquals(mObserver.getMsgListSms().get(TEST_HANDLE_ONE).type,
+                TEST_SMS_TYPE_ALL);
+        Assert.assertEquals(mObserver.getMsgListSms().get(TEST_HANDLE_ONE).threadId,
+                TEST_THREAD_ID);
+        Assert.assertEquals(mObserver.getMsgListSms().get(TEST_HANDLE_ONE).flagRead,
+                TEST_READ_FLAG_ONE);
+    }
+
+    @Test
+    public void handleMsgListChangesSms_withExistingMessage_withNonEqualType() {
+        MatrixCursor cursor = new MatrixCursor(new String[] {Sms._ID, Sms.TYPE, Sms.THREAD_ID,
+                Sms.READ});
+        cursor.addRow(new Object[] {TEST_HANDLE_ONE, TEST_SMS_TYPE_ALL, TEST_THREAD_ID,
+                TEST_READ_FLAG_ONE});
+        doReturn(cursor).when(mMapMethodProxy).contentResolverQuery(any(), any(), any(), any(),
+                any(), any());
+
+        Map<Long, BluetoothMapContentObserver.Msg> map = new HashMap<>();
+        // Giving the same handle for msg below and cursor above makes handleMsgListChangesSms()
+        // function for an existing message
+        BluetoothMapContentObserver.Msg msg = new BluetoothMapContentObserver.Msg(TEST_HANDLE_ONE,
+                TEST_SMS_TYPE_INBOX, TEST_THREAD_ID, TEST_READ_FLAG_ZERO);
+        map.put(TEST_HANDLE_ONE, msg);
+        mObserver.setMsgListSms(map, true);
+        mObserver.mMapEventReportVersion = BluetoothMapUtils.MAP_EVENT_REPORT_V12;
+
+        mObserver.handleMsgListChangesSms();
+
+        Assert.assertEquals(mObserver.getMsgListSms().get(TEST_HANDLE_ONE).id, TEST_HANDLE_ONE);
+        Assert.assertEquals(mObserver.getMsgListSms().get(TEST_HANDLE_ONE).type,
+                TEST_SMS_TYPE_ALL);
+        Assert.assertEquals(mObserver.getMsgListSms().get(TEST_HANDLE_ONE).threadId,
+                TEST_THREAD_ID);
+        Assert.assertEquals(mObserver.getMsgListSms().get(TEST_HANDLE_ONE).flagRead,
+                TEST_READ_FLAG_ONE);
+    }
+
+    @Test
+    public void handleMsgListChangesSms_withExistingMessage_withDeletedThreadId() {
+        MatrixCursor cursor = new MatrixCursor(new String[] {Sms._ID, Sms.TYPE, Sms.THREAD_ID,
+                Sms.READ});
+        cursor.addRow(new Object[] {TEST_HANDLE_ONE, TEST_SMS_TYPE_ALL,
+                BluetoothMapContentObserver.DELETED_THREAD_ID, TEST_READ_FLAG_ONE});
+        doReturn(cursor).when(mMapMethodProxy).contentResolverQuery(any(), any(), any(), any(),
+                any(), any());
+
+        Map<Long, BluetoothMapContentObserver.Msg> map = new HashMap<>();
+        // Giving the same handle for msg below and cursor above makes handleMsgListChangesSms()
+        // function for an existing message
+        BluetoothMapContentObserver.Msg msg = new BluetoothMapContentObserver.Msg(TEST_HANDLE_ONE,
+                TEST_SMS_TYPE_ALL, TEST_THREAD_ID, TEST_READ_FLAG_ZERO);
+        map.put(TEST_HANDLE_ONE, msg);
+        mObserver.setMsgListSms(map, true);
+        mObserver.mMapEventReportVersion = BluetoothMapUtils.MAP_EVENT_REPORT_V12;
+
+        mObserver.handleMsgListChangesSms();
+
+        Assert.assertEquals(mObserver.getMsgListSms().get(TEST_HANDLE_ONE).id, TEST_HANDLE_ONE);
+        Assert.assertEquals(mObserver.getMsgListSms().get(TEST_HANDLE_ONE).type, TEST_SMS_TYPE_ALL);
+        Assert.assertEquals(mObserver.getMsgListSms().get(TEST_HANDLE_ONE).threadId,
+                BluetoothMapContentObserver.DELETED_THREAD_ID);
+        Assert.assertEquals(mObserver.getMsgListSms().get(TEST_HANDLE_ONE).flagRead,
+                TEST_READ_FLAG_ONE);
+    }
+
+    @Test
+    public void handleMsgListChangesSms_withExistingMessage_withUndeletedThreadId() {
+        int undeletedThreadId = 0;
+        MatrixCursor cursor = new MatrixCursor(new String[] {Sms._ID, Sms.TYPE, Sms.THREAD_ID,
+                Sms.READ});
+        cursor.addRow(new Object[] {TEST_HANDLE_ONE, TEST_SMS_TYPE_ALL, undeletedThreadId,
+                TEST_READ_FLAG_ONE});
+        doReturn(cursor).when(mMapMethodProxy).contentResolverQuery(any(), any(), any(), any(),
+                any(), any());
+
+        Map<Long, BluetoothMapContentObserver.Msg> map = new HashMap<>();
+        // Giving the same handle for msg below and cursor above makes handleMsgListChangesSms()
+        // function for an existing message
+        BluetoothMapContentObserver.Msg msg = new BluetoothMapContentObserver.Msg(TEST_HANDLE_ONE,
+                TEST_SMS_TYPE_ALL, TEST_THREAD_ID, TEST_READ_FLAG_ZERO);
+        map.put(TEST_HANDLE_ONE, msg);
+        mObserver.setMsgListSms(map, true);
+        mObserver.mMapEventReportVersion = BluetoothMapUtils.MAP_EVENT_REPORT_V12;
+
+        mObserver.handleMsgListChangesSms();
+
+        Assert.assertEquals(mObserver.getMsgListSms().get(TEST_HANDLE_ONE).id, TEST_HANDLE_ONE);
+        Assert.assertEquals(mObserver.getMsgListSms().get(TEST_HANDLE_ONE).type, TEST_SMS_TYPE_ALL);
+        Assert.assertEquals(mObserver.getMsgListSms().get(TEST_HANDLE_ONE).threadId,
+                undeletedThreadId);
+        Assert.assertEquals(mObserver.getMsgListSms().get(TEST_HANDLE_ONE).flagRead,
+                TEST_READ_FLAG_ONE);
+    }
+
+    @Test
+    public void handleMmsSendIntent_withMnsClientNotConnected() {
+        when(mClient.isConnected()).thenReturn(false);
+
+        Assert.assertFalse(mObserver.handleMmsSendIntent(mContext, mIntent));
+    }
+
+    @Test
+    public void handleMmsSendIntent_withInvalidHandle() {
+        when(mClient.isConnected()).thenReturn(true);
+        doReturn((long) -1).when(mIntent).getLongExtra(
+                BluetoothMapContentObserver.EXTRA_MESSAGE_SENT_HANDLE, -1);
+
+        Assert.assertTrue(mObserver.handleMmsSendIntent(mContext, mIntent));
+    }
+
+    @Test
+    public void handleMmsSendIntent_withActivityResultOk() {
+        when(mClient.isConnected()).thenReturn(true);
+        doReturn(TEST_HANDLE_ONE).when(mIntent).getLongExtra(
+                BluetoothMapContentObserver.EXTRA_MESSAGE_SENT_HANDLE, -1);
+        doReturn(Activity.RESULT_OK).when(mIntent).getIntExtra(
+                BluetoothMapContentObserver.EXTRA_MESSAGE_SENT_RESULT, Activity.RESULT_CANCELED);
+        doReturn(0).when(mIntent).getIntExtra(
+                BluetoothMapContentObserver.EXTRA_MESSAGE_SENT_TRANSPARENT, 0);
+        mObserver.mObserverRegistered = true;
+
+        Assert.assertTrue(mObserver.handleMmsSendIntent(mContext, mIntent));
+    }
+
+    @Test
+    public void handleMmsSendIntent_withActivityResultFirstUser() {
+        when(mClient.isConnected()).thenReturn(true);
+        doReturn(TEST_HANDLE_ONE).when(mIntent).getLongExtra(
+                BluetoothMapContentObserver.EXTRA_MESSAGE_SENT_HANDLE, -1);
+        doReturn(Activity.RESULT_FIRST_USER).when(mIntent).getIntExtra(
+                BluetoothMapContentObserver.EXTRA_MESSAGE_SENT_RESULT, Activity.RESULT_CANCELED);
+        mObserver.mObserverRegistered = true;
+        doReturn(TEST_PLACEHOLDER_INT).when(mMapMethodProxy).contentResolverDelete(any(), any(),
+                any(), any());
+
+        Assert.assertTrue(mObserver.handleMmsSendIntent(mContext, mIntent));
+    }
+
+    @Test
+    public void actionMessageSentDisconnected_withTypeMms() {
+        Map<Long, BluetoothMapContentObserver.Msg> mmsMsgList = new HashMap<>();
+        BluetoothMapContentObserver.Msg msg = createSimpleMsg();
+        mmsMsgList.put(TEST_HANDLE_ONE, msg);
+        doReturn(1).when(mIntent).getIntExtra(
+                BluetoothMapContentObserver.EXTRA_MESSAGE_SENT_TRANSPARENT, 0);
+        doReturn((long) -1).when(mIntent).getLongExtra(
+                BluetoothMapContentObserver.EXTRA_MESSAGE_SENT_HANDLE, -1);
+        // This mock sets type to MMS
+        doReturn(4).when(mIntent).getIntExtra(
+                BluetoothMapContentObserver.EXTRA_MESSAGE_SENT_MSG_TYPE, TYPE.NONE.ordinal());
+
+        mObserver.actionMessageSentDisconnected(mContext, mIntent, 1);
+
+        Assert.assertTrue(mmsMsgList.containsKey(TEST_HANDLE_ONE));
+    }
+
+    @Test
+    public void actionMessageSentDisconnected_withTypeEmail() {
+        // This sets to null uriString
+        doReturn(null).when(mIntent).getStringExtra(
+                BluetoothMapContentObserver.EXTRA_MESSAGE_SENT_URI);
+        doReturn(1).when(mIntent).getIntExtra(
+                BluetoothMapContentObserver.EXTRA_MESSAGE_SENT_TRANSPARENT, 0);
+        // This mock sets type to Email
+        doReturn(1).when(mIntent).getIntExtra(
+                BluetoothMapContentObserver.EXTRA_MESSAGE_SENT_MSG_TYPE, TYPE.NONE.ordinal());
+        clearInvocations(mContext);
+
+        mObserver.actionMessageSentDisconnected(mContext, mIntent, Activity.RESULT_FIRST_USER);
+
+        verify(mContext, never()).getContentResolver();
+    }
+
+    @Test
+    public void actionMmsSent_withInvalidHandle() {
+        Map<Long, BluetoothMapContentObserver.Msg> mmsMsgList = new HashMap<>();
+        BluetoothMapContentObserver.Msg msg = createSimpleMsg();
+        mmsMsgList.put(TEST_HANDLE_ONE, msg);
+        doReturn(1).when(mIntent).getIntExtra(
+                BluetoothMapContentObserver.EXTRA_MESSAGE_SENT_TRANSPARENT, 0);
+        doReturn((long) -1).when(mIntent).getLongExtra(
+                BluetoothMapContentObserver.EXTRA_MESSAGE_SENT_HANDLE, -1);
+
+        mObserver.actionMmsSent(mContext, mIntent, 1, mmsMsgList);
+
+        Assert.assertTrue(mmsMsgList.containsKey(TEST_HANDLE_ONE));
+    }
+
+    @Test
+    public void actionMmsSent_withTransparency() {
+        Map<Long, BluetoothMapContentObserver.Msg> mmsMsgList = new HashMap<>();
+        BluetoothMapContentObserver.Msg msg = createSimpleMsg();
+        mmsMsgList.put(TEST_HANDLE_ONE, msg);
+        // This mock turns on the transparent flag
+        doReturn(1).when(mIntent).getIntExtra(
+                BluetoothMapContentObserver.EXTRA_MESSAGE_SENT_TRANSPARENT, 0);
+        doReturn(TEST_HANDLE_ONE).when(mIntent).getLongExtra(
+                BluetoothMapContentObserver.EXTRA_MESSAGE_SENT_HANDLE, -1);
+        doReturn(TEST_PLACEHOLDER_INT).when(mMapMethodProxy).contentResolverDelete(any(), any(),
+                any(), any());
+
+        mObserver.actionMmsSent(mContext, mIntent, 1, mmsMsgList);
+
+        Assert.assertFalse(mmsMsgList.containsKey(TEST_HANDLE_ONE));
+    }
+
+    @Test
+    public void actionMmsSent_withActivityResultOk() {
+        Map<Long, BluetoothMapContentObserver.Msg> mmsMsgList = new HashMap<>();
+        BluetoothMapContentObserver.Msg msg = createSimpleMsg();
+        mmsMsgList.put(TEST_HANDLE_ONE, msg);
+        // This mock turns off the transparent flag
+        doReturn(0).when(mIntent).getIntExtra(
+                BluetoothMapContentObserver.EXTRA_MESSAGE_SENT_TRANSPARENT, 0);
+        doReturn(TEST_HANDLE_ONE).when(mIntent).getLongExtra(
+                BluetoothMapContentObserver.EXTRA_MESSAGE_SENT_HANDLE, -1);
+
+        MatrixCursor cursor = new MatrixCursor(new String[] {});
+        doReturn(cursor).when(mMapMethodProxy).contentResolverQuery(any(), any(), any(), any(),
+                any(), any());
+        doReturn(TEST_PLACEHOLDER_INT).when(mMapMethodProxy).contentResolverUpdate(any(), any(),
+                any(), any(), any());
+
+        mObserver.actionMmsSent(mContext, mIntent, Activity.RESULT_OK, mmsMsgList);
+
+        Assert.assertTrue(mmsMsgList.containsKey(TEST_HANDLE_ONE));
+    }
+
+    @Test
+    public void actionMmsSent_withActivityResultFirstUser() {
+        Map<Long, BluetoothMapContentObserver.Msg> mmsMsgList = new HashMap<>();
+        BluetoothMapContentObserver.Msg msg = createSimpleMsg();
+        mmsMsgList.put(TEST_HANDLE_ONE, msg);
+        // This mock turns off the transparent flag
+        doReturn(0).when(mIntent).getIntExtra(
+                BluetoothMapContentObserver.EXTRA_MESSAGE_SENT_TRANSPARENT, 0);
+        doReturn(TEST_HANDLE_ONE).when(mIntent).getLongExtra(
+                BluetoothMapContentObserver.EXTRA_MESSAGE_SENT_HANDLE, -1);
+
+        mObserver.actionMmsSent(mContext, mIntent, Activity.RESULT_FIRST_USER, mmsMsgList);
+
+        Assert.assertEquals(msg.type, Mms.MESSAGE_BOX_OUTBOX);
+    }
+
+    @Test
+    public void actionSmsSentDisconnected_withNullUriString() {
+        // This sets to null uriString
+        doReturn(null).when(mIntent).getStringExtra(
+                BluetoothMapContentObserver.EXTRA_MESSAGE_SENT_URI);
+        doReturn(1).when(mIntent).getIntExtra(
+                BluetoothMapContentObserver.EXTRA_MESSAGE_SENT_TRANSPARENT, 0);
+
+        clearInvocations(mContext);
+        mObserver.actionSmsSentDisconnected(mContext, mIntent, Activity.RESULT_FIRST_USER);
+
+        verify(mContext, never()).getContentResolver();
+    }
+
+    @Test
+    public void actionSmsSentDisconnected_withActivityResultOk_andTransparentOff() {
+        doReturn(TEST_URI_STR).when(mIntent).getStringExtra(
+                BluetoothMapContentObserver.EXTRA_MESSAGE_SENT_URI);
+        // This mock turns off the transparent flag
+        doReturn(0).when(mIntent).getIntExtra(
+                BluetoothMapContentObserver.EXTRA_MESSAGE_SENT_TRANSPARENT, 0);
+        doReturn(TEST_PLACEHOLDER_INT).when(mMapMethodProxy).contentResolverUpdate(any(), any(),
+                any(), any(), any());
+
+        clearInvocations(mContext);
+        mObserver.actionSmsSentDisconnected(mContext, mIntent, Activity.RESULT_OK);
+
+        verify(mContext).getContentResolver();
+    }
+
+    @Test
+    public void actionSmsSentDisconnected_withActivityResultOk_andTransparentOn() {
+        doReturn(TEST_URI_STR).when(mIntent).getStringExtra(
+                BluetoothMapContentObserver.EXTRA_MESSAGE_SENT_URI);
+        // This mock turns on the transparent flag
+        doReturn(1).when(mIntent).getIntExtra(
+                BluetoothMapContentObserver.EXTRA_MESSAGE_SENT_TRANSPARENT, 0);
+        doReturn(TEST_PLACEHOLDER_INT).when(mMapMethodProxy).contentResolverDelete(any(), any(),
+                any(), any());
+
+        clearInvocations(mContext);
+        mObserver.actionSmsSentDisconnected(mContext, mIntent, Activity.RESULT_OK);
+
+        verify(mContext).getContentResolver();
+    }
+
+    @Test
+    public void actionSmsSentDisconnected_withActivityResultFirstUser_andTransparentOff() {
+        doReturn(TEST_URI_STR).when(mIntent).getStringExtra(
+                BluetoothMapContentObserver.EXTRA_MESSAGE_SENT_URI);
+        // This mock turns off the transparent flag
+        doReturn(0).when(mIntent).getIntExtra(
+                BluetoothMapContentObserver.EXTRA_MESSAGE_SENT_TRANSPARENT, 0);
+        doReturn(TEST_PLACEHOLDER_INT).when(mMapMethodProxy).contentResolverUpdate(any(), any(),
+                any(), any(), any());
+
+        clearInvocations(mContext);
+        mObserver.actionSmsSentDisconnected(mContext, mIntent, Activity.RESULT_OK);
+
+        verify(mContext).getContentResolver();
+    }
+
+    @Test
+    public void actionSmsSentDisconnected_withActivityResultFirstUser_andTransparentOn() {
+        doReturn(TEST_URI_STR).when(mIntent).getStringExtra(
+                BluetoothMapContentObserver.EXTRA_MESSAGE_SENT_URI);
+        // This mock turns on the transparent flag
+        doReturn(1).when(mIntent).getIntExtra(
+                BluetoothMapContentObserver.EXTRA_MESSAGE_SENT_TRANSPARENT, 0);
+        doReturn(null).when(mContext).getContentResolver();
+
+        clearInvocations(mContext);
+        mObserver.actionSmsSentDisconnected(mContext, mIntent, Activity.RESULT_OK);
+
+        verify(mContext).getContentResolver();
+    }
+
+    @Test
+    public void handleContactListChanges_withNullContactForUci() throws Exception {
+        Uri uri = mock(Uri.class);
+        mObserver.mAuthority = TEST_AUTHORITY;
+        when(uri.getAuthority()).thenReturn(TEST_AUTHORITY);
+
+        MatrixCursor cursor = new MatrixCursor(
+                new String[]{BluetoothMapContract.ConvoContactColumns.CONVO_ID,
+                        BluetoothMapContract.ConvoContactColumns.NAME,
+                        BluetoothMapContract.ConvoContactColumns.NICKNAME,
+                        BluetoothMapContract.ConvoContactColumns.X_BT_UID,
+                        BluetoothMapContract.ConvoContactColumns.CHAT_STATE,
+                        BluetoothMapContract.ConvoContactColumns.UCI,
+                        BluetoothMapContract.ConvoContactColumns.LAST_ACTIVE,
+                        BluetoothMapContract.ConvoContactColumns.PRESENCE_STATE,
+                        BluetoothMapContract.ConvoContactColumns.STATUS_TEXT,
+                        BluetoothMapContract.ConvoContactColumns.PRIORITY,
+                        BluetoothMapContract.ConvoContactColumns.LAST_ONLINE});
+        cursor.addRow(new Object[] {TEST_CONVO_ID, TEST_NAME, TEST_DISPLAY_NAME, TEST_BT_UID,
+                TEST_CHAT_STATE, TEST_UCI, TEST_LAST_ACTIVITY, TEST_PRESENCE_STATE,
+                TEST_STATUS_TEXT, TEST_PRIORITY, TEST_LAST_ONLINE});
+        doReturn(cursor).when(mProviderClient).query(any(), any(), any(), any(), any());
+
+        Map<String, BluetoothMapConvoContactElement> map = new HashMap<>();
+        map.put(TEST_UCI_DIFFERENT, null);
+        mObserver.setContactList(map, true);
+        mObserver.mMapEventReportVersion = BluetoothMapUtils.MAP_EVENT_REPORT_V12;
+
+        mObserver.handleContactListChanges(uri);
+
+        BluetoothMapConvoContactElement contactElement = mObserver.getContactList().get(TEST_UCI);
+        final SimpleDateFormat format = new SimpleDateFormat("yyyyMMdd'T'HHmmss");
+        Assert.assertEquals(contactElement.getContactId(), TEST_UCI);
+        Assert.assertEquals(contactElement.getName(), TEST_NAME);
+        Assert.assertEquals(contactElement.getDisplayName(), TEST_DISPLAY_NAME);
+        Assert.assertEquals(contactElement.getBtUid(), TEST_BT_UID);
+        Assert.assertEquals(contactElement.getChatState(), TEST_CHAT_STATE);
+        Assert.assertEquals(contactElement.getPresenceStatus(), TEST_STATUS_TEXT);
+        Assert.assertEquals(contactElement.getPresenceAvailability(), TEST_PRESENCE_STATE);
+        Assert.assertEquals(contactElement.getLastActivityString(), format.format(
+                TEST_LAST_ACTIVITY));
+        Assert.assertEquals(contactElement.getPriority(), TEST_PRIORITY);
+    }
+
+    @Test
+    public void handleContactListChanges_withNonNullContactForUci() throws Exception {
+        Uri uri = mock(Uri.class);
+        mObserver.mAuthority = TEST_AUTHORITY;
+        when(uri.getAuthority()).thenReturn(TEST_AUTHORITY);
+
+        MatrixCursor cursor = new MatrixCursor(
+                new String[]{BluetoothMapContract.ConvoContactColumns.CONVO_ID,
+                        BluetoothMapContract.ConvoContactColumns.NAME,
+                        BluetoothMapContract.ConvoContactColumns.NICKNAME,
+                        BluetoothMapContract.ConvoContactColumns.X_BT_UID,
+                        BluetoothMapContract.ConvoContactColumns.CHAT_STATE,
+                        BluetoothMapContract.ConvoContactColumns.UCI,
+                        BluetoothMapContract.ConvoContactColumns.LAST_ACTIVE,
+                        BluetoothMapContract.ConvoContactColumns.PRESENCE_STATE,
+                        BluetoothMapContract.ConvoContactColumns.STATUS_TEXT,
+                        BluetoothMapContract.ConvoContactColumns.PRIORITY,
+                        BluetoothMapContract.ConvoContactColumns.LAST_ONLINE});
+        cursor.addRow(new Object[] {TEST_CONVO_ID, TEST_NAME, TEST_DISPLAY_NAME, TEST_BT_UID,
+                TEST_CHAT_STATE, TEST_UCI, TEST_LAST_ACTIVITY, TEST_PRESENCE_STATE,
+                TEST_STATUS_TEXT, TEST_PRIORITY, TEST_LAST_ONLINE});
+        doReturn(cursor).when(mProviderClient).query(any(), any(), any(), any(), any());
+
+        Map<String, BluetoothMapConvoContactElement> map = new HashMap<>();
+        map.put(TEST_UCI_DIFFERENT, null);
+        BluetoothMapConvoContactElement contact = new BluetoothMapConvoContactElement(TEST_UCI,
+                TEST_NAME, TEST_DISPLAY_NAME, TEST_STATUS_TEXT_DIFFERENT,
+                TEST_PRESENCE_STATE_DIFFERENT, TEST_LAST_ACTIVITY, TEST_CHAT_STATE_DIFFERENT,
+                TEST_PRIORITY, TEST_BT_UID);
+        map.put(TEST_UCI, contact);
+        mObserver.setContactList(map, true);
+        mObserver.mMapEventReportVersion = BluetoothMapUtils.MAP_EVENT_REPORT_V12;
+        when(mTelephonyManager.getLine1Number()).thenReturn("");
+
+        mObserver.handleContactListChanges(uri);
+
+        BluetoothMapConvoContactElement contactElement = mObserver.getContactList().get(TEST_UCI);
+        final SimpleDateFormat format = new SimpleDateFormat("yyyyMMdd'T'HHmmss");
+        Assert.assertEquals(contactElement.getContactId(), TEST_UCI);
+        Assert.assertEquals(contactElement.getName(), TEST_NAME);
+        Assert.assertEquals(contactElement.getDisplayName(), TEST_DISPLAY_NAME);
+        Assert.assertEquals(contactElement.getBtUid(), TEST_BT_UID);
+        Assert.assertEquals(contactElement.getChatState(), TEST_CHAT_STATE);
+        Assert.assertEquals(contactElement.getPresenceStatus(), TEST_STATUS_TEXT);
+        Assert.assertEquals(contactElement.getPresenceAvailability(), TEST_PRESENCE_STATE);
+        Assert.assertEquals(contactElement.getLastActivityString(), format.format(
+                TEST_LAST_ACTIVITY));
+        Assert.assertEquals(contactElement.getPriority(), TEST_PRIORITY);
+    }
+
+    @Test
+    public void handleContactListChanges_withMapEventReportVersion11() throws Exception {
+        Uri uri = mock(Uri.class);
+        mObserver.mAuthority = TEST_AUTHORITY;
+        when(uri.getAuthority()).thenReturn(TEST_AUTHORITY);
+        mObserver.mMapEventReportVersion = BluetoothMapUtils.MAP_EVENT_REPORT_V11;
+
+        mObserver.handleContactListChanges(uri);
+
+        verify(mProviderClient, never()).query(any(), any(), any(), any(), any(), any());
+    }
+
     private BluetoothMapContentObserver.Msg createSimpleMsg() {
         return new BluetoothMapContentObserver.Msg(1, 1L, 1);
     }
diff --git a/android/app/tests/unit/src/com/android/bluetooth/map/BluetoothMapContentTest.java b/android/app/tests/unit/src/com/android/bluetooth/map/BluetoothMapContentTest.java
index d8449ec..9e1f738 100644
--- a/android/app/tests/unit/src/com/android/bluetooth/map/BluetoothMapContentTest.java
+++ b/android/app/tests/unit/src/com/android/bluetooth/map/BluetoothMapContentTest.java
@@ -19,6 +19,7 @@
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.junit.Assert.assertThrows;
+import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.any;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.doThrow;
@@ -47,6 +48,8 @@
 import com.android.bluetooth.map.BluetoothMapUtils.TYPE;
 import com.android.bluetooth.mapapi.BluetoothMapContract;
 
+import com.google.android.mms.pdu.PduHeaders;
+
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
@@ -69,8 +72,10 @@
     private static final String TEST_BCC_ADDRESS = "bccName (bccAddress) <bcc@google.com>";
     private static final String TEST_FROM_ADDRESS = "fromName (fromAddress) <from@google.com>";
     private static final String TEST_ADDRESS = "111-1111-1111";
-    private static final long TEST_DATE_SMS = 1;
+    private static final long TEST_DATE_SMS = 4;
+    private static final long TEST_DATE_MMS = 3;
     private static final long TEST_DATE_EMAIL = 2;
+    private static final long TEST_DATE_IM = 1;
     private static final String TEST_NAME = "test_name";
     private static final String TEST_FORMATTED_NAME = "test_formatted_name";
     private static final String TEST_PHONE = "test_phone";
@@ -84,6 +89,23 @@
     private static final String TEST_FIRST_BT_UID = "1111";
     private static final String TEST_FIRST_BT_UCI_RECIPIENT = "test_first_bt_uci_recipient";
     private static final String TEST_FIRST_BT_UCI_ORIGINATOR = "test_first_bt_uci_originator";
+    private static final int TEST_NO_FILTER = 0;
+    private static final String TEST_CONTACT_NAME_FILTER = "test_contact_name_filter";
+    private static final int TEST_SIZE = 1;
+    private static final int TEST_TEXT_ONLY = 1;
+    private static final int TEST_READ_TRUE = 1;
+    private static final int TEST_READ_FALSE = 0;
+    private static final int TEST_PRIORITY_HIGH = 1;
+    private static final int TEST_SENT_YES = 2;
+    private static final int TEST_SENT_NO = 1;
+    private static final int TEST_PROTECTED = 1;
+    private static final int TEST_ATTACHMENT_TRUE = 1;
+    private static final String TEST_DELIVERY_STATE = "delivered";
+    private static final long TEST_THREAD_ID = 1;
+    private static final String TEST_ATTACHMENT_MIME_TYPE = "test_mime_type";
+    private static final String TEST_YES = "yes";
+    private static final String TEST_NO = "no";
+    private static final String TEST_RECEPTION_STATUS = "complete";
 
     @Mock
     private BluetoothMapAccountItem mAccountItem;
@@ -376,8 +398,7 @@
 
     @Test
     public void smsSelected_withNoFilter() {
-        int noFilter = 0;
-        when(mParams.getFilterMessageType()).thenReturn(noFilter);
+        when(mParams.getFilterMessageType()).thenReturn(TEST_NO_FILTER);
 
         assertThat(mContent.smsSelected(mInfo, mParams)).isTrue();
     }
@@ -423,8 +444,7 @@
 
     @Test
     public void mmsSelected_withNoFilter() {
-        int noFilter = 0;
-        when(mParams.getFilterMessageType()).thenReturn(noFilter);
+        when(mParams.getFilterMessageType()).thenReturn(TEST_NO_FILTER);
 
         assertThat(mContent.mmsSelected(mParams)).isTrue();
     }
@@ -573,7 +593,7 @@
     }
 
     @Test
-    public void setSenderAddressing_withFilterMSgTypeSms_andSmsMsgTypeDraft() {
+    public void setSenderAddressing_withFilterMsgTypeSms_andSmsMsgTypeDraft() {
         when(mParams.getParameterMask()).thenReturn(
                 (long) BluetoothMapContent.MASK_SENDER_ADDRESSING);
         mInfo.mMsgType = FilterInfo.TYPE_SMS;
@@ -1044,4 +1064,421 @@
                 TEST_FORMATTED_NAME);
         assertThat(messageMimeParsed.getRecipients().get(0).getName()).isEmpty();
     }
+
+    @Test
+    public void convoListing_withNullFilterRecipient() {
+        when(mParams.getConvoParameterMask()).thenReturn(
+                (long) BluetoothMapAppParams.INVALID_VALUE_PARAMETER);
+        when(mParams.getFilterMessageType()).thenReturn(TEST_NO_FILTER);
+        when(mParams.getMaxListCount()).thenReturn(2);
+        when(mParams.getStartOffset()).thenReturn(0);
+        // This mock sets filter recipient to null
+        when(mParams.getFilterRecipient()).thenReturn(null);
+
+        MatrixCursor smsMmsCursor = new MatrixCursor(new String[] {"MmsSmsThreadColId",
+                "MmsSmsThreadColDate", "MmsSmsThreadColSnippet", "MmsSmsThreadSnippetCharset",
+                "MmsSmsThreadColRead", "MmsSmsThreadColRecipientIds"});
+        smsMmsCursor.addRow(new Object[] {TEST_ID, TEST_DATE_SMS, "test_col_snippet",
+                "test_col_snippet_cs", 1, "test_recipient_ids"});
+        smsMmsCursor.moveToFirst();
+        doReturn(smsMmsCursor).when(mMapMethodProxy).contentResolverQuery(any(), any(),
+                eq(BluetoothMapContent.MMS_SMS_THREAD_PROJECTION), any(), any(), any());
+
+        MatrixCursor imEmailCursor = new MatrixCursor(
+                new String[] {BluetoothMapContract.ConversationColumns.THREAD_ID,
+                        BluetoothMapContract.ConversationColumns.LAST_THREAD_ACTIVITY,
+                        BluetoothMapContract.ConversationColumns.THREAD_NAME,
+                        BluetoothMapContract.ConversationColumns.READ_STATUS,
+                        BluetoothMapContract.ConversationColumns.VERSION_COUNTER,
+                        BluetoothMapContract.ConversationColumns.SUMMARY,
+                        BluetoothMapContract.ConvoContactColumns.X_BT_UID,
+                        BluetoothMapContract.ConvoContactColumns.CHAT_STATE,
+                        BluetoothMapContract.ConvoContactColumns.UCI,
+                        BluetoothMapContract.ConvoContactColumns.NICKNAME,
+                        BluetoothMapContract.ConvoContactColumns.LAST_ACTIVE,
+                        BluetoothMapContract.ConvoContactColumns.NAME,
+                        BluetoothMapContract.ConvoContactColumns.PRESENCE_STATE,
+                        BluetoothMapContract.ConvoContactColumns.STATUS_TEXT,
+                        BluetoothMapContract.ConvoContactColumns.PRIORITY});
+        imEmailCursor.addRow(new Object[] {TEST_ID, TEST_DATE_EMAIL, TEST_NAME, 0, 0, 0, 0, 0, 0, 0,
+                0, 0, 0, 0, 0});
+        doReturn(imEmailCursor).when(mMapMethodProxy).contentResolverQuery(any(), any(),
+                eq(BluetoothMapContract.BT_CONVERSATION_PROJECTION), any(), any(), any());
+
+        BluetoothMapConvoListing listing = mContent.convoListing(mParams, false);
+
+        assertThat(listing.getCount()).isEqualTo(2);
+        BluetoothMapConvoListingElement emailElement = listing.getList().get(1);
+        assertThat(emailElement.getType()).isEqualTo(TYPE.EMAIL);
+        assertThat(emailElement.getLastActivity()).isEqualTo(TEST_DATE_EMAIL);
+        assertThat(emailElement.getName()).isEqualTo(TEST_NAME);
+        assertThat(emailElement.getReadBool()).isFalse();
+        BluetoothMapConvoListingElement smsElement = listing.getList().get(0);
+        assertThat(smsElement.getType()).isEqualTo(TYPE.SMS_GSM);
+        assertThat(smsElement.getLastActivity()).isEqualTo(TEST_DATE_SMS);
+        assertThat(smsElement.getName()).isEqualTo("");
+        assertThat(smsElement.getReadBool()).isTrue();
+    }
+
+    @Test
+    public void convoListing_withNonNullFilterRecipient() {
+        when(mParams.getConvoParameterMask()).thenReturn(
+                (long) BluetoothMapAppParams.INVALID_VALUE_PARAMETER);
+        when(mParams.getFilterMessageType()).thenReturn(BluetoothMapAppParams.FILTER_NO_EMAIL);
+        when(mParams.getMaxListCount()).thenReturn(2);
+        when(mParams.getStartOffset()).thenReturn(0);
+        // This mock sets filter recipient to non null
+        when(mParams.getFilterRecipient()).thenReturn(TEST_CONTACT_NAME_FILTER);
+
+        MatrixCursor smsMmsCursor = new MatrixCursor(new String[] {"MmsSmsThreadColId",
+                "MmsSmsThreadColDate", "MmsSmsThreadColSnippet", "MmsSmsThreadSnippetCharset",
+                "MmsSmsThreadColRead", "MmsSmsThreadColRecipientIds"});
+        smsMmsCursor.addRow(new Object[] {TEST_ID, TEST_DATE_SMS, "test_col_snippet",
+                "test_col_snippet_cs", 1, String.valueOf(TEST_ID)});
+        smsMmsCursor.moveToFirst();
+        doReturn(smsMmsCursor).when(mMapMethodProxy).contentResolverQuery(any(), any(),
+                eq(BluetoothMapContent.MMS_SMS_THREAD_PROJECTION), any(), any(), any());
+
+        MatrixCursor addressCursor = new MatrixCursor(new String[] {"COL_ADDR_ID",
+                "COL_ADDR_ADDR"});
+        addressCursor.addRow(new Object[]{TEST_ID, TEST_ADDRESS});
+        doReturn(addressCursor).when(mMapMethodProxy).contentResolverQuery(any(), any(),
+                eq(SmsMmsContacts.ADDRESS_PROJECTION), any(), any(), any());
+
+        MatrixCursor contactCursor = new MatrixCursor(new String[] {"COL_CONTACT_ID",
+                "COL_CONTACT_NAME"});
+        contactCursor.addRow(new Object[]{TEST_ID, TEST_NAME});
+        doReturn(contactCursor).when(mMapMethodProxy).contentResolverQuery(any(), any(),
+                eq(SmsMmsContacts.CONTACT_PROJECTION), any(), any(), any());
+
+        MatrixCursor imEmailCursor = new MatrixCursor(
+                new String[] {BluetoothMapContract.ConversationColumns.THREAD_ID,
+                        BluetoothMapContract.ConversationColumns.LAST_THREAD_ACTIVITY,
+                        BluetoothMapContract.ConversationColumns.THREAD_NAME,
+                        BluetoothMapContract.ConversationColumns.READ_STATUS,
+                        BluetoothMapContract.ConversationColumns.VERSION_COUNTER,
+                        BluetoothMapContract.ConversationColumns.SUMMARY,
+                        BluetoothMapContract.ConvoContactColumns.X_BT_UID,
+                        BluetoothMapContract.ConvoContactColumns.CHAT_STATE,
+                        BluetoothMapContract.ConvoContactColumns.UCI,
+                        BluetoothMapContract.ConvoContactColumns.NICKNAME,
+                        BluetoothMapContract.ConvoContactColumns.LAST_ACTIVE,
+                        BluetoothMapContract.ConvoContactColumns.NAME,
+                        BluetoothMapContract.ConvoContactColumns.PRESENCE_STATE,
+                        BluetoothMapContract.ConvoContactColumns.STATUS_TEXT,
+                        BluetoothMapContract.ConvoContactColumns.PRIORITY});
+        imEmailCursor.addRow(new Object[] {TEST_ID, TEST_DATE_EMAIL, TEST_NAME, 0, 0, 0, 0, 0, 0, 0,
+                0, 0, 0, 0, 0});
+        doReturn(imEmailCursor).when(mMapMethodProxy).contentResolverQuery(any(), any(),
+                eq(BluetoothMapContract.BT_CONVERSATION_PROJECTION), any(), any(), any());
+
+        BluetoothMapConvoListing listing = mContent.convoListing(mParams, false);
+
+        assertThat(listing.getCount()).isEqualTo(2);
+        BluetoothMapConvoListingElement imElement = listing.getList().get(1);
+        assertThat(imElement.getType()).isEqualTo(TYPE.IM);
+        assertThat(imElement.getLastActivity()).isEqualTo(TEST_DATE_EMAIL);
+        assertThat(imElement.getName()).isEqualTo(TEST_NAME);
+        assertThat(imElement.getReadBool()).isFalse();
+        BluetoothMapConvoListingElement smsElement = listing.getList().get(0);
+        assertThat(smsElement.getType()).isEqualTo(TYPE.SMS_GSM);
+        assertThat(smsElement.getLastActivity()).isEqualTo(TEST_DATE_SMS);
+        assertThat(smsElement.getName()).isEqualTo("");
+        assertThat(smsElement.getReadBool()).isTrue();
+    }
+
+    @Test
+    public void msgListing_withSmsCursorOnly() {
+        when(mParams.getParameterMask()).thenReturn(
+                (long) BluetoothMapAppParams.INVALID_VALUE_PARAMETER);
+        int noMms = BluetoothMapAppParams.FILTER_NO_MMS;
+        when(mParams.getFilterMessageType()).thenReturn(noMms);
+        when(mParams.getMaxListCount()).thenReturn(1);
+        when(mParams.getStartOffset()).thenReturn(0);
+
+        mCurrentFolder.setHasSmsMmsContent(true);
+        mCurrentFolder.setFolderId(TEST_ID);
+        mContent.mMsgListingVersion = BluetoothMapUtils.MAP_MESSAGE_LISTING_FORMAT_V11;
+
+        MatrixCursor smsCursor = new MatrixCursor(new String[] {BaseColumns._ID, Telephony.Sms.TYPE,
+                Telephony.Sms.READ, Telephony.Sms.BODY, Telephony.Sms.ADDRESS, Telephony.Sms.DATE,
+                Telephony.Sms.THREAD_ID, ContactsContract.Contacts.DISPLAY_NAME});
+        smsCursor.addRow(new Object[] {TEST_ID, TEST_SENT_NO, TEST_READ_TRUE, TEST_SUBJECT,
+                TEST_ADDRESS, TEST_DATE_SMS, TEST_THREAD_ID, TEST_PHONE_NAME});
+        doReturn(smsCursor).when(mMapMethodProxy).contentResolverQuery(any(), any(),
+                eq(BluetoothMapContent.SMS_PROJECTION), any(), any(), any());
+        doReturn(smsCursor).when(mMapMethodProxy).contentResolverQuery(any(), any(),
+                eq(new String[] {ContactsContract.Contacts._ID,
+                        ContactsContract.Contacts.DISPLAY_NAME}), any(), any(), any());
+
+        BluetoothMapMessageListing listing = mContent.msgListing(mCurrentFolder, mParams);
+        assertThat(listing.getCount()).isEqualTo(1);
+
+        BluetoothMapMessageListingElement smsElement = listing.getList().get(0);
+        assertThat(smsElement.getHandle()).isEqualTo(TEST_ID);
+        assertThat(smsElement.getDateTime()).isEqualTo(TEST_DATE_SMS);
+        assertThat(smsElement.getType()).isEqualTo(TYPE.SMS_GSM);
+        assertThat(smsElement.getReadBool()).isTrue();
+        assertThat(smsElement.getSenderAddressing()).isEqualTo(
+                PhoneNumberUtils.extractNetworkPortion(TEST_ADDRESS));
+        assertThat(smsElement.getSenderName()).isEqualTo(TEST_PHONE_NAME);
+        assertThat(smsElement.getSize()).isEqualTo(TEST_SUBJECT.length());
+        assertThat(smsElement.getPriority()).isEqualTo(TEST_NO);
+        assertThat(smsElement.getSent()).isEqualTo(TEST_NO);
+        assertThat(smsElement.getProtect()).isEqualTo(TEST_NO);
+        assertThat(smsElement.getReceptionStatus()).isEqualTo(TEST_RECEPTION_STATUS);
+        assertThat(smsElement.getAttachmentSize()).isEqualTo(0);
+        assertThat(smsElement.getDeliveryStatus()).isEqualTo(TEST_DELIVERY_STATE);
+    }
+
+    @Test
+    public void msgListing_withMmsCursorOnly() {
+        when(mParams.getParameterMask()).thenReturn(
+                (long) BluetoothMapAppParams.INVALID_VALUE_PARAMETER);
+        int onlyMms =
+                BluetoothMapAppParams.FILTER_NO_EMAIL | BluetoothMapAppParams.FILTER_NO_SMS_CDMA
+                        | BluetoothMapAppParams.FILTER_NO_SMS_GSM
+                        | BluetoothMapAppParams.FILTER_NO_IM;
+        when(mParams.getFilterMessageType()).thenReturn(onlyMms);
+        when(mParams.getMaxListCount()).thenReturn(1);
+        when(mParams.getStartOffset()).thenReturn(0);
+
+        mCurrentFolder.setHasSmsMmsContent(true);
+        mCurrentFolder.setFolderId(TEST_ID);
+        mContent.mMsgListingVersion = BluetoothMapUtils.MAP_MESSAGE_LISTING_FORMAT_V11;
+
+        MatrixCursor mmsCursor = new MatrixCursor(new String[] {BaseColumns._ID,
+                Telephony.Mms.MESSAGE_BOX, Telephony.Mms.READ, Telephony.Mms.MESSAGE_SIZE,
+                Telephony.Mms.TEXT_ONLY, Telephony.Mms.DATE, Telephony.Mms.SUBJECT,
+                Telephony.Mms.THREAD_ID, Telephony.Mms.Addr.ADDRESS,
+                ContactsContract.Contacts.DISPLAY_NAME, Telephony.Mms.PRIORITY});
+        mmsCursor.addRow(new Object[] {TEST_ID, TEST_SENT_NO, TEST_READ_FALSE, TEST_SIZE,
+                TEST_TEXT_ONLY, TEST_DATE_MMS, TEST_SUBJECT, TEST_THREAD_ID, TEST_PHONE,
+                TEST_PHONE_NAME, PduHeaders.PRIORITY_HIGH});
+        doReturn(mmsCursor).when(mMapMethodProxy).contentResolverQuery(any(), any(),
+                eq(BluetoothMapContent.MMS_PROJECTION), any(), any(), any());
+        doReturn(mmsCursor).when(mMapMethodProxy).contentResolverQuery(any(), any(),
+                eq(new String[] {Telephony.Mms.Addr.ADDRESS}), any(), any(), any());
+        doReturn(mmsCursor).when(mMapMethodProxy).contentResolverQuery(any(), any(),
+                eq(new String[] {ContactsContract.Contacts._ID,
+                        ContactsContract.Contacts.DISPLAY_NAME}), any(), any(), any());
+
+        BluetoothMapMessageListing listing = mContent.msgListing(mCurrentFolder, mParams);
+        assertThat(listing.getCount()).isEqualTo(1);
+
+        BluetoothMapMessageListingElement mmsElement = listing.getList().get(0);
+        assertThat(mmsElement.getHandle()).isEqualTo(TEST_ID);
+        assertThat(mmsElement.getDateTime()).isEqualTo(TEST_DATE_MMS * 1000L);
+        assertThat(mmsElement.getType()).isEqualTo(TYPE.MMS);
+        assertThat(mmsElement.getReadBool()).isFalse();
+        assertThat(mmsElement.getSenderAddressing()).isEqualTo(TEST_PHONE);
+        assertThat(mmsElement.getSenderName()).isEqualTo(TEST_PHONE_NAME);
+        assertThat(mmsElement.getSize()).isEqualTo(TEST_SIZE);
+        assertThat(mmsElement.getPriority()).isEqualTo(TEST_YES);
+        assertThat(mmsElement.getSent()).isEqualTo(TEST_NO);
+        assertThat(mmsElement.getProtect()).isEqualTo(TEST_NO);
+        assertThat(mmsElement.getReceptionStatus()).isEqualTo(TEST_RECEPTION_STATUS);
+        assertThat(mmsElement.getAttachmentSize()).isEqualTo(0);
+        assertThat(mmsElement.getDeliveryStatus()).isEqualTo(TEST_DELIVERY_STATE);
+    }
+
+    @Test
+    public void msgListing_withEmailCursorOnly() {
+        when(mParams.getParameterMask()).thenReturn(
+                (long) BluetoothMapAppParams.INVALID_VALUE_PARAMETER);
+        int onlyEmail =
+                BluetoothMapAppParams.FILTER_NO_MMS | BluetoothMapAppParams.FILTER_NO_SMS_CDMA
+                        | BluetoothMapAppParams.FILTER_NO_SMS_GSM
+                        | BluetoothMapAppParams.FILTER_NO_IM;
+        when(mParams.getFilterMessageType()).thenReturn(onlyEmail);
+        when(mParams.getMaxListCount()).thenReturn(1);
+        when(mParams.getStartOffset()).thenReturn(0);
+
+        mCurrentFolder.setHasEmailContent(true);
+        mCurrentFolder.setFolderId(TEST_ID);
+        mContent.mMsgListingVersion = BluetoothMapUtils.MAP_MESSAGE_LISTING_FORMAT_V11;
+
+        MatrixCursor emailCursor = new MatrixCursor(new String[] {
+                BluetoothMapContract.MessageColumns._ID,
+                BluetoothMapContract.MessageColumns.DATE,
+                BluetoothMapContract.MessageColumns.SUBJECT,
+                BluetoothMapContract.MessageColumns.FOLDER_ID,
+                BluetoothMapContract.MessageColumns.FLAG_READ,
+                BluetoothMapContract.MessageColumns.MESSAGE_SIZE,
+                BluetoothMapContract.MessageColumns.FROM_LIST,
+                BluetoothMapContract.MessageColumns.TO_LIST,
+                BluetoothMapContract.MessageColumns.FLAG_ATTACHMENT,
+                BluetoothMapContract.MessageColumns.ATTACHMENT_SIZE,
+                BluetoothMapContract.MessageColumns.FLAG_HIGH_PRIORITY,
+                BluetoothMapContract.MessageColumns.FLAG_PROTECTED,
+                BluetoothMapContract.MessageColumns.RECEPTION_STATE,
+                BluetoothMapContract.MessageColumns.DEVILERY_STATE,
+                BluetoothMapContract.MessageColumns.THREAD_ID,
+                BluetoothMapContract.MessageColumns.CC_LIST,
+                BluetoothMapContract.MessageColumns.BCC_LIST,
+                BluetoothMapContract.MessageColumns.REPLY_TO_LIST});
+        emailCursor.addRow(new Object[] {TEST_ID, TEST_DATE_EMAIL, TEST_SUBJECT, TEST_SENT_YES,
+                TEST_READ_TRUE, TEST_SIZE, TEST_FROM_ADDRESS, TEST_TO_ADDRESS, TEST_ATTACHMENT_TRUE,
+                0, TEST_PRIORITY_HIGH, TEST_PROTECTED, 0, TEST_DELIVERY_STATE,
+                TEST_THREAD_ID, TEST_CC_ADDRESS, TEST_BCC_ADDRESS, TEST_TO_ADDRESS});
+        doReturn(emailCursor).when(mMapMethodProxy).contentResolverQuery(any(), any(),
+                eq(BluetoothMapContract.BT_MESSAGE_PROJECTION), any(), any(), any());
+
+        BluetoothMapMessageListing listing = mContent.msgListing(mCurrentFolder, mParams);
+        assertThat(listing.getCount()).isEqualTo(1);
+
+        BluetoothMapMessageListingElement emailElement = listing.getList().get(0);
+        assertThat(emailElement.getHandle()).isEqualTo(TEST_ID);
+        assertThat(emailElement.getDateTime()).isEqualTo(TEST_DATE_EMAIL);
+        assertThat(emailElement.getType()).isEqualTo(TYPE.EMAIL);
+        assertThat(emailElement.getReadBool()).isTrue();
+        StringBuilder expectedAddress = new StringBuilder();
+        expectedAddress.append(Rfc822Tokenizer.tokenize(TEST_FROM_ADDRESS)[0].getAddress());
+        assertThat(emailElement.getSenderAddressing()).isEqualTo(expectedAddress.toString());
+        StringBuilder expectedName = new StringBuilder();
+        expectedName.append(Rfc822Tokenizer.tokenize(TEST_FROM_ADDRESS)[0].getName());
+        assertThat(emailElement.getSenderName()).isEqualTo(expectedName.toString());
+        assertThat(emailElement.getSize()).isEqualTo(TEST_SIZE);
+        assertThat(emailElement.getPriority()).isEqualTo(TEST_YES);
+        assertThat(emailElement.getSent()).isEqualTo(TEST_YES);
+        assertThat(emailElement.getProtect()).isEqualTo(TEST_YES);
+        assertThat(emailElement.getReceptionStatus()).isEqualTo(TEST_RECEPTION_STATUS);
+        assertThat(emailElement.getAttachmentSize()).isEqualTo(TEST_SIZE);
+        assertThat(emailElement.getDeliveryStatus()).isEqualTo(TEST_DELIVERY_STATE);
+    }
+
+    @Test
+    public void msgListing_withImCursorOnly() {
+        when(mParams.getParameterMask()).thenReturn(
+                (long) BluetoothMapAppParams.INVALID_VALUE_PARAMETER);
+        int onlyIm = BluetoothMapAppParams.FILTER_NO_MMS | BluetoothMapAppParams.FILTER_NO_SMS_CDMA
+                | BluetoothMapAppParams.FILTER_NO_SMS_GSM | BluetoothMapAppParams.FILTER_NO_EMAIL;
+        when(mParams.getFilterMessageType()).thenReturn(onlyIm);
+        when(mParams.getMaxListCount()).thenReturn(1);
+        when(mParams.getStartOffset()).thenReturn(0);
+
+        mCurrentFolder.setHasImContent(true);
+        mCurrentFolder.setFolderId(TEST_ID);
+        mContent.mMsgListingVersion = BluetoothMapUtils.MAP_MESSAGE_LISTING_FORMAT_V11;
+
+        MatrixCursor imCursor = new MatrixCursor(new String[] {
+                BluetoothMapContract.MessageColumns._ID,
+                BluetoothMapContract.MessageColumns.DATE,
+                BluetoothMapContract.MessageColumns.SUBJECT,
+                BluetoothMapContract.MessageColumns.FOLDER_ID,
+                BluetoothMapContract.MessageColumns.FLAG_READ,
+                BluetoothMapContract.MessageColumns.MESSAGE_SIZE,
+                BluetoothMapContract.MessageColumns.FROM_LIST,
+                BluetoothMapContract.MessageColumns.TO_LIST,
+                BluetoothMapContract.MessageColumns.FLAG_ATTACHMENT,
+                BluetoothMapContract.MessageColumns.ATTACHMENT_SIZE,
+                BluetoothMapContract.MessageColumns.FLAG_HIGH_PRIORITY,
+                BluetoothMapContract.MessageColumns.FLAG_PROTECTED,
+                BluetoothMapContract.MessageColumns.RECEPTION_STATE,
+                BluetoothMapContract.MessageColumns.DEVILERY_STATE,
+                BluetoothMapContract.MessageColumns.THREAD_ID,
+                BluetoothMapContract.MessageColumns.THREAD_NAME,
+                BluetoothMapContract.MessageColumns.ATTACHMENT_MINE_TYPES,
+                BluetoothMapContract.MessageColumns.BODY,
+                BluetoothMapContract.ConvoContactColumns.UCI,
+                BluetoothMapContract.ConvoContactColumns.NAME});
+        imCursor.addRow(new Object[] {TEST_ID, TEST_DATE_IM, TEST_SUBJECT, TEST_SENT_NO,
+                TEST_READ_FALSE, TEST_SIZE, TEST_ID, TEST_TO_ADDRESS, TEST_ATTACHMENT_TRUE,
+                0 /*=attachment size*/, TEST_PRIORITY_HIGH, TEST_PROTECTED, 0, TEST_DELIVERY_STATE,
+                TEST_THREAD_ID, TEST_NAME, TEST_ATTACHMENT_MIME_TYPE, 0, TEST_ADDRESS, TEST_NAME});
+        doReturn(imCursor).when(mMapMethodProxy).contentResolverQuery(any(), any(),
+                eq(BluetoothMapContract.BT_INSTANT_MESSAGE_PROJECTION), any(), any(), any());
+        doReturn(imCursor).when(mMapMethodProxy).contentResolverQuery(any(), any(),
+                eq(BluetoothMapContract.BT_CONTACT_PROJECTION), any(), any(), any());
+
+        BluetoothMapMessageListing listing = mContent.msgListing(mCurrentFolder, mParams);
+        assertThat(listing.getCount()).isEqualTo(1);
+
+        BluetoothMapMessageListingElement imElement = listing.getList().get(0);
+        assertThat(imElement.getHandle()).isEqualTo(TEST_ID);
+        assertThat(imElement.getDateTime()).isEqualTo(TEST_DATE_IM);
+        assertThat(imElement.getType()).isEqualTo(TYPE.IM);
+        assertThat(imElement.getReadBool()).isFalse();
+        assertThat(imElement.getSenderAddressing()).isEqualTo(TEST_ADDRESS);
+        assertThat(imElement.getSenderName()).isEqualTo(TEST_NAME);
+        assertThat(imElement.getSize()).isEqualTo(TEST_SIZE);
+        assertThat(imElement.getPriority()).isEqualTo(TEST_YES);
+        assertThat(imElement.getSent()).isEqualTo(TEST_NO);
+        assertThat(imElement.getProtect()).isEqualTo(TEST_YES);
+        assertThat(imElement.getReceptionStatus()).isEqualTo(TEST_RECEPTION_STATUS);
+        assertThat(imElement.getAttachmentSize()).isEqualTo(TEST_SIZE);
+        assertThat(imElement.getAttachmentMimeTypes()).isEqualTo(TEST_ATTACHMENT_MIME_TYPE);
+        assertThat(imElement.getDeliveryStatus()).isEqualTo(TEST_DELIVERY_STATE);
+        assertThat(imElement.getThreadName()).isEqualTo(TEST_NAME);
+    }
+
+    @Test
+    public void msgListingSize() {
+        when(mParams.getFilterMessageType()).thenReturn(TEST_NO_FILTER);
+        mCurrentFolder.setHasSmsMmsContent(true);
+        mCurrentFolder.setHasEmailContent(true);
+        mCurrentFolder.setHasImContent(true);
+        mCurrentFolder.setFolderId(TEST_ID);
+
+        MatrixCursor smsCursor = new MatrixCursor(new String[] {"Placeholder"});
+        // Making cursor.getCount() as 1
+        smsCursor.addRow(new Object[] {1});
+        doReturn(smsCursor).when(mMapMethodProxy).contentResolverQuery(any(), any(),
+                eq(BluetoothMapContent.SMS_PROJECTION), any(), any(), any());
+
+        MatrixCursor mmsCursor = new MatrixCursor(new String[] {"Placeholder"});
+        // Making cursor.getCount() as 1
+        mmsCursor.addRow(new Object[] {1});
+        doReturn(mmsCursor).when(mMapMethodProxy).contentResolverQuery(any(), any(),
+                eq(BluetoothMapContent.MMS_PROJECTION), any(), any(), any());
+
+        MatrixCursor emailCursor = new MatrixCursor(new String[] {"Placeholder"});
+        // Making cursor.getCount() as 1
+        emailCursor.addRow(new Object[] {1});
+        doReturn(emailCursor).when(mMapMethodProxy).contentResolverQuery(any(), any(),
+                eq(BluetoothMapContract.BT_MESSAGE_PROJECTION), any(), any(), any());
+
+        MatrixCursor imCursor = new MatrixCursor(new String[] {"Placeholder"});
+        // Making cursor.getCount() as 1
+        imCursor.addRow(new Object[] {1});
+        doReturn(imCursor).when(mMapMethodProxy).contentResolverQuery(any(), any(),
+                eq(BluetoothMapContract.BT_INSTANT_MESSAGE_PROJECTION), any(), any(), any());
+
+        assertThat(mContent.msgListingSize(mCurrentFolder, mParams)).isEqualTo(4);
+    }
+
+    @Test
+    public void msgListingHasUnread() {
+        when(mParams.getFilterMessageType()).thenReturn(TEST_NO_FILTER);
+        mCurrentFolder.setHasSmsMmsContent(true);
+        mCurrentFolder.setHasEmailContent(true);
+        mCurrentFolder.setHasImContent(true);
+        mCurrentFolder.setFolderId(TEST_ID);
+
+        MatrixCursor smsCursor = new MatrixCursor(new String[] {"Placeholder"});
+        // Making cursor.getCount() as 1
+        smsCursor.addRow(new Object[] {1});
+        doReturn(smsCursor).when(mMapMethodProxy).contentResolverQuery(any(), any(),
+                eq(BluetoothMapContent.SMS_PROJECTION), any(), any(), any());
+
+        MatrixCursor mmsCursor = new MatrixCursor(new String[] {"Placeholder"});
+        // Making cursor.getCount() as 1
+        mmsCursor.addRow(new Object[] {1});
+        doReturn(mmsCursor).when(mMapMethodProxy).contentResolverQuery(any(), any(),
+                eq(BluetoothMapContent.MMS_PROJECTION), any(), any(), any());
+
+        MatrixCursor emailCursor = new MatrixCursor(new String[] {"Placeholder"});
+        // Making cursor.getCount() as 1
+        emailCursor.addRow(new Object[] {1});
+        doReturn(emailCursor).when(mMapMethodProxy).contentResolverQuery(any(), any(),
+                eq(BluetoothMapContract.BT_MESSAGE_PROJECTION), any(), any(), any());
+
+        MatrixCursor imCursor = new MatrixCursor(new String[] {"Placeholder"});
+        // Making cursor.getCount() as 1
+        imCursor.addRow(new Object[] {1});
+        doReturn(imCursor).when(mMapMethodProxy).contentResolverQuery(any(), any(),
+                eq(BluetoothMapContract.BT_INSTANT_MESSAGE_PROJECTION), any(), any(), any());
+
+        assertThat(mContent.msgListingHasUnread(mCurrentFolder, mParams)).isTrue();
+    }
 }
\ No newline at end of file
diff --git a/android/app/tests/unit/src/com/android/bluetooth/map/BluetoothMapObexServerTest.java b/android/app/tests/unit/src/com/android/bluetooth/map/BluetoothMapObexServerTest.java
index 0a6f6bd..4b252fc 100644
--- a/android/app/tests/unit/src/com/android/bluetooth/map/BluetoothMapObexServerTest.java
+++ b/android/app/tests/unit/src/com/android/bluetooth/map/BluetoothMapObexServerTest.java
@@ -18,6 +18,7 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.junit.Assert.assertThrows;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.doReturn;
@@ -39,6 +40,7 @@
 import com.android.bluetooth.BluetoothMethodProxy;
 import com.android.bluetooth.mapapi.BluetoothMapContract;
 import com.android.obex.ResponseCodes;
+import com.android.obex.Operation;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -185,6 +187,53 @@
         assertThat(parentFolder.getFolderById(childId)).isNotNull();
     }
 
+    @Test
+    public void setMsgTypeFilterParams_withAccountNull_andOverwriteTrue() throws Exception {
+        BluetoothMapObexServer obexServer = new BluetoothMapObexServer(null, mContext, mObserver,
+                mMasInstance, null, false);
+
+        obexServer.setMsgTypeFilterParams(mParams, true);
+
+        int expectedMask = 0;
+        expectedMask |= BluetoothMapAppParams.FILTER_NO_SMS_CDMA;
+        expectedMask |= BluetoothMapAppParams.FILTER_NO_SMS_GSM;
+        expectedMask |= BluetoothMapAppParams.FILTER_NO_MMS;
+        expectedMask |= BluetoothMapAppParams.FILTER_NO_EMAIL;
+        expectedMask |= BluetoothMapAppParams.FILTER_NO_IM;
+        assertThat(mParams.getFilterMessageType()).isEqualTo(expectedMask);
+    }
+
+    @Test
+    public void setMsgTypeFilterParams_withInvalidFilterMessageType() throws Exception {
+        BluetoothMapAccountItem accountItemWithTypeEmail = BluetoothMapAccountItem.create(TEST_ID,
+                TEST_NAME, TEST_PACKAGE_NAME, TEST_PROVIDER_AUTHORITY, TEST_DRAWABLE,
+                BluetoothMapUtils.TYPE.EMAIL, TEST_UCI, TEST_UCI_PREFIX);
+        BluetoothMapObexServer obexServer = new BluetoothMapObexServer(null, mContext, mObserver,
+                mMasInstance, accountItemWithTypeEmail, TEST_ENABLE_SMS_MMS);
+
+        // Passing mParams without any previous settings pass invalid filter message type
+        assertThrows(IllegalArgumentException.class,
+                () -> obexServer.setMsgTypeFilterParams(mParams, false));
+    }
+
+    @Test
+    public void setMsgTypeFilterParams_withValidFilterMessageType() throws Exception {
+        BluetoothMapAccountItem accountItemWithTypeIm = BluetoothMapAccountItem.create(TEST_ID,
+                TEST_NAME, TEST_PACKAGE_NAME, TEST_PROVIDER_AUTHORITY, TEST_DRAWABLE,
+                BluetoothMapUtils.TYPE.IM, TEST_UCI, TEST_UCI_PREFIX);
+        BluetoothMapObexServer obexServer = new BluetoothMapObexServer(null, mContext, mObserver,
+                mMasInstance, accountItemWithTypeIm, TEST_ENABLE_SMS_MMS);
+        int expectedMask = 1;
+        mParams.setFilterMessageType(expectedMask);
+
+        obexServer.setMsgTypeFilterParams(mParams, false);
+
+        int masFilterMask = 0;
+        masFilterMask |= BluetoothMapAppParams.FILTER_NO_EMAIL;
+        expectedMask |= masFilterMask;
+        assertThat(mParams.getFilterMessageType()).isEqualTo(expectedMask);
+    }
+
     private void setUpBluetoothMapAppParams(BluetoothMapAppParams params) {
         params.setPresenceAvailability(1);
         params.setPresenceStatus("test_presence_status");
diff --git a/android/app/tests/unit/src/com/android/bluetooth/map/BluetoothMapSettingsTest.java b/android/app/tests/unit/src/com/android/bluetooth/map/BluetoothMapSettingsTest.java
new file mode 100644
index 0000000..df751e3
--- /dev/null
+++ b/android/app/tests/unit/src/com/android/bluetooth/map/BluetoothMapSettingsTest.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.bluetooth.map;
+
+import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DEFAULT;
+import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_ENABLED;
+import static android.content.pm.PackageManager.DONT_KILL_APP;
+
+import static androidx.test.espresso.Espresso.onView;
+import static androidx.test.espresso.assertion.ViewAssertions.matches;
+import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
+import static androidx.test.espresso.matcher.ViewMatchers.withId;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+
+import androidx.test.core.app.ActivityScenario;
+import androidx.test.filters.LargeTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.bluetooth.R;
+
+import org.junit.After;
+import org.junit.Assume;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@LargeTest
+@RunWith(AndroidJUnit4.class)
+public class BluetoothMapSettingsTest {
+
+    Context mTargetContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
+    Intent mIntent;
+
+    ActivityScenario<BluetoothMapSettings> mActivityScenario;
+
+    @Before
+    public void setUp() {
+        Assume.assumeTrue("Ignore test when BluetoothMapService is not enabled",
+                BluetoothMapService.isEnabled());
+        enableActivity(true);
+        mIntent = new Intent();
+        mIntent.setClass(mTargetContext, BluetoothMapSettings.class);
+        mActivityScenario = ActivityScenario.launch(mIntent);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        if (mActivityScenario != null) {
+            // Workaround for b/159805732. Without this, test hangs for 45 seconds.
+            Thread.sleep(1_000);
+            mActivityScenario.close();
+        }
+        enableActivity(false);
+    }
+
+    @Test
+    public void initialize() throws Exception {
+        onView(withId(R.id.bluetooth_map_settings_list_view)).check(matches(isDisplayed()));
+    }
+
+    private void enableActivity(boolean enable) {
+        int enabledState = enable ? COMPONENT_ENABLED_STATE_ENABLED
+                : COMPONENT_ENABLED_STATE_DEFAULT;
+
+        mTargetContext.getPackageManager().setApplicationEnabledSetting(
+                mTargetContext.getPackageName(), enabledState, DONT_KILL_APP);
+
+        ComponentName activityName = new ComponentName(mTargetContext, BluetoothMapSettings.class);
+        mTargetContext.getPackageManager().setComponentEnabledSetting(
+                activityName, enabledState, DONT_KILL_APP);
+    }
+}
diff --git a/android/app/tests/unit/src/com/android/bluetooth/mapclient/MapClientContentTest.java b/android/app/tests/unit/src/com/android/bluetooth/mapclient/MapClientContentTest.java
index 8b3eced..29b59f3 100644
--- a/android/app/tests/unit/src/com/android/bluetooth/mapclient/MapClientContentTest.java
+++ b/android/app/tests/unit/src/com/android/bluetooth/mapclient/MapClientContentTest.java
@@ -364,6 +364,17 @@
                 eq(SubscriptionManager.SUBSCRIPTION_TYPE_REMOTE_SIM));
     }
 
+    /**
+     * Test to validate that cleaning content does not crash when no subscription are available.
+     */
+    @Test
+    public void testCleanUpWithNoSubscriptions() {
+        when(mMockSubscriptionManager.getActiveSubscriptionInfoList())
+                .thenReturn(null);
+
+        MapClientContent.clearAllContent(mMockContext);
+    }
+
     void createTestMessages() {
         mOriginator = new VCardEntry();
         VCardProperty property = new VCardProperty();
diff --git a/android/app/tests/unit/src/com/android/bluetooth/mapclient/MapClientServiceTest.java b/android/app/tests/unit/src/com/android/bluetooth/mapclient/MapClientServiceTest.java
index 07e1266..93365e5 100644
--- a/android/app/tests/unit/src/com/android/bluetooth/mapclient/MapClientServiceTest.java
+++ b/android/app/tests/unit/src/com/android/bluetooth/mapclient/MapClientServiceTest.java
@@ -301,7 +301,7 @@
         MceStateMachine sm = mock(MceStateMachine.class);
         mService.getInstanceMap().put(mRemoteDevice, sm);
 
-        Intent intent = new Intent(BluetoothHeadsetClient.ACTION_CONNECTION_STATE_CHANGED);
+        Intent intent = new Intent(BluetoothDevice.ACTION_SDP_RECORD);
         intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mRemoteDevice);
         intent.putExtra(BluetoothProfile.EXTRA_STATE, BluetoothProfile.STATE_DISCONNECTED);
         intent.putExtra(BluetoothDevice.EXTRA_UUID, BluetoothUuid.MAS);
diff --git a/android/app/tests/unit/src/com/android/bluetooth/mapclient/MapClientTest.java b/android/app/tests/unit/src/com/android/bluetooth/mapclient/MapClientTest.java
index c78616e..7c80bff 100644
--- a/android/app/tests/unit/src/com/android/bluetooth/mapclient/MapClientTest.java
+++ b/android/app/tests/unit/src/com/android/bluetooth/mapclient/MapClientTest.java
@@ -122,6 +122,7 @@
 
         // is the statemachine created
         Map<BluetoothDevice, MceStateMachine> map = mService.getInstanceMap();
+
         Assert.assertEquals(1, map.size());
         Assert.assertNotNull(map.get(device));
         TestUtils.waitForLooperToFinishScheduledTask(mService.getMainLooper());
diff --git a/android/app/tests/unit/src/com/android/bluetooth/mapclient/MessagesFilterTest.java b/android/app/tests/unit/src/com/android/bluetooth/mapclient/MessagesFilterTest.java
new file mode 100644
index 0000000..9c6a307
--- /dev/null
+++ b/android/app/tests/unit/src/com/android/bluetooth/mapclient/MessagesFilterTest.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright 2023 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 com.google.common.truth.Truth.assertThat;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class MessagesFilterTest {
+
+    @Test
+    public void setOriginator() {
+        MessagesFilter filter = new MessagesFilter();
+
+        String originator = "test_originator";
+        filter.setOriginator(originator);
+        assertThat(filter.originator).isEqualTo(originator);
+
+        filter.setOriginator("");
+        assertThat(filter.originator).isEqualTo(null); // Empty string is stored as null
+
+        filter.setOriginator(null);
+        assertThat(filter.originator).isEqualTo(null);
+    }
+
+    @Test
+    public void setPriority() {
+        MessagesFilter filter = new MessagesFilter();
+
+        byte priority = 5;
+        filter.setPriority(priority);
+
+        assertThat(filter.priority).isEqualTo(priority);
+    }
+
+    @Test
+    public void setReadStatus() {
+        MessagesFilter filter = new MessagesFilter();
+
+        byte readStatus = 5;
+        filter.setReadStatus(readStatus);
+
+        assertThat(filter.readStatus).isEqualTo(readStatus);
+    }
+
+    @Test
+    public void setRecipient() {
+        MessagesFilter filter = new MessagesFilter();
+
+        String recipient = "test_originator";
+        filter.setRecipient(recipient);
+        assertThat(filter.recipient).isEqualTo(recipient);
+
+        filter.setRecipient("");
+        assertThat(filter.recipient).isEqualTo(null); // Empty string is stored as null
+
+        filter.setRecipient(null);
+        assertThat(filter.recipient).isEqualTo(null);
+    }
+
+}
diff --git a/android/app/tests/unit/src/com/android/bluetooth/mapclient/MessagesListingTest.java b/android/app/tests/unit/src/com/android/bluetooth/mapclient/MessagesListingTest.java
new file mode 100644
index 0000000..48ece84
--- /dev/null
+++ b/android/app/tests/unit/src/com/android/bluetooth/mapclient/MessagesListingTest.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2023 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 com.google.common.truth.Truth.assertThat;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.ByteArrayInputStream;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class MessagesListingTest {
+
+    @Test
+    public void constructor() {
+        String handle = "FFAB";
+        String subject = "test_subject";
+        final StringBuilder xml = new StringBuilder();
+        xml.append("<msg\n");
+        xml.append("handle=\"" + handle + "\"\n");
+        xml.append("subject=\"" + subject + "\"\n");
+        xml.append("/>\n");
+        ByteArrayInputStream stream = new ByteArrayInputStream(xml.toString().getBytes());
+
+        MessagesListing listing = new MessagesListing(stream);
+
+        assertThat(listing.getList()).hasSize(1);
+        Message msg = listing.getList().get(0);
+        assertThat(msg.getHandle()).isEqualTo(handle);
+        assertThat(msg.getSubject()).isEqualTo(subject);
+    }
+}
diff --git a/android/app/tests/unit/src/com/android/bluetooth/mapclient/MnsObexServerTest.java b/android/app/tests/unit/src/com/android/bluetooth/mapclient/MnsObexServerTest.java
new file mode 100644
index 0000000..e57ab50
--- /dev/null
+++ b/android/app/tests/unit/src/com/android/bluetooth/mapclient/MnsObexServerTest.java
@@ -0,0 +1,169 @@
+/*
+ * Copyright 2022 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 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.doThrow;
+import static org.mockito.Mockito.when;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+
+import android.os.Handler;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.obex.HeaderSet;
+import com.android.obex.Operation;
+import com.android.obex.ResponseCodes;
+
+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 org.mockito.Spy;
+
+import java.io.ByteArrayInputStream;
+import java.io.DataInputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class MnsObexServerTest {
+
+    @Mock
+    MceStateMachine mStateMachine;
+
+    MnsObexServer mServer;
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+        mServer = new MnsObexServer(mStateMachine, null);
+    }
+
+    @Test
+    public void onConnect_whenUuidIsWrong() {
+        byte[] wrongUuid = new byte[]{};
+        HeaderSet request = new HeaderSet();
+        request.setHeader(HeaderSet.TARGET, wrongUuid);
+        HeaderSet reply = new HeaderSet();
+
+        assertThat(mServer.onConnect(request, reply))
+                .isEqualTo(ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE);
+    }
+
+    @Test
+    public void onConnect_withCorrectUuid() throws Exception {
+        HeaderSet request = new HeaderSet();
+        request.setHeader(HeaderSet.TARGET, MnsObexServer.MNS_TARGET);
+        HeaderSet reply = new HeaderSet();
+
+        assertThat(mServer.onConnect(request, reply)).isEqualTo(ResponseCodes.OBEX_HTTP_OK);
+        assertThat(reply.getHeader(HeaderSet.WHO)).isEqualTo(MnsObexServer.MNS_TARGET);
+    }
+
+    @Test
+    public void onDisconnect_callsStateMachineDisconnect() {
+        HeaderSet request = new HeaderSet();
+        HeaderSet reply = new HeaderSet();
+
+        mServer.onDisconnect(request, reply);
+
+        verify(mStateMachine).disconnect();
+    }
+
+    @Test
+    public void onGet_returnsBadRequest() {
+        Operation op = mock(Operation.class);
+
+        assertThat(mServer.onGet(op)).isEqualTo(ResponseCodes.OBEX_HTTP_BAD_REQUEST);
+    }
+
+    @Test
+    public void onPut_whenTypeIsInvalid_returnsBadRequest() throws Exception {
+        HeaderSet headerSet = new HeaderSet();
+        headerSet.setHeader(HeaderSet.TYPE, "some_invalid_type");
+        Operation op = mock(Operation.class);
+        when(op.getReceivedHeader()).thenReturn(headerSet);
+
+        assertThat(mServer.onPut(op)).isEqualTo(ResponseCodes.OBEX_HTTP_BAD_REQUEST);
+    }
+
+    @Test
+    public void onPut_whenHeaderSetIsValid_returnsOk() throws Exception {
+        final StringBuilder xml = new StringBuilder();
+        xml.append("<event\n");
+        xml.append("    type=\"test_type\"\n");
+        xml.append("    handle=\"FFAB\"\n");
+        xml.append("    folder=\"test_folder\"\n");
+        xml.append("    old_folder=\"test_old_folder\"\n");
+        xml.append("    msg_type=\"MMS\"\n");
+        xml.append("/>\n");
+        DataInputStream stream = new DataInputStream(
+                new ByteArrayInputStream(xml.toString().getBytes()));
+
+        byte[] applicationParameter = new byte[] {
+                Request.OAP_TAGID_MAS_INSTANCE_ID,
+                1, // length in byte
+                (byte) 55
+        };
+
+        HeaderSet headerSet = new HeaderSet();
+        headerSet.setHeader(HeaderSet.TYPE, MnsObexServer.TYPE);
+        headerSet.setHeader(HeaderSet.APPLICATION_PARAMETER, applicationParameter);
+
+        Operation op = mock(Operation.class);
+        when(op.getReceivedHeader()).thenReturn(headerSet);
+        when(op.openDataInputStream()).thenReturn(stream);
+
+        assertThat(mServer.onPut(op)).isEqualTo(ResponseCodes.OBEX_HTTP_OK);
+
+        verify(mStateMachine).receiveEvent(any());
+    }
+
+    @Test
+    public void onAbort_returnsNotImplemented() {
+        HeaderSet request = new HeaderSet();
+        HeaderSet reply = new HeaderSet();
+
+        assertThat(mServer.onAbort(request, reply))
+                .isEqualTo(ResponseCodes.OBEX_HTTP_NOT_IMPLEMENTED);
+    }
+
+    @Test
+    public void onSetPath_returnsBadRequest() {
+        HeaderSet request = new HeaderSet();
+        HeaderSet reply = new HeaderSet();
+
+        assertThat(mServer.onSetPath(request, reply, false, false))
+                .isEqualTo(ResponseCodes.OBEX_HTTP_BAD_REQUEST);
+    }
+
+    @Test
+    public void onClose_doesNotCrash() {
+        mServer.onClose();
+    }
+}
diff --git a/android/app/tests/unit/src/com/android/bluetooth/mcp/MediaControlGattServiceTest.java b/android/app/tests/unit/src/com/android/bluetooth/mcp/MediaControlGattServiceTest.java
index ff7406a..d840126 100644
--- a/android/app/tests/unit/src/com/android/bluetooth/mcp/MediaControlGattServiceTest.java
+++ b/android/app/tests/unit/src/com/android/bluetooth/mcp/MediaControlGattServiceTest.java
@@ -90,6 +90,7 @@
         mAdapter = BluetoothAdapter.getDefaultAdapter();
 
         doReturn(true).when(mMockGattServer).addService(any(BluetoothGattService.class));
+        doReturn(new BluetoothDevice[0]).when(mAdapterService).getBondedDevices();
 
         mMcpService = new MediaControlGattService(mMockMcpService, mMockMcsCallbacks, TEST_CCID);
         mMcpService.setBluetoothGattServerForTesting(mMockGattServer);
@@ -122,7 +123,7 @@
         List<BluetoothDevice> devices = new ArrayList<BluetoothDevice>();
         devices.add(mCurrentDevice);
         doReturn(devices).when(mMockGattServer).getConnectedDevices();
-        mMcpService.setCcc(mCurrentDevice, characteristic.getUuid(), 0, value);
+        mMcpService.setCcc(mCurrentDevice, characteristic.getUuid(), 0, value, true);
     }
 
     @Test
diff --git a/android/app/tests/unit/src/com/android/bluetooth/opp/BluetoothOppLauncherActivityTest.java b/android/app/tests/unit/src/com/android/bluetooth/opp/BluetoothOppLauncherActivityTest.java
index e16e310..d9dbbb1 100644
--- a/android/app/tests/unit/src/com/android/bluetooth/opp/BluetoothOppLauncherActivityTest.java
+++ b/android/app/tests/unit/src/com/android/bluetooth/opp/BluetoothOppLauncherActivityTest.java
@@ -33,6 +33,7 @@
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.doThrow;
 import static org.mockito.Mockito.spy;
@@ -187,7 +188,6 @@
         assertThat(file.length()).isGreaterThan(shareContent.length());
     }
 
-    @Ignore("b/263754734")
     @Test
     public void sendFileInfo_finishImmediately() throws Exception {
         doReturn(true).when(mMethodProxy).bluetoothAdapterIsEnabled(any());
@@ -195,7 +195,7 @@
         mIntent.setAction("unsupported-action");
         ActivityScenario<BluetoothOppLauncherActivity> scenario = ActivityScenario.launch(mIntent);
         doThrow(new IllegalArgumentException()).when(mBluetoothOppManager).saveSendingFileInfo(
-                any(), any(String.class), any(), any());
+                any(), any(String.class), anyBoolean(), anyBoolean());
         scenario.onActivity(activity -> {
             activity.sendFileInfo("text/plain", "content:///abc.txt", false, false);
         });
@@ -208,18 +208,4 @@
         Thread.sleep(2_000);
         assertThat(activityScenario.getState()).isEqualTo(state);
     }
-
-
-    private void enableActivity(boolean enable) {
-        int enabledState = enable ? COMPONENT_ENABLED_STATE_ENABLED
-                : COMPONENT_ENABLED_STATE_DEFAULT;
-
-        mTargetContext.getPackageManager().setApplicationEnabledSetting(
-                mTargetContext.getPackageName(), enabledState, DONT_KILL_APP);
-
-        ComponentName activityName = new ComponentName(mTargetContext,
-                BluetoothOppLauncherActivity.class);
-        mTargetContext.getPackageManager().setComponentEnabledSetting(
-                activityName, enabledState, DONT_KILL_APP);
-    }
 }
diff --git a/android/app/tests/unit/src/com/android/bluetooth/opp/BluetoothOppTransferHistoryTest.java b/android/app/tests/unit/src/com/android/bluetooth/opp/BluetoothOppTransferHistoryTest.java
index e8fd829..56c5621 100644
--- a/android/app/tests/unit/src/com/android/bluetooth/opp/BluetoothOppTransferHistoryTest.java
+++ b/android/app/tests/unit/src/com/android/bluetooth/opp/BluetoothOppTransferHistoryTest.java
@@ -48,6 +48,7 @@
 
 import org.junit.After;
 import org.junit.Before;
+import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
@@ -166,6 +167,7 @@
                 matches(isDisplayed()));
     }
 
+    @Ignore("b/268424815")
     @Test
     public void onOptionsItemSelected_clearAllSelected_promptWarning() {
         BluetoothOppTestUtils.setUpMockCursor(mCursor, mCursorMockDataList);
diff --git a/android/app/tests/unit/src/com/android/bluetooth/opp/BluetoothOppTransferTest.java b/android/app/tests/unit/src/com/android/bluetooth/opp/BluetoothOppTransferTest.java
index 35042b4..f1e2a77 100644
--- a/android/app/tests/unit/src/com/android/bluetooth/opp/BluetoothOppTransferTest.java
+++ b/android/app/tests/unit/src/com/android/bluetooth/opp/BluetoothOppTransferTest.java
@@ -21,6 +21,7 @@
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.mockito.ArgumentMatchers.argThat;
+import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.ArgumentMatchers.nullable;
 import static org.mockito.Mockito.any;
 import static org.mockito.Mockito.doAnswer;
@@ -28,10 +29,15 @@
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
 
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothManager;
+import android.bluetooth.BluetoothUuid;
 import android.content.ContentValues;
 import android.content.Context;
 import android.content.ContextWrapper;
+import android.content.Intent;
 import android.content.IntentFilter;
 import android.net.Uri;
 import android.os.Looper;
@@ -51,7 +57,8 @@
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
-import org.mockito.Spy;
+
+import java.util.Objects;
 
 @MediumTest
 @RunWith(AndroidJUnit4.class)
@@ -72,8 +79,8 @@
 
     @Mock
     BluetoothOppObexSession mSession;
-    @Spy
-    BluetoothMethodProxy mCallProxy = BluetoothMethodProxy.getInstance();
+    @Mock
+    BluetoothMethodProxy mCallProxy;
     Context mContext;
     BluetoothOppBatch mBluetoothOppBatch;
     BluetoothOppTransfer mTransfer;
@@ -257,4 +264,84 @@
         verify(mContext).sendBroadcast(argThat(
                 arg -> arg.getAction().equals(BluetoothShare.USER_CONFIRMATION_TIMEOUT_ACTION)));
     }
+
+    @Test
+    public void socketConnectThreadConstructors() {
+        String address = "AA:BB:CC:EE:DD:11";
+        BluetoothDevice device = (mContext.getSystemService(BluetoothManager.class))
+                .getAdapter().getRemoteDevice(address);
+        BluetoothOppTransfer transfer = new BluetoothOppTransfer(mContext, mBluetoothOppBatch);
+        BluetoothOppTransfer.SocketConnectThread socketConnectThread =
+                transfer.new SocketConnectThread(device, true);
+        BluetoothOppTransfer.SocketConnectThread socketConnectThread2 =
+                transfer.new SocketConnectThread(device, true, false, 0);
+        assertThat(Objects.equals(socketConnectThread.mDevice, device)).isTrue();
+        assertThat(Objects.equals(socketConnectThread2.mDevice, device)).isTrue();
+    }
+
+    @Test
+    public void socketConnectThreadInterrupt() {
+        String address = "AA:BB:CC:EE:DD:11";
+        BluetoothDevice device = (mContext.getSystemService(BluetoothManager.class))
+                .getAdapter().getRemoteDevice(address);
+        BluetoothOppTransfer transfer = new BluetoothOppTransfer(mContext, mBluetoothOppBatch);
+        BluetoothOppTransfer.SocketConnectThread socketConnectThread =
+                transfer.new SocketConnectThread(device, true);
+        socketConnectThread.interrupt();
+        assertThat(socketConnectThread.mIsInterrupted).isTrue();
+    }
+
+    @Test
+    @SuppressWarnings("DoNotCall")
+    public void socketConnectThreadRun_bluetoothDisabled_connectionFailed() {
+        String address = "AA:BB:CC:EE:DD:11";
+        BluetoothDevice device = (mContext.getSystemService(BluetoothManager.class))
+                .getAdapter().getRemoteDevice(address);
+        BluetoothOppTransfer transfer = new BluetoothOppTransfer(mContext, mBluetoothOppBatch);
+        BluetoothOppTransfer.SocketConnectThread socketConnectThread =
+                transfer.new SocketConnectThread(device, true);
+        transfer.mSessionHandler = mEventHandler;
+
+        socketConnectThread.run();
+        verify(mCallProxy).handlerSendEmptyMessage(any(), eq(TRANSPORT_ERROR));
+    }
+
+    @Test
+    public void oppConnectionReceiver_onReceiveWithActionAclDisconnected_sendsConnectTimeout() {
+        BluetoothDevice device = (mContext.getSystemService(BluetoothManager.class))
+                .getAdapter().getRemoteDevice(mDestination);
+        BluetoothOppTransfer transfer = new BluetoothOppTransfer(mContext, mBluetoothOppBatch);
+        transfer.mCurrentShare = mInitShareInfo;
+        transfer.mCurrentShare.mConfirm = BluetoothShare.USER_CONFIRMATION_PENDING;
+        BluetoothOppTransfer.OppConnectionReceiver receiver = transfer.new OppConnectionReceiver();
+        Intent intent = new Intent();
+        intent.setAction(BluetoothDevice.ACTION_ACL_DISCONNECTED);
+        intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
+
+        transfer.mSessionHandler = mEventHandler;
+        receiver.onReceive(mContext, intent);
+        verify(mCallProxy).handlerSendEmptyMessage(any(),
+                eq(BluetoothOppObexSession.MSG_CONNECT_TIMEOUT));
+    }
+
+    @Test
+    public void oppConnectionReceiver_onReceiveWithActionSdpRecord_sendsNoMessage() {
+        BluetoothDevice device = (mContext.getSystemService(BluetoothManager.class))
+                .getAdapter().getRemoteDevice(mDestination);
+        BluetoothOppTransfer transfer = new BluetoothOppTransfer(mContext, mBluetoothOppBatch);
+        transfer.mCurrentShare = mInitShareInfo;
+        transfer.mCurrentShare.mConfirm = BluetoothShare.USER_CONFIRMATION_PENDING;
+        transfer.mDevice = device;
+        transfer.mSessionHandler = mEventHandler;
+        BluetoothOppTransfer.OppConnectionReceiver receiver = transfer.new OppConnectionReceiver();
+        Intent intent = new Intent();
+        intent.setAction(BluetoothDevice.ACTION_SDP_RECORD);
+        intent.putExtra(BluetoothDevice.EXTRA_UUID, BluetoothUuid.OBEX_OBJECT_PUSH);
+        intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
+
+        receiver.onReceive(mContext, intent);
+
+        // bluetooth device name is null => skip without interaction
+        verifyNoMoreInteractions(mCallProxy);
+    }
 }
diff --git a/android/app/tests/unit/src/com/android/bluetooth/pbap/BluetoothPbapUtilsTest.java b/android/app/tests/unit/src/com/android/bluetooth/pbap/BluetoothPbapUtilsTest.java
index 6ea294f..875042f 100644
--- a/android/app/tests/unit/src/com/android/bluetooth/pbap/BluetoothPbapUtilsTest.java
+++ b/android/app/tests/unit/src/com/android/bluetooth/pbap/BluetoothPbapUtilsTest.java
@@ -48,7 +48,6 @@
 
 import org.junit.After;
 import org.junit.Before;
-import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
@@ -78,13 +77,13 @@
         BluetoothMethodProxy.setInstanceForTesting(mProxy);
 
         when(mContext.getResources()).thenReturn(mResources);
-        initStaticFields();
+        clearStaticFields();
     }
 
     @After
     public void tearDown() {
         BluetoothMethodProxy.setInstanceForTesting(null);
-        initStaticFields();
+        clearStaticFields();
     }
 
     @Test
@@ -310,15 +309,11 @@
         }
     }
 
-    @Ignore("b/262486295")
     @Test
     public void updateSecondaryVersionCounter_whenContactsAreUpdated() {
         MatrixCursor contactCursor = new MatrixCursor(
                 new String[] {Contacts._ID, Contacts.CONTACT_LAST_UPDATED_TIMESTAMP});
         contactCursor.addRow(new Object[] {"id1", Calendar.getInstance().getTimeInMillis()});
-        contactCursor.addRow(new Object[] {"id2", Calendar.getInstance().getTimeInMillis()});
-        contactCursor.addRow(new Object[] {"id3", Calendar.getInstance().getTimeInMillis()});
-        contactCursor.addRow(new Object[] {"id4", Calendar.getInstance().getTimeInMillis()});
         doReturn(contactCursor).when(mProxy).contentResolverQuery(
                 any(), eq(Contacts.CONTENT_URI), any(), any(), any(), any());
 
@@ -326,31 +321,28 @@
         dataCursor.addRow(new Object[] {"id1", Phone.CONTENT_ITEM_TYPE, "01234567"});
         dataCursor.addRow(new Object[] {"id1", Email.CONTENT_ITEM_TYPE, "android@android.com"});
         dataCursor.addRow(new Object[] {"id1", StructuredPostal.CONTENT_ITEM_TYPE, "01234"});
-        dataCursor.addRow(new Object[] {"id2", StructuredName.CONTENT_ITEM_TYPE, "And Roid"});
+        dataCursor.addRow(new Object[] {"id1", StructuredName.CONTENT_ITEM_TYPE, "And Roid"});
         doReturn(dataCursor).when(mProxy).contentResolverQuery(
                 any(), eq(Data.CONTENT_URI), any(), any(), any(), any());
+        assertThat(BluetoothPbapUtils.sSecondaryVersionCounter).isEqualTo(0);
 
-        HandlerThread handlerThread = new HandlerThread("BluetoothPbapUtilsTest");
-        handlerThread.start();
-        Handler handler = new Handler(handlerThread.getLooper());
+        BluetoothPbapUtils.sTotalContacts = 1;
+        BluetoothPbapUtils.setContactFields(BluetoothPbapUtils.TYPE_NAME, "id1",
+                "test_previous_name_before_update");
 
-        try {
-            BluetoothPbapUtils.sTotalContacts = 4;
+        BluetoothPbapUtils.updateSecondaryVersionCounter(mContext, null);
 
-            BluetoothPbapUtils.updateSecondaryVersionCounter(mContext, handler);
-
-            assertThat(BluetoothPbapUtils.sContactDataset).isNotEmpty();
-        } finally {
-            handlerThread.quit();
-        }
+        assertThat(BluetoothPbapUtils.sSecondaryVersionCounter).isEqualTo(1);
     }
 
-    private static void initStaticFields() {
+    private static void clearStaticFields() {
         BluetoothPbapUtils.sPrimaryVersionCounter = 0;
         BluetoothPbapUtils.sSecondaryVersionCounter = 0;
         BluetoothPbapUtils.sContactSet.clear();
+        BluetoothPbapUtils.sContactDataset.clear();
         BluetoothPbapUtils.sTotalContacts = 0;
         BluetoothPbapUtils.sTotalFields = 0;
         BluetoothPbapUtils.sTotalSvcFields = 0;
+        BluetoothPbapUtils.sContactsLastUpdated = 0;
     }
 }
diff --git a/android/app/tests/unit/src/com/android/bluetooth/telephony/BluetoothInCallServiceTest.java b/android/app/tests/unit/src/com/android/bluetooth/telephony/BluetoothInCallServiceTest.java
index 9cd29f4..9332eea 100644
--- a/android/app/tests/unit/src/com/android/bluetooth/telephony/BluetoothInCallServiceTest.java
+++ b/android/app/tests/unit/src/com/android/bluetooth/telephony/BluetoothInCallServiceTest.java
@@ -1354,6 +1354,66 @@
         Assert.assertFalse(mBluetoothInCallService.mOnCreateCalled);
     }
 
+    @Test
+    public void testLeCallControlCallback_onAcceptCall_withUnknownCallId() {
+        BluetoothLeCallControlProxy callControlProxy = mock(BluetoothLeCallControlProxy.class);
+        mBluetoothInCallService.mBluetoothLeCallControl = callControlProxy;
+        BluetoothLeCallControl.Callback callback =
+                mBluetoothInCallService.mBluetoothLeCallControlCallback;
+
+        int requestId = 1;
+        UUID unknownCallId = UUID.randomUUID();
+        callback.onAcceptCall(requestId, unknownCallId);
+
+        verify(callControlProxy).requestResult(
+                requestId, BluetoothLeCallControl.RESULT_ERROR_UNKNOWN_CALL_ID);
+    }
+
+    @Test
+    public void testLeCallControlCallback_onTerminateCall_withUnknownCallId() {
+        BluetoothLeCallControlProxy callControlProxy = mock(BluetoothLeCallControlProxy.class);
+        mBluetoothInCallService.mBluetoothLeCallControl = callControlProxy;
+        BluetoothLeCallControl.Callback callback =
+                mBluetoothInCallService.mBluetoothLeCallControlCallback;
+
+        int requestId = 1;
+        UUID unknownCallId = UUID.randomUUID();
+        callback.onTerminateCall(requestId, unknownCallId);
+
+        verify(callControlProxy).requestResult(
+                requestId, BluetoothLeCallControl.RESULT_ERROR_UNKNOWN_CALL_ID);
+    }
+
+    @Test
+    public void testLeCallControlCallback_onHoldCall_withUnknownCallId() {
+        BluetoothLeCallControlProxy callControlProxy = mock(BluetoothLeCallControlProxy.class);
+        mBluetoothInCallService.mBluetoothLeCallControl = callControlProxy;
+        BluetoothLeCallControl.Callback callback =
+                mBluetoothInCallService.mBluetoothLeCallControlCallback;
+
+        int requestId = 1;
+        UUID unknownCallId = UUID.randomUUID();
+        callback.onHoldCall(requestId, unknownCallId);
+
+        verify(callControlProxy).requestResult(
+                requestId, BluetoothLeCallControl.RESULT_ERROR_UNKNOWN_CALL_ID);
+    }
+
+    @Test
+    public void testLeCallControlCallback_onUnholdCall_withUnknownCallId() {
+        BluetoothLeCallControlProxy callControlProxy = mock(BluetoothLeCallControlProxy.class);
+        mBluetoothInCallService.mBluetoothLeCallControl = callControlProxy;
+        BluetoothLeCallControl.Callback callback =
+                mBluetoothInCallService.mBluetoothLeCallControlCallback;
+
+        int requestId = 1;
+        UUID unknownCallId = UUID.randomUUID();
+        callback.onUnholdCall(requestId, unknownCallId);
+
+        verify(callControlProxy).requestResult(
+                requestId, BluetoothLeCallControl.RESULT_ERROR_UNKNOWN_CALL_ID);
+    }
+
     private void addCallCapability(BluetoothCall call, int capability) {
         when(call.can(capability)).thenReturn(true);
     }
diff --git a/android/pandora/mmi2grpc/mmi2grpc/avrcp.py b/android/pandora/mmi2grpc/mmi2grpc/avrcp.py
index 7e1d9c2..92972dc 100644
--- a/android/pandora/mmi2grpc/mmi2grpc/avrcp.py
+++ b/android/pandora/mmi2grpc/mmi2grpc/avrcp.py
@@ -793,3 +793,169 @@
         self.mediaplayer.Play()
 
         return "OK"
+
+    @assert_description
+    def _mmi_1016(self, test: str, pts_addr: bytes, **kwargs):
+        """
+        Create an AVDTP signaling channel.
+
+        Action: Create an audio or video
+        connection with PTS.
+        """
+        self.connection = self.host.Connect(address=pts_addr).connection
+        if "TG" in test:
+            try:
+                self.source = self.a2dp.OpenSource(connection=self.connection).source
+            except RpcError:
+                pass
+        else:
+            try:
+                self.sink = self.a2dp.WaitSink(connection=self.connection).sink
+            except RpcError:
+                pass
+
+        return "OK"
+
+    @assert_description
+    def TSC_AVCTP_mmi_send_AVCT_ConnectReq(self, pts_addr: bytes, **kwargs):
+        """
+        Using the Upper Tester, send an AVCT_ConnectReq command to the IUT with
+        the following input parameter values:
+           * BD_ADDR = BD_ADDRLower_Tester
+        * PID = PIDTest_System
+
+        The IUT should then initiate an
+        L2CAP_ConnectReq.
+        """
+
+        return "OK"
+
+    @assert_description
+    def TSC_AVCTP_mmi_verify_ConnectCfm_CB(self, pts_addr: bytes, **kwargs):
+        """
+        Press 'OK' if the following conditions were met :
+
+        1. The IUT returns
+        the following AVCT_ConnectReq output parameters to the Upper Tester:
+        * Result = 0x0000 (Event successfully registered)
+
+        2. The IUT calls the
+        ConnectCfm_CBTest_System function in the Upper Tester with the following
+        parameters:
+           * BD_ADDR = BD_ADDRLower_Tester
+           * Connect Result =
+        0x0000 (L2CAP Connect Request successful)
+           * Config Result = 0x0000
+        (L2CAP Configure successful)
+           * Status = L2CAP Connect Request Status
+        """
+
+        return "OK"
+
+    @assert_description
+    def TSC_AVCTP_mmi_register_DisconnectCfm_CB(self, pts_addr: bytes, **kwargs):
+        """
+        Using the Upper Tester register the function DisconnectCfm_CBTest_System
+        for callback on the AVCT_Disconnect_Cfm event by sending an
+        AVCT_EventRegistration command to the IUT with the following parameter
+        values:
+           * Event = AVCT_Disconnect_Cfm
+           * Callback =
+        DisconnectCfm_CBTest_System
+           * PID = PIDTest_System
+
+        Press 'OK' to
+        continue once the IUT has responded.
+        """
+
+        return "OK"
+
+    def TSC_AVCTP_mmi_send_AVCT_Disconnect_Req(self, test: str, pts_addr: bytes, **kwargs):
+        """
+        Using the Upper Tester send an AVCT_DisconnectReq command to the IUT
+        with the following parameter values:
+           * BD_ADDR = BD_ADDRLower_Tester
+        * PID = PIDTest_System
+
+        The IUT should then initiate an
+        L2CAP_DisconnectReq.   
+        """
+        # Currently disconnect is required in TG role
+        if "TG" in test:
+            if self.connection is None:
+                self.connection = self.host.GetConnection(address=pts_addr).connection
+            time.sleep(3)
+            self.host.Disconnect(connection=self.connection)
+            self.connection = None
+
+        return "OK"
+
+    @assert_description
+    def TSC_AVCTP_mmi_verify_DisconnectCfm_CB(self, **kwargs):
+        """
+        Press 'OK' if the following conditions were met :
+
+        1. The IUT returns
+        the following AVCT_EventRegistration output parameters to the Upper
+        Tester:
+           * Result = 0x0000 (Event successfully registered)
+
+        2. The IUT
+        calls the DisconnectCfm_CBTest_System function in the Upper Tester with
+        the following parameter values:
+           * BD_ADDR = BD_ADDRLower_Tester
+           *
+        Disconnect Result = 0x0000 (L2CAP disconnect success)
+
+        3. The IUT
+        returns the following AVCT_DisconnectReq output parameter values to the
+        Upper Tester:
+           * RSP = 0x0000 (Request accepted)
+        """
+
+        return "OK"
+
+    @assert_description
+    def TSC_AVCTP_mmi_send_AVCT_SendMessage_TG(self, **kwargs):
+        """
+        Upon a call to the call back function MessageInd_CBTest_System, use the
+        Upper Tester to send an AVCT_SendMessage command to the IUT with the
+        following parameter values:
+           * BD_ADDR = BD_ADDRTest_System
+           *
+        Transaction = TRANSTest_System
+           * Type = CRTest_System = 1 (Response
+        Message)
+           * PID = PIDTest_System
+           * Data = ADDRESSdata_buffer
+        (Buffer containing DATA[]Upper_Tester)
+           * Length =
+        LengthOf(DATA[]Upper_Tester) <= MTU – 3bytes
+        """
+
+        return "OK"
+
+    @assert_description
+    def TSC_AVCTP_mmi_verify_MessageInd_CB_TG(self, **kwargs):
+        """
+        Press 'OK' if the following conditions were met :
+
+        1. The
+        MessageInd_CBTest_System function in the Upper Tester is called with the
+        following parameters:
+           * BD_ADDR = BD_ADDRLower_Tester
+           *
+        Transaction = TRANSTest_System
+           * Type = 0x00 (Command message)
+           *
+        Data = ADDRESSdata_buffer (Buffer containing DATA[]Lower_Tester)
+           *
+        Length = LengthOf(DATA[]Lower_Tester)
+
+        2. the IUT returns the following
+        AVCT_SendMessage output parameters to the Upper Tester:
+           * Result =
+        0x0000 (Request accepted)
+        """
+
+        return "OK"
diff --git a/android/pandora/mmi2grpc/mmi2grpc/hfp.py b/android/pandora/mmi2grpc/mmi2grpc/hfp.py
index f6ce7c9..1cf2227 100644
--- a/android/pandora/mmi2grpc/mmi2grpc/hfp.py
+++ b/android/pandora/mmi2grpc/mmi2grpc/hfp.py
@@ -18,7 +18,7 @@
 
 from pandora_experimental.hfp_grpc import HFP
 from pandora_experimental.host_grpc import Host
-from pandora_experimental.host_pb2 import ConnectabilityMode
+from pandora_experimental.host_pb2 import ConnectabilityMode, DiscoverabilityMode
 from pandora_experimental.security_grpc import Security, SecurityStorage
 from pandora_experimental.hfp_pb2 import AudioPath
 
@@ -96,7 +96,10 @@
                 if not self.connection:
                     self.connection = self.host.Connect(address=pts_addr).connection
 
-            self.hfp.EnableSlc(connection=self.connection)
+            if "HFP/HF" in test:
+                self.hfp.EnableSlcAsHandsfree(connection=self.connection)
+            else:
+                self.hfp.EnableSlc(connection=self.connection)
 
         threading.Thread(target=enable_slc).start()
 
@@ -137,7 +140,7 @@
         return "OK"
 
     @assert_description
-    def TSC_iut_disable_slc(self, pts_addr: bytes, **kwargs):
+    def TSC_iut_disable_slc(self, test: str, pts_addr: bytes, **kwargs):
         """
         Click Ok, then disable the service level connection using the
         Implementation Under Test (IUT).
@@ -147,7 +150,10 @@
 
         def disable_slc():
             time.sleep(2)
-            self.hfp.DisableSlc(connection=self.connection)
+            if "HFP/HF" in test:
+                self.hfp.DisableSlcAsHandsfree(connection=self.connection)
+            else:
+                self.hfp.DisableSlc(connection=self.connection)
 
         threading.Thread(target=disable_slc).start()
 
@@ -225,16 +231,21 @@
         return "OK"
 
     @assert_description
-    def TSC_iut_disable_audio(self, **kwargs):
+    def TSC_iut_disable_audio(self, test: str, pts_addr: bytes, **kwargs):
         """
         Click Ok, then close the audio connection (SCO) between the
         Implementation Under Test (IUT) and the PTS.  Do not close the serivice
         level connection (SLC) or power-off the IUT.
         """
 
+        self.connection = self.host.GetConnection(address=pts_addr).connection
+
         def disable_audio():
             time.sleep(2)
-            self.hfp.SetAudioPath(audio_path=AudioPath.AUDIO_PATH_SPEAKERS)
+            if "HFP/HF" in test:
+                self.hfp.DisconnectToAudioAsHandsfree(connection=self.connection)
+            else:
+                self.hfp.SetAudioPath(audio_path=AudioPath.AUDIO_PATH_SPEAKERS)
 
         threading.Thread(target=disable_audio).start()
 
@@ -249,15 +260,20 @@
         return "OK"
 
     @assert_description
-    def TSC_iut_enable_audio(self, **kwargs):
+    def TSC_iut_enable_audio(self, test: str, pts_addr: bytes, **kwargs):
         """
         Click Ok, then initiate an audio connection (SCO) from the
         Implementation Under Test (IUT) to the PTS.
         """
 
+        self.connection = self.host.GetConnection(address=pts_addr).connection
+
         def enable_audio():
             time.sleep(2)
-            self.hfp.SetAudioPath(audio_path=AudioPath.AUDIO_PATH_HANDSFREE)
+            if "HFP/HF" in test:
+                self.hfp.ConnectToAudioAsHandsfree(connection=self.connection)
+            else:
+                self.hfp.SetAudioPath(audio_path=AudioPath.AUDIO_PATH_HANDSFREE)
 
         threading.Thread(target=enable_audio).start()
 
@@ -579,16 +595,26 @@
         return "OK"
 
     @assert_description
-    def TSC_prepare_iut_for_vra(self, pts_addr: bytes, **kwargs):
+    def TSC_prepare_iut_for_vra(self, pts_addr: bytes, test: str, **kwargs):
         """
         Place the Implementation Under Test (IUT) in a state which will allow a
         request from the PTS to activate voice recognition, then click Ok.
         """
 
-        self.hfp.SetVoiceRecognition(
-            enabled=True,
-            connection=self.host.GetConnection(address=pts_addr).connection,
-        )
+        if "HFP/HF" not in test:
+            self.hfp.SetVoiceRecognition(
+                enabled=True,
+                connection=self.host.GetConnection(address=pts_addr).connection,
+            )
+
+        return "OK"
+
+    @assert_description
+    def TSC_prepare_iut_for_vrd(self, **kwargs):
+        """
+        Place the Implementation Under Test (IUT) in a state which will allow a
+        voice recognition deactivation from PTS, then click Ok.
+        """
 
         return "OK"
 
@@ -604,20 +630,285 @@
         return "OK"
 
     @assert_description
-    def TSC_reject_call(self, **kwargs):
+    def TSC_reject_call(self, test: str, pts_addr: bytes, **kwargs):
         """
         Click Ok, then reject the incoming call using the Implemention Under
         Test (IUT).
         """
 
+        self.connection = self.host.GetConnection(address=pts_addr).connection
+
         def reject_call():
             time.sleep(2)
-            self.hfp.DeclineCall()
+            if "HFP/HF" in test:
+                self.hfp.DeclineCallAsHandsfree(connection=self.connection)
+            else:
+                self.hfp.DeclineCall()
 
         threading.Thread(target=reject_call).start()
 
         return "OK"
 
+    @assert_description
+    def TSC_hf_iut_answer_call(self, pts_addr: bytes, **kwargs):
+        """
+        Click Ok, then answer the incoming call using the Implementation Under
+        Test (IUT).
+        """
+
+        self.connection = self.host.GetConnection(address=pts_addr).connection
+
+        def answer_call():
+            time.sleep(2)
+            self.hfp.AnswerCallAsHandsfree(connection=self.connection)
+
+        threading.Thread(target=answer_call).start()
+
+        return "OK"
+
+    @assert_description
+    def TSC_iut_disable_audio_poweroff_ok(self, **kwargs):
+        """
+        Click Ok, then close the audio connection (SCO) by one of the following
+        ways:
+
+        1. Close the service level connection (SLC)
+        2. Powering off the
+        Implementation Under Test (IUT)
+        """
+
+        self.host.Reset()
+
+        return "OK"
+
+    @assert_description
+    def TSC_verify_inband_ring(self, **kwargs):
+        """
+        Verify that the in-band ringtone is audible, then click Ok.
+        """
+
+        return "OK"
+
+    @assert_description
+    def TSC_verify_inband_ring_muting(self, **kwargs):
+        """
+        Verify that the in-band ringtone is not audible , then click Ok.
+        """
+
+        return "OK"
+
+    @assert_description
+    def TSC_hf_iut_disable_call(self, pts_addr: bytes, **kwargs):
+        """
+        Click Ok, then end the call process from the Implementation Under Test
+        (IUT).
+        """
+
+        self.connection = self.host.GetConnection(address=pts_addr).connection
+
+        def disable_call():
+            time.sleep(2)
+            self.hfp.EndCallAsHandsfree(connection=self.connection)
+
+        threading.Thread(target=disable_call).start()
+
+        return "OK"
+
+    @assert_description
+    def TSC_mute_inband_ring_iut(self, **kwargs):
+        """
+        Mute the in-band ringtone on the Implementation Under Test (IUT) and
+        then click OK.
+        """
+
+        return "OK"
+
+    @assert_description
+    def TSC_verify_iut_alerting(self, **kwargs):
+        """
+        Verify that the Implementation Under Test (IUT) is generating a local
+        alert, then click Ok.
+        """
+
+        return "OK"
+
+    @assert_description
+    def TSC_verify_iut_not_alerting(self, **kwargs):
+        """
+        Verify that the Implementation Under Test (IUT) is not generating a
+        local alert.
+        """
+
+        return "OK"
+
+    @assert_description
+    def TSC_hf_iut_enable_call_number(self, pts_addr: bytes, **kwargs):
+        """
+        Click Ok, then place an outgoing call from the Implementation Under Test
+        (IUT) using an enterted phone number.
+        """
+
+        self.connection = self.host.GetConnection(address=pts_addr).connection
+
+        def disable_call():
+            time.sleep(2)
+            self.hfp.MakeCallAsHandsfree(connection=self.connection, number="42")
+
+        threading.Thread(target=disable_call).start()
+
+        return "OK"
+
+    @assert_description
+    def TSC_hf_iut_enable_call_memory(self, **kwargs):
+        """
+        Click Ok, then place an outgoing call from the Implementation Under Test
+        (IUT) by entering the memory index.  For further clarification please
+        see the HFP 1.5 Specification.
+        """
+
+        return "OK"
+
+    @assert_description
+    def TSC_hf_iut_call_swap_then_disable_held_alternative(self, pts_addr: bytes, **kwargs):
+        """
+        Using the Implementation Under Test (IUT), perform one of the following
+        two actions:
+
+        1. Click OK, make the held/waiting call active, disabling
+        the active call.
+        2. Click OK, make the held/waiting call active, placing
+        the active call on hold.
+        """
+
+        self.connection = self.host.GetConnection(address=pts_addr).connection
+
+        def call_swap_then_disable_held_alternative():
+            time.sleep(2)
+            self.hfp.CallTransferAsHandsfree(connection=self.connection)
+
+        threading.Thread(target=call_swap_then_disable_held_alternative).start()
+
+        return "OK"
+
+    @assert_description
+    def TSC_iut_make_discoverable(self, **kwargs):
+        """
+        Place the Implementation Under Test (IUT) in discoverable mode, then
+        click Ok.
+        """
+
+        self.host.SetDiscoverabilityMode(mode=DiscoverabilityMode.DISCOVERABLE_GENERAL)
+
+        return "OK"
+
+    @assert_description
+    def TSC_iut_accept_connection(self, **kwargs):
+        """
+        Click Ok, then accept the pairing and connection requests on the
+        Implementation Under Test (IUT), if prompted.
+        """
+
+        return "OK"
+
+    @assert_description
+    def TSC_voice_recognition_enable_iut(self, pts_addr: bytes, **kwargs):
+        """
+        Using the Implementation Under Test (IUT), activate voice recognition.
+        """
+
+        self.hfp.SetVoiceRecognitionAsHandsfree(
+            enabled=True,
+            connection=self.host.GetConnection(address=pts_addr).connection,
+        )
+
+        return "OK"
+
+    @assert_description
+    def TSC_voice_recognition_disable_iut(self, pts_addr: bytes, **kwargs):
+        """
+        Using the Implementation Under Test (IUT), deactivate voice recognition.
+        """
+
+        self.hfp.SetVoiceRecognitionAsHandsfree(
+            enabled=False,
+            connection=self.host.GetConnection(address=pts_addr).connection,
+        )
+
+        return "OK"
+
+    @match_description
+    def TSC_dtmf_send(self, pts_addr: bytes, dtmf: str, **kwargs):
+        r"""
+        Send the DTMF code, then click Ok. (?P<dtmf>.*)
+        """
+
+        self.hfp.SendDtmfFromHandsfree(
+            connection=self.host.GetConnection(address=pts_addr).connection,
+            code=dtmf[0].encode("ascii")[0],
+        )
+
+        return "OK"
+
+    @assert_description
+    def TSC_verify_hf_iut_reports_held_and_active_call(self, **kwargs):
+        """
+        Verify that the Implementation Under Test (IUT) interprets both held and
+        active call signals, then click Ok.  If applicable, verify that the
+        information is correctly displayed on the IUT, then click Ok.
+        """
+
+        return "OK"
+
+    def TSC_rf_shield_iut_or_pts(self, **kwargs):
+        """
+        Click Ok, then move the PTS and the Implementation Under Test (IUT) out
+        of range of each other by performing one of the following IUT specific
+        actions:
+
+        1. Hands Free (HF) IUT - Place the IUT in the RF shield box or
+        physically take out of range from the PTS.
+
+        2. Audio Gateway (AG) IUT-
+        Physically take the IUT out range.  Do not place in the RF shield box as
+        it will interfere with the cellular network.
+
+        Note: The PTS can also be
+        placed in the RF shield box if necessary.
+        """
+
+        def shield_iut_or_pts():
+            time.sleep(2)
+            self.rootcanal.disconnect_phy()
+
+        threading.Thread(target=shield_iut_or_pts).start()
+
+        return "OK"
+
+    @assert_description
+    def TSC_rf_shield_open(self, **kwargs):
+        """
+        Click Ok, then remove the Implementation Under Test (IUT) and/or the PTS
+        from the RF shield.  If the out of range method was used, bring the IUT
+        and PTS back within range.
+        """
+
+        def shield_open():
+            time.sleep(2)
+            self.rootcanal.reconnect_phy_if_needed()
+
+        threading.Thread(target=shield_open).start()
+
+        return "OK"
+
+    @match_description
+    def TSC_verify_speaker_volume(self, volume: str, **kwargs):
+        r"""
+        Verify that the Hands Free \(HF\) speaker volume is displayed correctly on
+        the Implementation Under Test \(IUT\).(?P<volume>[0-9]*)
+        """
+
+        return "OK"
+
     def _auto_confirm_requests(self, times=None):
 
         def task():
diff --git a/android/pandora/mmi2grpc/mmi2grpc/l2cap.py b/android/pandora/mmi2grpc/mmi2grpc/l2cap.py
index 561c600..b6db482 100644
--- a/android/pandora/mmi2grpc/mmi2grpc/l2cap.py
+++ b/android/pandora/mmi2grpc/mmi2grpc/l2cap.py
@@ -16,6 +16,7 @@
 class L2CAPProxy(ProfileProxy):
     test_status_map = {}  # record test status and pass them between MMI
     LE_DATA_PACKET_LARGE = "data: LE_DATA_PACKET_LARGE"
+    LE_DATA_PACKET1 = "data: LE_PACKET1"
     connection: Optional[Connection] = None
 
     def __init__(self, channel):
@@ -101,6 +102,10 @@
         )
         # not strictly necessary, but can save time on waiting connection
         tests_to_open_bluetooth_server_socket = [
+            "L2CAP/COS/CFC/BV-01-C",
+            "L2CAP/COS/CFC/BV-02-C",
+            "L2CAP/COS/CFC/BV-03-C",
+            "L2CAP/COS/CFC/BV-04-C",
             "L2CAP/LE/CFC/BV-03-C",
             "L2CAP/LE/CFC/BV-05-C",
             "L2CAP/LE/CFC/BV-06-C",
@@ -138,7 +143,7 @@
         return "OK"
 
     @match_description
-    def MMI_UPPER_TESTER_CONFIRM_LE_DATA(self, sent_data: str, **kwargs):
+    def MMI_UPPER_TESTER_CONFIRM_LE_DATA(self, sent_data: str, test: str, **kwargs):
         """
         Did the Upper Tester send the data (?P<sent_data>[0-9A-F]*) to to the
         PTS\? Click Yes if it matched, otherwise click No.
@@ -146,10 +151,13 @@
         Description: The Implementation Under Test
         \(IUT\) send data is receive correctly in the PTS.
         """
-        hex_LE_DATA_PACKET_LARGE = self.LE_DATA_PACKET_LARGE.encode("utf-8").hex().upper()
-        if sent_data != hex_LE_DATA_PACKET_LARGE:
-            print(f"data not match, sent_data:{sent_data} and {hex_LE_DATA_PACKET_LARGE}", file=sys.stderr)
-            raise Exception(f"data not match, sent_data:{sent_data} and {hex_LE_DATA_PACKET_LARGE}")
+        if test == 'L2CAP/COS/CFC/BV-02-C':
+            hex_LE_DATA_PACKET = self.LE_DATA_PACKET1.encode("utf-8").hex().upper()
+        else:
+            hex_LE_DATA_PACKET = self.LE_DATA_PACKET_LARGE.encode("utf-8").hex().upper()
+        if sent_data != hex_LE_DATA_PACKET:
+            print(f"data not match, sent_data:{sent_data} and {hex_LE_DATA_PACKET}", file=sys.stderr)
+            raise Exception(f"data not match, sent_data:{sent_data} and {hex_LE_DATA_PACKET}")
         return "OK"
 
     @assert_description
@@ -424,3 +432,23 @@
         """
 
         return "OK"
+
+    @assert_description
+    def MMI_UPPER_TESTER_SEND_LE_DATA_PACKET1(self, **kwargs):
+        """
+        Upper Tester command IUT to send a non-segmented LE data packet to the
+        PTS with any values.
+         Description : The Implementation Under Test(IUT)
+        should send none segmantation LE frame of LE data to the PTS.
+        """
+        self.l2cap.SendData(connection=self.connection, data=bytes(self.LE_DATA_PACKET1, "utf-8"))
+        return "OK"
+
+    @assert_description
+    def MMI_IUT_SEND_L2CAP_DATA(self, **kwargs):
+        """
+        Using the Implementation Under Test(IUT), send L2CAP_Data over the
+        assigned channel with correct DCID to the PTS.
+        """
+
+        return "OK"
diff --git a/android/pandora/server/configs/PtsBotTest.xml b/android/pandora/server/configs/PtsBotTest.xml
index feb8524..e748a2e 100644
--- a/android/pandora/server/configs/PtsBotTest.xml
+++ b/android/pandora/server/configs/PtsBotTest.xml
@@ -38,9 +38,11 @@
         <option name="profile" value="GAP" />
         <option name="profile" value="GATT" />
         <option name="profile" value="HFP/AG" />
+        <option name="profile" value="HFP/HF" />
         <option name="profile" value="HID/HOS" />
         <option name="profile" value="HOGP" />
         <option name="profile" value="L2CAP/COS" />
+        <option name="profile" value="L2CAP/EXF" />
         <option name="profile" value="L2CAP/LE" />
         <option name="profile" value="MAP" />
         <option name="profile" value="OPP" />
diff --git a/android/pandora/server/configs/PtsBotTestMts.xml b/android/pandora/server/configs/PtsBotTestMts.xml
index 5df935c..127cb4f 100644
--- a/android/pandora/server/configs/PtsBotTestMts.xml
+++ b/android/pandora/server/configs/PtsBotTestMts.xml
@@ -38,9 +38,11 @@
         <option name="profile" value="GAP" />
         <option name="profile" value="GATT" />
         <option name="profile" value="HFP/AG" />
+        <option name="profile" value="HFP/HF" />
         <option name="profile" value="HID/HOS" />
         <option name="profile" value="HOGP" />
         <option name="profile" value="L2CAP/COS" />
+        <option name="profile" value="L2CAP/EXF" />
         <option name="profile" value="L2CAP/LE" />
         <option name="profile" value="MAP" />
         <option name="profile" value="OPP" />
diff --git a/android/pandora/server/configs/pts_bot_tests_config.json b/android/pandora/server/configs/pts_bot_tests_config.json
index 2421238..19b572f 100644
--- a/android/pandora/server/configs/pts_bot_tests_config.json
+++ b/android/pandora/server/configs/pts_bot_tests_config.json
@@ -25,10 +25,13 @@
     "A2DP/SRC/SUS/BV-01-I",
     "AVCTP/CT/CCM/BV-03-C",
     "AVCTP/CT/CCM/BV-04-C",
+    "AVCTP/TG/CCM/BV-01-C",
+    "AVCTP/TG/CCM/BV-02-C",
     "AVCTP/TG/CCM/BV-03-C",
     "AVCTP/TG/CCM/BV-04-C",
     "AVCTP/TG/FRA/BV-03-C",
     "AVCTP/TG/NFR/BI-01-C",
+    "AVCTP/TG/NFR/BV-02-C",
     "AVCTP/TG/NFR/BV-03-C",
     "AVDTP/SNK/ACP/SIG/SMG/BI-05-C",
     "AVDTP/SNK/ACP/SIG/SMG/BI-08-C",
@@ -316,15 +319,26 @@
     "HOGP/RH/HGRF/BV-10-I",
     "HOGP/RH/HGRF/BV-12-I",
     "L2CAP/COS/CED/BI-01-C",
+    "L2CAP/COS/CED/BV-03-C",
     "L2CAP/COS/CED/BV-05-C",
     "L2CAP/COS/CED/BV-07-C",
     "L2CAP/COS/CED/BV-08-C",
     "L2CAP/COS/CED/BV-11-C",
+    "L2CAP/COS/CFC/BV-01-C",
+    "L2CAP/COS/CFC/BV-02-C",
+    "L2CAP/COS/CFC/BV-03-C",
+    "L2CAP/COS/CFC/BV-04-C",
     "L2CAP/COS/CFD/BV-02-C",
     "L2CAP/COS/CFD/BV-03-C",
     "L2CAP/COS/CFD/BV-11-C",
     "L2CAP/COS/CFD/BV-12-C",
     "L2CAP/COS/CFD/BV-14-C",
+    "L2CAP/COS/ECH/BV-01-C",
+    "L2CAP/COS/IEX/BV-02-C",
+    "L2CAP/EXF/BV-01-C",
+    "L2CAP/EXF/BV-02-C",
+    "L2CAP/EXF/BV-03-C",
+    "L2CAP/EXF/BV-05-C",
     "L2CAP/LE/CFC/BI-01-C",
     "L2CAP/LE/CFC/BV-01-C",
     "L2CAP/LE/CFC/BV-02-C",
@@ -540,10 +554,7 @@
     "AVCTP/CT/FRA/BV-04-C",
     "AVCTP/CT/NFR/BV-01-C",
     "AVCTP/CT/NFR/BV-04-C",
-    "AVCTP/TG/CCM/BV-01-C",
-    "AVCTP/TG/CCM/BV-02-C",
     "AVCTP/TG/FRA/BV-02-C",
-    "AVCTP/TG/NFR/BV-02-C",
     "AVDTP/SNK/ACP/SIG/SMG/BI-11-C",
     "AVDTP/SNK/ACP/SIG/SMG/BI-23-C",
     "AVDTP/SNK/ACP/SIG/SMG/BV-14-C",
@@ -737,10 +748,24 @@
     "HFP/AG/HFI/BI-03-I",
     "HFP/AG/OCN/BV-01-I",
     "HFP/AG/SLC/BV-04-C",
+    "HFP/HF/OCM/BV-01-I",
+    "HFP/HF/OCM/BV-02-I",
+    "HFP/HF/OCL/BV-01-I",
+    "HFP/HF/OCL/BV-02-I",
+    "HFP/HF/TWC/BV-02-I",
+    "HFP/HF/TWC/BV-03-I",
+    "HFP/HF/ENO/BV-01-I",
+    "HFP/HF/VRD/BV-01-I",
+    "HFP/HF/NUM/BV-01-I",
+    "HFP/HF/NUM/BI-01-I",
+    "HFP/HF/ACC/BV-01-I",
+    "HFP/HF/ACC/BV-02-I",
+    "HFP/HF/ECC/BV-01-I",
+    "HFP/HF/ECC/BV-02-I",
+    "HFP/HF/ECS/BV-01-I",
     "HID/HOS/HCR/BV-01-I",
     "L2CAP/COS/CED/BI-02-C",
     "L2CAP/COS/CED/BV-01-C",
-    "L2CAP/COS/CED/BV-03-C",
     "L2CAP/COS/CED/BV-04-C",
     "L2CAP/COS/CED/BV-09-C",
     "L2CAP/COS/CED/BV-10-C",
@@ -751,14 +776,9 @@
     "L2CAP/COS/CFD/BV-08-C",
     "L2CAP/COS/CFD/BV-10-C",
     "L2CAP/COS/CFD/BV-13-C",
-    "L2CAP/COS/CFC/BV-01-C",
-    "L2CAP/COS/CFC/BV-02-C",
-    "L2CAP/COS/CFC/BV-03-C",
-    "L2CAP/COS/CFC/BV-04-C",
     "L2CAP/COS/CFC/BV-05-C",
     "L2CAP/COS/ECH/BV-02-C",
     "L2CAP/COS/IEX/BV-01-C",
-    "L2CAP/COS/IEX/BV-02-C",
     "L2CAP/LE/CFC/BV-07-C",
     "L2CAP/LE/CFC/BV-11-C",
     "L2CAP/LE/CFC/BV-13-C",
@@ -1411,8 +1431,11 @@
     "TSPC_HFP_3_14": true,
     "TSPC_HFP_3_15": true,
     "TSPC_HFP_3_17": true,
+    "TSPC_HFP_3_18a": true,
+    "TSPC_HFP_3_18c": true,
     "TSPC_HFP_3_20": true,
     "TSPC_HFP_3_21a": true,
+    "TSPC_HFP_3_21b": true,
     "TSPC_HFP_3_23": true,
     "TSPC_HFP_3_24": true,
     "TSPC_HFP_3_26": true,
diff --git a/android/pandora/server/src/com/android/pandora/HfpHandsfree.kt b/android/pandora/server/src/com/android/pandora/HfpHandsfree.kt
new file mode 100644
index 0000000..f2af500
--- /dev/null
+++ b/android/pandora/server/src/com/android/pandora/HfpHandsfree.kt
@@ -0,0 +1,190 @@
+/*
+ * Copyright (C) 2022 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.pandora
+
+import android.annotation.SuppressLint
+import android.bluetooth.BluetoothDevice
+import android.bluetooth.BluetoothHeadset
+import android.bluetooth.BluetoothHeadsetClient
+import android.bluetooth.BluetoothManager
+import android.bluetooth.BluetoothProfile
+import android.content.Context
+import android.content.Intent
+import android.content.IntentFilter
+import android.net.Uri
+import android.os.Bundle
+import android.os.IBinder
+import android.provider.CallLog
+import android.telecom.Call
+import android.telecom.CallAudioState
+import android.telecom.InCallService
+import android.telecom.TelecomManager
+import android.telecom.VideoProfile
+import com.google.protobuf.Empty
+import io.grpc.stub.StreamObserver
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.cancel
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.shareIn
+import pandora.HFPGrpc.HFPImplBase
+import pandora.HfpProto.*
+
+private const val TAG = "PandoraHfpHandsfree"
+
+@kotlinx.coroutines.ExperimentalCoroutinesApi
+class HfpHandsfree(val context: Context) : HFPImplBase() {
+  private val scope: CoroutineScope = CoroutineScope(Dispatchers.Default)
+  private val flow: Flow<Intent>
+
+  private val bluetoothManager = context.getSystemService(BluetoothManager::class.java)!!
+  private val telecomManager = context.getSystemService(TelecomManager::class.java)!!
+  private val bluetoothAdapter = bluetoothManager.adapter
+
+  private val bluetoothHfpClient = getProfileProxy<BluetoothHeadsetClient>(context, BluetoothProfile.HEADSET_CLIENT)
+
+  companion object {
+    @SuppressLint("StaticFieldLeak") private lateinit var inCallService: InCallService
+  }
+
+  init {
+    val intentFilter = IntentFilter(BluetoothDevice.ACTION_BOND_STATE_CHANGED)
+    flow = intentFlow(context, intentFilter).shareIn(scope, SharingStarted.Eagerly)
+  }
+
+  fun deinit() {
+    bluetoothAdapter.closeProfileProxy(BluetoothProfile.HEADSET_CLIENT, bluetoothHfpClient)
+    scope.cancel()
+  }
+
+  override fun answerCallAsHandsfree(
+    request: AnswerCallAsHandsfreeRequest,
+    responseObserver: StreamObserver<AnswerCallAsHandsfreeResponse>
+  ) {
+    grpcUnary(scope, responseObserver) {
+      bluetoothHfpClient.acceptCall(request.connection.toBluetoothDevice(bluetoothAdapter), BluetoothHeadsetClient.CALL_ACCEPT_NONE)
+      AnswerCallAsHandsfreeResponse.getDefaultInstance()
+    }
+  }
+
+  override fun endCallAsHandsfree(
+    request: EndCallAsHandsfreeRequest,
+    responseObserver: StreamObserver<EndCallAsHandsfreeResponse>
+  ) {
+    grpcUnary(scope, responseObserver) {
+      for (call in bluetoothHfpClient.getCurrentCalls(request.connection.toBluetoothDevice(bluetoothAdapter))) {
+        bluetoothHfpClient.terminateCall(request.connection.toBluetoothDevice(bluetoothAdapter), call)
+      }
+      EndCallAsHandsfreeResponse.getDefaultInstance()
+    }
+  }
+
+  override fun declineCallAsHandsfree(
+    request: DeclineCallAsHandsfreeRequest,
+    responseObserver: StreamObserver<DeclineCallAsHandsfreeResponse>
+  ) {
+    grpcUnary(scope, responseObserver) {
+      bluetoothHfpClient.rejectCall(request.connection.toBluetoothDevice(bluetoothAdapter))
+      DeclineCallAsHandsfreeResponse.getDefaultInstance()
+    }
+  }
+
+  override fun connectToAudioAsHandsfree(
+    request: ConnectToAudioAsHandsfreeRequest,
+    responseObserver: StreamObserver<ConnectToAudioAsHandsfreeResponse>
+  ) {
+    grpcUnary(scope, responseObserver) {
+      bluetoothHfpClient.connectAudio(request.connection.toBluetoothDevice(bluetoothAdapter))
+      ConnectToAudioAsHandsfreeResponse.getDefaultInstance()
+    }
+  }
+
+  override fun disconnectFromAudioAsHandsfree(
+    request: DisconnectFromAudioAsHandsfreeRequest,
+    responseObserver: StreamObserver<DisconnectFromAudioAsHandsfreeResponse>
+  ) {
+    grpcUnary(scope, responseObserver) {
+      bluetoothHfpClient.disconnectAudio(request.connection.toBluetoothDevice(bluetoothAdapter))
+      DisconnectFromAudioAsHandsfreeResponse.getDefaultInstance()
+    }
+  }
+
+  override fun makeCallAsHandsfree(
+    request: MakeCallAsHandsfreeRequest,
+    responseObserver: StreamObserver<MakeCallAsHandsfreeResponse>
+  ) {
+    grpcUnary(scope, responseObserver) {
+      bluetoothHfpClient.dial(request.connection.toBluetoothDevice(bluetoothAdapter), request.number)
+      MakeCallAsHandsfreeResponse.getDefaultInstance()
+    }
+  }
+
+  override fun callTransferAsHandsfree(
+    request: CallTransferAsHandsfreeRequest,
+    responseObserver: StreamObserver<CallTransferAsHandsfreeResponse>
+  ) {
+    grpcUnary(scope, responseObserver) {
+      bluetoothHfpClient.explicitCallTransfer(request.connection.toBluetoothDevice(bluetoothAdapter))
+      CallTransferAsHandsfreeResponse.getDefaultInstance()
+    }
+  }
+
+  override fun enableSlcAsHandsfree(
+    request: EnableSlcAsHandsfreeRequest,
+    responseObserver: StreamObserver<Empty>
+  ) {
+    grpcUnary(scope, responseObserver) {
+      bluetoothHfpClient.setConnectionPolicy(request.connection.toBluetoothDevice(bluetoothAdapter), BluetoothProfile.CONNECTION_POLICY_ALLOWED)
+      Empty.getDefaultInstance()
+    }
+  }
+
+  override fun disableSlcAsHandsfree(
+    request: DisableSlcAsHandsfreeRequest,
+    responseObserver: StreamObserver<Empty>
+  ) {
+    grpcUnary(scope, responseObserver) {
+      bluetoothHfpClient.setConnectionPolicy(request.connection.toBluetoothDevice(bluetoothAdapter), BluetoothProfile.CONNECTION_POLICY_FORBIDDEN)
+      Empty.getDefaultInstance()
+    }
+  }
+
+  override fun setVoiceRecognitionAsHandsfree(
+    request: SetVoiceRecognitionAsHandsfreeRequest,
+    responseObserver: StreamObserver<SetVoiceRecognitionAsHandsfreeResponse>
+  ) {
+    grpcUnary(scope, responseObserver) {
+      if (request.enabled) {
+        bluetoothHfpClient.startVoiceRecognition(request.connection.toBluetoothDevice(bluetoothAdapter))
+      } else {
+        bluetoothHfpClient.stopVoiceRecognition(request.connection.toBluetoothDevice(bluetoothAdapter))
+      }
+      SetVoiceRecognitionAsHandsfreeResponse.getDefaultInstance()
+    }
+  }
+
+  override fun sendDtmfFromHandsfree(
+    request: SendDtmfFromHandsfreeRequest,
+    responseObserver: StreamObserver<SendDtmfFromHandsfreeResponse>
+  ) {
+    grpcUnary(scope, responseObserver) {
+      bluetoothHfpClient.sendDTMF(request.connection.toBluetoothDevice(bluetoothAdapter), request.code.toByte())
+      SendDtmfFromHandsfreeResponse.getDefaultInstance()
+    }
+  }
+}
diff --git a/android/pandora/server/src/com/android/pandora/Server.kt b/android/pandora/server/src/com/android/pandora/Server.kt
index c4f95ca..7b17dd4 100644
--- a/android/pandora/server/src/com/android/pandora/Server.kt
+++ b/android/pandora/server/src/com/android/pandora/Server.kt
@@ -34,7 +34,8 @@
   private var a2dpSink: A2dpSink? = null
   private var avrcp: Avrcp
   private var gatt: Gatt
-  private var hfp: Hfp
+  private var hfp: Hfp? = null
+  private var hfpHandsfree: HfpHandsfree? = null
   private var hid: Hid
   private var l2cap: L2cap
   private var mediaplayer: MediaPlayer
@@ -50,7 +51,6 @@
     host = Host(context, security, this)
     avrcp = Avrcp(context)
     gatt = Gatt(context)
-    hfp = Hfp(context)
     hid = Hid(context)
     l2cap = L2cap(context)
     mediaplayer = MediaPlayer(context)
@@ -64,7 +64,6 @@
         .addService(host)
         .addService(avrcp)
         .addService(gatt)
-        .addService(hfp)
         .addService(hid)
         .addService(l2cap)
         .addService(mediaplayer)
@@ -84,6 +83,15 @@
       grpcServerBuilder.addService(a2dpSink!!)
     }
 
+    val is_hfp_hf = bluetoothAdapter.getSupportedProfiles().contains(BluetoothProfile.HEADSET_CLIENT)
+    if (is_hfp_hf) {
+      hfpHandsfree = HfpHandsfree(context)
+      grpcServerBuilder.addService(hfpHandsfree!!)
+    } else {
+      hfp = Hfp(context)
+      grpcServerBuilder.addService(hfp!!)
+    }
+
     grpcServer = grpcServerBuilder.build()
 
     Log.d(TAG, "Starting Pandora Server")
@@ -101,7 +109,8 @@
     a2dpSink?.deinit()
     avrcp.deinit()
     gatt.deinit()
-    hfp.deinit()
+    hfp?.deinit()
+    hfpHandsfree?.deinit()
     hid.deinit()
     l2cap.deinit()
     mediaplayer.deinit()
diff --git a/framework/java/android/bluetooth/BluetoothDevice.java b/framework/java/android/bluetooth/BluetoothDevice.java
index f7ab590..58895ef 100644
--- a/framework/java/android/bluetooth/BluetoothDevice.java
+++ b/framework/java/android/bluetooth/BluetoothDevice.java
@@ -510,7 +510,9 @@
             METADATA_UNTETHERED_CASE_LOW_BATTERY_THRESHOLD,
             METADATA_SPATIAL_AUDIO,
             METADATA_FAST_PAIR_CUSTOMIZED_FIELDS,
-            METADATA_LE_AUDIO})
+            METADATA_LE_AUDIO,
+            METADATA_GMCS_CCCD,
+            METADATA_GTBS_CCCD})
     @Retention(RetentionPolicy.SOURCE)
     public @interface MetadataKey{}
 
@@ -663,6 +665,21 @@
     public static final int METADATA_ENHANCED_SETTINGS_UI_URI = 16;
 
     /**
+     * @hide
+     */
+    public static final String COMPANION_TYPE_PRIMARY = "COMPANION_PRIMARY";
+
+    /**
+     * @hide
+     */
+    public static final String COMPANION_TYPE_SECONDARY = "COMPANION_SECONDARY";
+
+    /**
+     * @hide
+     */
+    public static final String COMPANION_TYPE_NONE = "COMPANION_NONE";
+
+    /**
      * Type of the Bluetooth device, must be within the list of
      * BluetoothDevice.DEVICE_TYPE_*
      * Data type should be {@String} as {@link Byte} array.
@@ -742,6 +759,21 @@
      */
     public static final int METADATA_LE_AUDIO = 26;
 
+    /**
+     * The UUIDs (16-bit) of registered to CCC characteristics from Media Control services.
+     * Data type should be {@link Byte} array.
+     * @hide
+     */
+    public static final int METADATA_GMCS_CCCD = 27;
+
+    /**
+     * The UUIDs (16-bit) of registered to CCC characteristics from Telephony Bearer service.
+     * Data type should be {@link Byte} array.
+     * @hide
+     */
+    public static final int METADATA_GTBS_CCCD = 28;
+
+    private static final int METADATA_MAX_KEY = METADATA_GTBS_CCCD;
 
     /**
      * Device type which is used in METADATA_DEVICE_TYPE
@@ -3210,7 +3242,165 @@
      * @hide
      */
     public static @MetadataKey int getMaxMetadataKey() {
-        return METADATA_LE_AUDIO;
+        return METADATA_MAX_KEY;
+    }
+
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(
+        prefix = { "FEATURE_" },
+        value = {
+            /** Remote support status of audio policy feature is unknown/unconfigured **/
+            BluetoothStatusCodes.FEATURE_NOT_CONFIGURED,
+            /** Remote support status of audio policy feature is supported **/
+            BluetoothStatusCodes.FEATURE_SUPPORTED,
+            /** Remote support status of audio policy feature is not supported **/
+            BluetoothStatusCodes.FEATURE_NOT_SUPPORTED,
+        }
+    )
+
+    public @interface AudioPolicyRemoteSupport {}
+
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(value = {
+            BluetoothStatusCodes.SUCCESS,
+            BluetoothStatusCodes.ERROR_BLUETOOTH_NOT_ENABLED,
+            BluetoothStatusCodes.ERROR_BLUETOOTH_NOT_ALLOWED,
+            BluetoothStatusCodes.ERROR_DEVICE_NOT_BONDED,
+            BluetoothStatusCodes.ERROR_MISSING_BLUETOOTH_CONNECT_PERMISSION,
+            BluetoothStatusCodes.ERROR_PROFILE_NOT_CONNECTED,
+    })
+    public @interface AudioPolicyReturnValues{}
+
+    /**
+     * Returns whether the audio policy feature is supported by
+     * both the local and the remote device.
+     * This is configured during initiating the connection between the devices through
+     * one of the transport protocols (e.g. HFP Vendor specific protocol). So if the API
+     * is invoked before this initial configuration is completed, it returns
+     * {@link BluetoothStatusCodes#FEATURE_NOT_CONFIGURED} to indicate the remote
+     * device has not yet relayed this information. After the internal configuration,
+     * the support status will be set to either
+     * {@link BluetoothStatusCodes#FEATURE_NOT_SUPPORTED} or
+     * {@link BluetoothStatusCodes#FEATURE_SUPPORTED}.
+     * The rest of the APIs related to this feature in both {@link BluetoothDevice}
+     * and {@link BluetoothSinkAudioPolicy} should be invoked  only after getting a
+     * {@link BluetoothStatusCodes#FEATURE_SUPPORTED} response from this API.
+     * <p>Note that this API is intended to be used by a client device to send these requests
+     * to the server represented by this BluetoothDevice object.
+     *
+     * @return if call audio policy feature is supported by both local and remote
+     * device or not
+     *
+     * @hide
+     */
+    @RequiresBluetoothConnectPermission
+    @RequiresPermission(allOf = {
+            android.Manifest.permission.BLUETOOTH_CONNECT,
+            android.Manifest.permission.BLUETOOTH_PRIVILEGED,
+    })
+    public @AudioPolicyRemoteSupport int isRequestAudioPolicyAsSinkSupported() {
+        if (DBG) log("isRequestAudioPolicyAsSinkSupported()");
+        final IBluetooth service = getService();
+        final int defaultValue = BluetoothStatusCodes.FEATURE_NOT_CONFIGURED;
+        if (service == null || !isBluetoothEnabled()) {
+            Log.e(TAG, "BT not enabled. Cannot retrieve audio policy support status.");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else {
+            try {
+                final SynchronousResultReceiver<Integer> recv = SynchronousResultReceiver.get();
+                service.isRequestAudioPolicyAsSinkSupported(this, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
+            } catch (RemoteException e) {
+                Log.e(TAG, "", e);
+                throw e.rethrowFromSystemServer();
+            }
+        }
+        return defaultValue;
+    }
+
+    /**
+     * Sets call audio preferences and sends them to the remote device.
+     * <p>Note that the caller should check if the feature is supported by
+     * invoking {@link BluetoothDevice#isRequestAudioPolicyAsSinkSupported} first.
+     * <p>This API will throw an exception if the feature is not supported but still
+     * invoked.
+     * <p>Note that this API is intended to be used by a client device to send these requests
+     * to the server represented by this BluetoothDevice object.
+     *
+     * @param policies call audio policy preferences
+     * @return whether audio policy was requested successfully or not
+     *
+     * @hide
+     */
+    @RequiresBluetoothConnectPermission
+    @RequiresPermission(allOf = {
+            android.Manifest.permission.BLUETOOTH_CONNECT,
+            android.Manifest.permission.BLUETOOTH_PRIVILEGED,
+    })
+    public @AudioPolicyReturnValues int requestAudioPolicyAsSink(
+            @NonNull BluetoothSinkAudioPolicy policies) {
+        if (DBG) log("requestAudioPolicyAsSink");
+        final IBluetooth service = getService();
+        final int defaultValue = BluetoothStatusCodes.ERROR_BLUETOOTH_NOT_ENABLED;
+        if (service == null || !isBluetoothEnabled()) {
+            Log.e(TAG, "Bluetooth is not enabled. Cannot set Audio Policy.");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else {
+            try {
+                final SynchronousResultReceiver<Integer> recv = SynchronousResultReceiver.get();
+                service.requestAudioPolicyAsSink(this, policies, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
+            }
+        }
+        return defaultValue;
+    }
+
+    /**
+     * Gets the call audio preferences for the remote device.
+     * <p>Note that the caller should check if the feature is supported by
+     * invoking {@link BluetoothDevice#isRequestAudioPolicyAsSinkSupported} first.
+     * <p>This API will throw an exception if the feature is not supported but still
+     * invoked.
+     * <p>This API will return null if
+     * 1. The bleutooth service is not started yet,
+     * 2. It is invoked for a device which is not bonded, or
+     * 3. The used transport, for example, HFP Client profile is not enabled or
+     * connected yet.
+     * <p>Note that this API is intended to be used by a client device to send these requests
+     * to the server represented by this BluetoothDevice object.
+     *
+     * @return call audio policy as {@link BluetoothSinkAudioPolicy} object
+     *
+     * @hide
+     */
+    @RequiresBluetoothConnectPermission
+    @RequiresPermission(allOf = {
+            android.Manifest.permission.BLUETOOTH_CONNECT,
+            android.Manifest.permission.BLUETOOTH_PRIVILEGED,
+    })
+    public @Nullable BluetoothSinkAudioPolicy getRequestedAudioPolicyAsSink() {
+        if (DBG) log("getRequestedAudioPolicyAsSink");
+        final IBluetooth service = getService();
+        if (service == null || !isBluetoothEnabled()) {
+            Log.e(TAG, "Bluetooth is not enabled. Cannot get Audio Policy.");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else {
+            try {
+                final SynchronousResultReceiver<BluetoothSinkAudioPolicy>
+                        recv = SynchronousResultReceiver.get();
+                service.getRequestedAudioPolicyAsSink(this, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(null);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
+            }
+        }
+        return null;
     }
 
     /**
diff --git a/framework/java/android/bluetooth/BluetoothServerSocket.java b/framework/java/android/bluetooth/BluetoothServerSocket.java
index bb4e354..5a23f7e3 100644
--- a/framework/java/android/bluetooth/BluetoothServerSocket.java
+++ b/framework/java/android/bluetooth/BluetoothServerSocket.java
@@ -75,7 +75,7 @@
 public final class BluetoothServerSocket implements Closeable {
 
     private static final String TAG = "BluetoothServerSocket";
-    private static final boolean DBG = false;
+    private static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG);
     @UnsupportedAppUsage(publicAlternatives = "Use public {@link BluetoothServerSocket} API "
             + "instead.")
     /*package*/ final BluetoothSocket mSocket;
diff --git a/framework/java/android/bluetooth/BluetoothSinkAudioPolicy.java b/framework/java/android/bluetooth/BluetoothSinkAudioPolicy.java
new file mode 100644
index 0000000..8641e51
--- /dev/null
+++ b/framework/java/android/bluetooth/BluetoothSinkAudioPolicy.java
@@ -0,0 +1,325 @@
+/*
+ * Copyright 2022 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 android.bluetooth;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Objects;
+
+/**
+ * Represents Bluetooth Audio Policies of a Handsfree (HF) device (if HFP is used)
+ * and Call Terminal (CT) device (if BLE Audio is used), which describes the
+ * preferences of allowing or disallowing audio based on the use cases. The HF/CT
+ * devices shall send objects of this class to send its preference to the AG/CG
+ * devices.
+ *
+ * <p>HF/CT side applications on can use {@link BluetoothDevice#requestAudioPolicyAsSink}
+ * API to set and send a {@link BluetoothSinkAudioPolicy} object containing the
+ * preference/policy values. This object will be stored in the memory of HF/CT
+ * side, will be send to the AG/CG side using Android Specific AT Commands and will
+ * be stored in the AG side memory and database.
+ *
+ * <p>HF/CT side API {@link BluetoothDevice#getRequestedAudioPolicyAsSink} can be used to retrieve
+ * the stored audio policies currently.
+ *
+ * <p>Note that the setter APIs of this class will only set the values of the
+ * object. To actually set the policies, API {@link BluetoothDevice#requestAudioPolicyAsSink}
+ * must need to be invoked with the {@link BluetoothSinkAudioPolicy} object.
+ *
+ * <p>Note that any API related to this feature should be used after configuring
+ * the support of the AG device and after checking whether the AG device supports
+ * this feature or not by invoking {@link BluetoothDevice#isRequestAudioPolicyAsSinkSupported}.
+ * Only after getting a {@link BluetoothStatusCodes#FEATURE_SUPPORTED} response
+ * from the API should the APIs related to this feature be used.
+ *
+ *
+ * @hide
+ */
+public final class BluetoothSinkAudioPolicy implements Parcelable {
+
+    /**
+     * @hide
+    */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(
+        prefix = {"POLICY_"},
+        value = {
+            POLICY_UNCONFIGURED,
+            POLICY_ALLOWED,
+            POLICY_NOT_ALLOWED,
+        }
+    )
+    public @interface AudioPolicyValues{}
+
+    /**
+     * Audio behavior not configured for the policy.
+     *
+     * If a policy is set with this value, it means that the policy is not
+     * configured with a value yet and should not be used to make any decision.
+     * @hide
+     */
+    public static final int POLICY_UNCONFIGURED = 0;
+
+    /**
+     * Audio is preferred by HF device for the policy.
+     *
+     * If a policy is set with this value, then the HF device will prefer the
+     * audio for the policy use case. For example, if the Call Establish audio
+     * policy is set with this value, then the HF will prefer the audio
+     * during making or picking up a call.
+     * @hide
+     */
+    public static final int POLICY_ALLOWED = 1;
+
+    /**
+     * Audio is not preferred by HF device for the policy.
+     *
+     * If a policy is set with this value, then the HF device will not prefer the
+     * audio for the policy use case. For example, if the Call Establish audio
+     * policy is set with this value, then the HF will not prefer the audio
+     * during making or picking up a call.
+     * @hide
+     */
+    public static final int POLICY_NOT_ALLOWED = 2;
+
+    @AudioPolicyValues private final int mCallEstablishPolicy;
+    @AudioPolicyValues private final int mConnectingTimePolicy;
+    @AudioPolicyValues private final int mInBandRingtonePolicy;
+
+    /**
+     * @hide
+     */
+    public BluetoothSinkAudioPolicy(int callEstablishPolicy,
+            int connectingTimePolicy, int inBandRingtonePolicy) {
+        mCallEstablishPolicy = callEstablishPolicy;
+        mConnectingTimePolicy = connectingTimePolicy;
+        mInBandRingtonePolicy = inBandRingtonePolicy;
+    }
+
+    /**
+     * Get Call establishment policy audio policy.
+     * <p>This policy is used to determine the audio preference when the
+     * HF device makes or answers a call. That is, if this device
+     * makes or answers a call, is the audio preferred by HF.
+     *
+     * @return the call pick up audio policy value
+     *
+     * @hide
+     */
+    public @AudioPolicyValues int getCallEstablishPolicy() {
+        return mCallEstablishPolicy;
+    }
+
+    /**
+     * Get during connection audio up policy.
+     * <p>This policy is used to determine the audio preference when the
+     * HF device connects with the AG device. That is, when the
+     * HF device gets connected, should the HF become active and get audio
+     * is decided by this policy. This also covers the case of during a call.
+     * If the HF connects with the AG during an ongoing call, should the call
+     * audio be routed to the HF will be decided by this policy.
+     *
+     * @return the during connection audio policy value
+     *
+     * @hide
+     */
+    public @AudioPolicyValues int getActiveDevicePolicyAfterConnection() {
+        return mConnectingTimePolicy;
+    }
+
+    /**
+     * Get In band ringtone audio up policy.
+     * <p>This policy is used to determine the audio preference of the
+     * in band ringtone. That is, if there is an incoming call, should the
+     * inband ringtone audio be routed to the HF will be decided by this policy.
+     *
+     * @return the in band ringtone audio policy value
+     *
+     * @hide
+     */
+    public @AudioPolicyValues int getInBandRingtonePolicy() {
+        return mInBandRingtonePolicy;
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder builder = new StringBuilder("BluetoothSinkAudioPolicy{");
+        builder.append("mCallEstablishPolicy: ");
+        builder.append(mCallEstablishPolicy);
+        builder.append(", mConnectingTimePolicy: ");
+        builder.append(mConnectingTimePolicy);
+        builder.append(", mInBandRingtonePolicy: ");
+        builder.append(mInBandRingtonePolicy);
+        builder.append("}");
+        return builder.toString();
+    }
+
+    /**
+     * {@link Parcelable.Creator} interface implementation.
+     */
+    public static final @android.annotation.NonNull Parcelable.Creator<BluetoothSinkAudioPolicy>
+            CREATOR = new Parcelable.Creator<BluetoothSinkAudioPolicy>() {
+                @Override
+                public BluetoothSinkAudioPolicy createFromParcel(@NonNull Parcel in) {
+                    return new BluetoothSinkAudioPolicy(
+                            in.readInt(), in.readInt(), in.readInt());
+                }
+
+                @Override
+                public BluetoothSinkAudioPolicy[] newArray(int size) {
+                    return new BluetoothSinkAudioPolicy[size];
+                }
+            };
+
+    @Override
+    public void writeToParcel(@NonNull Parcel out, int flags) {
+        out.writeInt(mCallEstablishPolicy);
+        out.writeInt(mConnectingTimePolicy);
+        out.writeInt(mInBandRingtonePolicy);
+    }
+
+    /**
+     * @hide
+     */
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public boolean equals(@Nullable Object o) {
+        if (o instanceof BluetoothSinkAudioPolicy) {
+            BluetoothSinkAudioPolicy other = (BluetoothSinkAudioPolicy) o;
+            return (other.mCallEstablishPolicy == mCallEstablishPolicy
+                    && other.mConnectingTimePolicy == mConnectingTimePolicy
+                    && other.mInBandRingtonePolicy == mInBandRingtonePolicy);
+        }
+        return false;
+    }
+
+    /**
+     * Returns a hash representation of this BluetoothCodecConfig
+     * based on all the config values.
+     *
+     * @hide
+     */
+    @Override
+    public int hashCode() {
+        return Objects.hash(mCallEstablishPolicy, mConnectingTimePolicy, mInBandRingtonePolicy);
+    }
+
+    /**
+     * Builder for {@link BluetoothSinkAudioPolicy}.
+     * <p> By default, the audio policy values will be set to
+     * {@link BluetoothSinkAudioPolicy#POLICY_UNCONFIGURED}.
+     */
+    public static final class Builder {
+        private int mCallEstablishPolicy = POLICY_UNCONFIGURED;
+        private int mConnectingTimePolicy = POLICY_UNCONFIGURED;
+        private int mInBandRingtonePolicy = POLICY_UNCONFIGURED;
+
+        public Builder() {
+
+        }
+
+        public Builder(@NonNull BluetoothSinkAudioPolicy policies) {
+            mCallEstablishPolicy = policies.mCallEstablishPolicy;
+            mConnectingTimePolicy = policies.mConnectingTimePolicy;
+            mInBandRingtonePolicy = policies.mInBandRingtonePolicy;
+        }
+
+        /**
+         * Set Call Establish (pick up and answer) policy.
+         * <p>This policy is used to determine the audio preference when the
+         * HF device makes or answers a call. That is, if this device
+         * makes or answers a call, is the audio preferred by HF.
+         * <p>If set to {@link BluetoothSinkAudioPolicy#POLICY_ALLOWED}, answering or making
+         * a call from the HF device will route the call audio to it.
+         * If set to {@link BluetoothSinkAudioPolicy#POLICY_NOT_ALLOWED}, answering or making
+         * a call from the HF device will NOT route the call audio to it.
+         *
+         * @return reference to the current object
+         *
+         * @hide
+         */
+        public @NonNull Builder setCallEstablishPolicy(
+                @AudioPolicyValues int callEstablishPolicy) {
+            mCallEstablishPolicy = callEstablishPolicy;
+            return this;
+        }
+
+        /**
+         * Set during connection audio up policy.
+         * <p>This policy is used to determine the audio preference when the
+         * HF device connects with the AG device. That is, when the
+         * HF device gets connected, should the HF become active and get audio
+         * is decided by this policy. This also covers the case of during a call.
+         * If the HF connects with the AG during an ongoing call, should the call
+         * audio be routed to the HF will be decided by this policy.
+         * <p>If set to {@link BluetoothSinkAudioPolicy#POLICY_ALLOWED}, connecting HF
+         * during a call will route the call audio to it.
+         * If set to {@link BluetoothSinkAudioPolicy#POLICY_NOT_ALLOWED}, connecting HF
+         * during a call will NOT route the call audio to it.
+         *
+         * @return reference to the current object
+         *
+         * @hide
+         */
+        public @NonNull Builder setActiveDevicePolicyAfterConnection(
+                @AudioPolicyValues int connectingTimePolicy) {
+            mConnectingTimePolicy = connectingTimePolicy;
+            return this;
+        }
+
+        /**
+         * Set In band ringtone audio up policy.
+         * <p>This policy is used to determine the audio preference of the
+         * in band ringtone. That is, if there is an incoming call, should the
+         * inband ringtone audio be routed to the HF will be decided by this policy.
+         * <p>If set to {@link BluetoothSinkAudioPolicy#POLICY_ALLOWED}, there will be
+         * in band ringtone in the HF device during an incoming call.
+         * If set to {@link BluetoothSinkAudioPolicy#POLICY_NOT_ALLOWED}, there will NOT
+         * be in band ringtone in the HF device during an incoming call.
+         *
+         * @return reference to the current object
+         *
+         * @hide
+         */
+        public @NonNull Builder setInBandRingtonePolicy(
+                @AudioPolicyValues int inBandRingtonePolicy) {
+            mInBandRingtonePolicy = inBandRingtonePolicy;
+            return this;
+        }
+
+        /**
+         * Build {@link BluetoothSinkAudioPolicy}.
+         * @return new BluetoothSinkAudioPolicy object
+         *
+         * @hide
+         */
+        public @NonNull BluetoothSinkAudioPolicy build() {
+            return new BluetoothSinkAudioPolicy(
+                    mCallEstablishPolicy, mConnectingTimePolicy, mInBandRingtonePolicy);
+        }
+    }
+}
diff --git a/framework/java/android/bluetooth/BluetoothSocket.java b/framework/java/android/bluetooth/BluetoothSocket.java
index ef2f7b8..b57bfca 100644
--- a/framework/java/android/bluetooth/BluetoothSocket.java
+++ b/framework/java/android/bluetooth/BluetoothSocket.java
@@ -285,7 +285,7 @@
         BluetoothSocket as = new BluetoothSocket(this);
         as.mSocketState = SocketState.CONNECTED;
         FileDescriptor[] fds = mSocket.getAncillaryFileDescriptors();
-        if (DBG) Log.d(TAG, "socket fd passed by stack fds: " + Arrays.toString(fds));
+        if (DBG) Log.d(TAG, "acceptSocket: socket fd passed by stack fds:" + Arrays.toString(fds));
         if (fds == null || fds.length != 1) {
             Log.e(TAG, "socket fd passed from stack failed, fds: " + Arrays.toString(fds));
             as.close();
@@ -450,6 +450,7 @@
                     throw new IOException("bt socket closed");
                 }
                 mSocketState = SocketState.CONNECTED;
+                if (DBG) Log.d(TAG, "connect(), socket connected");
             }
         } catch (RemoteException e) {
             Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
@@ -536,8 +537,8 @@
         if (mSocketState != SocketState.LISTENING) {
             throw new IOException("bt socket is not in listen state");
         }
+        Log.d(TAG, "accept(), timeout (ms):" + timeout);
         if (timeout > 0) {
-            Log.d(TAG, "accept() set timeout (ms):" + timeout);
             mSocket.setSoTimeout(timeout);
         }
         String RemoteAddr = waitSocketSignal(mSocketIS);
diff --git a/framework/java/android/bluetooth/BluetoothStatusCodes.java b/framework/java/android/bluetooth/BluetoothStatusCodes.java
index 35e03f5..24be7fc 100644
--- a/framework/java/android/bluetooth/BluetoothStatusCodes.java
+++ b/framework/java/android/bluetooth/BluetoothStatusCodes.java
@@ -216,6 +216,12 @@
     public static final int ERROR_REMOTE_OPERATION_NOT_SUPPORTED = 27;
 
     /**
+     * Indicates that the feature status is not configured yet.
+     * @hide
+     */
+    public static final int FEATURE_NOT_CONFIGURED = 30;
+
+    /**
      * A GATT writeCharacteristic request is not permitted on the remote device.
      */
     public static final int ERROR_GATT_WRITE_NOT_ALLOWED = 200;
diff --git a/framework/tests/unit/Android.bp b/framework/tests/unit/Android.bp
index 23d9db2..a976d96 100644
--- a/framework/tests/unit/Android.bp
+++ b/framework/tests/unit/Android.bp
@@ -30,5 +30,4 @@
         "general-tests",
         "mts-bluetooth",
     ],
-    certificate: ":com.android.bluetooth.certificate",
 }
diff --git a/framework/tests/unit/AndroidTest.xml b/framework/tests/unit/AndroidTest.xml
index f6dfc2d..4c04e11 100644
--- a/framework/tests/unit/AndroidTest.xml
+++ b/framework/tests/unit/AndroidTest.xml
@@ -19,6 +19,10 @@
         <option name="test-file-name" value="FrameworkBluetoothTests.apk" />
     </target_preparer>
 
+    <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer">
+        <option name="force-root" value="true" />
+    </target_preparer>
+
     <option name="test-suite-tag" value="apct"/>
     <option name="test-tag" value="FrameworkBluetoothTests"/>
 
diff --git a/pandora/interfaces/pandora_experimental/hfp.proto b/pandora/interfaces/pandora_experimental/hfp.proto
index 496e665..2406165 100644
--- a/pandora/interfaces/pandora_experimental/hfp.proto
+++ b/pandora/interfaces/pandora_experimental/hfp.proto
@@ -31,6 +31,28 @@
   rpc SetVoiceRecognition(SetVoiceRecognitionRequest) returns (SetVoiceRecognitionResponse);
   // Clear the call history
   rpc ClearCallHistory(ClearCallHistoryRequest) returns (ClearCallHistoryResponse);
+  // Answer an incoming call from a peer device (as a handsfree)
+  rpc AnswerCallAsHandsfree(AnswerCallAsHandsfreeRequest) returns (AnswerCallAsHandsfreeResponse);
+  // End a call from a peer device (as a handsfree)
+  rpc EndCallAsHandsfree(EndCallAsHandsfreeRequest) returns (EndCallAsHandsfreeResponse);
+  // Decline an incoming call from a peer device (as a handsfree)
+  rpc DeclineCallAsHandsfree(DeclineCallAsHandsfreeRequest) returns (DeclineCallAsHandsfreeResponse);
+  // Connect to an incoming audio stream from a peer device (as a handsfree)
+  rpc ConnectToAudioAsHandsfree(ConnectToAudioAsHandsfreeRequest) returns (ConnectToAudioAsHandsfreeResponse);
+  // Disonnect from an incoming audio stream from a peer device (as a handsfree)
+  rpc DisconnectFromAudioAsHandsfree(DisconnectFromAudioAsHandsfreeRequest) returns (DisconnectFromAudioAsHandsfreeResponse);
+  // Make a call to a given phone number (as a handsfree)
+  rpc MakeCallAsHandsfree(MakeCallAsHandsfreeRequest) returns (MakeCallAsHandsfreeResponse);
+  // Connect a call on hold, and disconnect the current call (as a handsfree)
+  rpc CallTransferAsHandsfree(CallTransferAsHandsfreeRequest) returns (CallTransferAsHandsfreeResponse);
+  // Enable Service level connection (as a handsfree)
+  rpc EnableSlcAsHandsfree(EnableSlcAsHandsfreeRequest) returns (google.protobuf.Empty);
+  // Disable Service level connection (as a handsfree)
+  rpc DisableSlcAsHandsfree(DisableSlcAsHandsfreeRequest) returns (google.protobuf.Empty);
+  // Set voice recognition (as a handsfree)
+  rpc SetVoiceRecognitionAsHandsfree(SetVoiceRecognitionAsHandsfreeRequest) returns (SetVoiceRecognitionAsHandsfreeResponse);
+  // Send DTMF code from the handsfree
+  rpc SendDtmfFromHandsfree(SendDtmfFromHandsfreeRequest) returns (SendDtmfFromHandsfreeResponse);
 }
 
 // Request of the `EnableSlc` method.
@@ -99,3 +121,68 @@
 message ClearCallHistoryRequest {}
 
 message ClearCallHistoryResponse {}
+
+message AnswerCallAsHandsfreeRequest {
+  Connection connection = 1;
+}
+
+message AnswerCallAsHandsfreeResponse {}
+
+message EndCallAsHandsfreeRequest {
+  Connection connection = 1;
+}
+
+message EndCallAsHandsfreeResponse {}
+
+message DeclineCallAsHandsfreeRequest {
+  Connection connection = 1;
+}
+
+message DeclineCallAsHandsfreeResponse {}
+
+message ConnectToAudioAsHandsfreeRequest {
+  Connection connection = 1;
+}
+
+message ConnectToAudioAsHandsfreeResponse {}
+
+message DisconnectFromAudioAsHandsfreeRequest {
+  Connection connection = 1;
+}
+
+message DisconnectFromAudioAsHandsfreeResponse {}
+
+message MakeCallAsHandsfreeRequest {
+  Connection connection = 1;
+  string number = 2;
+}
+
+message MakeCallAsHandsfreeResponse {}
+
+message CallTransferAsHandsfreeRequest {
+  Connection connection = 1;
+}
+
+message CallTransferAsHandsfreeResponse {}
+
+message EnableSlcAsHandsfreeRequest {
+  Connection connection = 1;
+}
+
+message DisableSlcAsHandsfreeRequest {
+  Connection connection = 1;
+}
+
+message SetVoiceRecognitionAsHandsfreeRequest {
+  Connection connection = 1;
+  bool enabled = 2;
+}
+
+message SetVoiceRecognitionAsHandsfreeResponse {}
+
+message SendDtmfFromHandsfreeRequest {
+  Connection connection = 1;
+  uint32 code = 2;
+}
+
+message SendDtmfFromHandsfreeResponse {}
diff --git a/service/java/com/android/server/bluetooth/BluetoothManagerService.java b/service/java/com/android/server/bluetooth/BluetoothManagerService.java
index da4b71e..994b438 100644
--- a/service/java/com/android/server/bluetooth/BluetoothManagerService.java
+++ b/service/java/com/android/server/bluetooth/BluetoothManagerService.java
@@ -302,6 +302,8 @@
     // Save a ProfileServiceConnections object for each of the bound
     // bluetooth profile services
     private final Map<Integer, ProfileServiceConnections> mProfileServices = new HashMap<>();
+    @GuardedBy("mProfileServices")
+    private boolean mUnbindingAll = false;
 
     private final IBluetoothCallback mBluetoothCallback = new IBluetoothCallback.Stub() {
         @Override
@@ -333,10 +335,7 @@
 
     @VisibleForTesting
     public void onInitFlagsChanged() {
-        mHandler.removeMessages(MESSAGE_INIT_FLAGS_CHANGED);
-        mHandler.sendEmptyMessageDelayed(
-                MESSAGE_INIT_FLAGS_CHANGED,
-                DELAY_BEFORE_RESTART_DUE_TO_INIT_FLAGS_CHANGED_MS);
+        // TODO(b/265386284)
     }
 
     public boolean onFactoryReset(AttributionSource attributionSource) {
@@ -969,7 +968,7 @@
     }
 
     public int getState() {
-        if ((Binder.getCallingUid() != Process.SYSTEM_UID) && (!checkIfCallerIsForegroundUser())) {
+        if (!isCallerSystem(getCallingAppId()) && !checkIfCallerIsForegroundUser()) {
             Log.w(TAG, "getState(): report OFF for non-active and non system user");
             return BluetoothAdapter.STATE_OFF;
         }
@@ -1146,11 +1145,11 @@
             }
             return false;
         }
-        // Check if packageName belongs to callingUid
-        final int callingUid = Binder.getCallingUid();
-        final boolean isCallerSystem = UserHandle.getAppId(callingUid) == Process.SYSTEM_UID;
-        if (!isCallerSystem && callingUid != Process.SHELL_UID) {
-            checkPackage(callingUid, attributionSource.getPackageName());
+        int callingAppId = getCallingAppId();
+        if (!isCallerSystem(callingAppId)
+                && !isCallerShell(callingAppId)
+                && !isCallerRoot(callingAppId)) {
+            checkPackage(attributionSource.getPackageName());
 
             if (requireForeground && !checkIfCallerIsForegroundUser()) {
                 Log.w(TAG, "Not allowed for non-active and non system user");
@@ -1456,24 +1455,27 @@
     }
 
     /**
-     * Check if AppOpsManager is available and the packageName belongs to uid
+     * Check if AppOpsManager is available and the packageName belongs to calling uid
      *
      * A null package belongs to any uid
      */
-    private void checkPackage(int uid, String packageName) {
+    private void checkPackage(String packageName) {
+        int callingUid = Binder.getCallingUid();
+
         if (mAppOps == null) {
             Log.w(TAG, "checkPackage(): called before system boot up, uid "
-                    + uid + ", packageName " + packageName);
+                    + callingUid + ", packageName " + packageName);
             throw new IllegalStateException("System has not boot yet");
         }
         if (packageName == null) {
-            Log.w(TAG, "checkPackage(): called with null packageName from " + uid);
+            Log.w(TAG, "checkPackage(): called with null packageName from " + callingUid);
             return;
         }
+
         try {
-            mAppOps.checkPackage(uid, packageName);
+            mAppOps.checkPackage(callingUid, packageName);
         } catch (SecurityException e) {
-            Log.w(TAG, "checkPackage(): " + packageName + " does not belong to uid " + uid);
+            Log.w(TAG, "checkPackage(): " + packageName + " does not belong to uid " + callingUid);
             throw new SecurityException(e.getMessage());
         }
     }
@@ -1595,13 +1597,16 @@
                 } catch (IllegalArgumentException e) {
                     Log.e(TAG, "Unable to unbind service with intent: " + psc.mIntent, e);
                 }
-                mProfileServices.remove(profile);
+                if (!mUnbindingAll) {
+                    mProfileServices.remove(profile);
+                }
             }
         }
     }
 
     private void unbindAllBluetoothProfileServices() {
         synchronized (mProfileServices) {
+            mUnbindingAll = true;
             for (Integer i : mProfileServices.keySet()) {
                 ProfileServiceConnections psc = mProfileServices.get(i);
                 try {
@@ -1611,6 +1616,7 @@
                 }
                 psc.removeAllProxies();
             }
+            mUnbindingAll = false;
             mProfileServices.clear();
         }
     }
@@ -1909,7 +1915,7 @@
             return null;
         }
 
-        if ((Binder.getCallingUid() != Process.SYSTEM_UID) && (!checkIfCallerIsForegroundUser())) {
+        if (!isCallerSystem(getCallingAppId()) && !checkIfCallerIsForegroundUser()) {
             Log.w(TAG, "getAddress(): not allowed for non-active and non system user");
             return null;
         }
@@ -1943,7 +1949,7 @@
             return null;
         }
 
-        if ((Binder.getCallingUid() != Process.SYSTEM_UID) && (!checkIfCallerIsForegroundUser())) {
+        if (!isCallerSystem(getCallingAppId()) && !checkIfCallerIsForegroundUser()) {
             Log.w(TAG, "getName(): not allowed for non-active and non system user");
             return null;
         }
@@ -2715,6 +2721,19 @@
         }
     }
 
+    private static int getCallingAppId() {
+        return UserHandle.getAppId(Binder.getCallingUid());
+    }
+    private static boolean isCallerSystem(int callingAppId) {
+        return callingAppId == Process.SYSTEM_UID;
+    }
+    private static boolean isCallerShell(int callingAppId) {
+        return callingAppId == Process.SHELL_UID;
+    }
+    private static boolean isCallerRoot(int callingAppId) {
+        return callingAppId == Process.ROOT_UID;
+    }
+
     private boolean checkIfCallerIsForegroundUser() {
         int callingUid = Binder.getCallingUid();
         UserHandle callingUser = UserHandle.getUserHandleForUid(callingUid);
@@ -2832,11 +2851,18 @@
             sendBluetoothStateCallback(isUp);
             sendBleStateChanged(prevState, newState);
 
-        } else if (newState == BluetoothAdapter.STATE_BLE_TURNING_ON
-                || newState == BluetoothAdapter.STATE_BLE_TURNING_OFF) {
+        } else if (newState == BluetoothAdapter.STATE_BLE_TURNING_ON) {
             sendBleStateChanged(prevState, newState);
             isStandardBroadcast = false;
-
+        } else if (newState == BluetoothAdapter.STATE_BLE_TURNING_OFF) {
+            sendBleStateChanged(prevState, newState);
+            if (prevState != BluetoothAdapter.STATE_TURNING_OFF) {
+                isStandardBroadcast = false;
+            } else {
+                // Broadcast as STATE_OFF for app that do not receive BLE update
+                newState = BluetoothAdapter.STATE_OFF;
+                sendBrEdrDownCallback(mContext.getAttributionSource());
+            }
         } else if (newState == BluetoothAdapter.STATE_TURNING_ON
                 || newState == BluetoothAdapter.STATE_TURNING_OFF) {
             sendBleStateChanged(prevState, newState);
diff --git a/sysprop/Android.bp b/sysprop/Android.bp
new file mode 100644
index 0000000..67cc6eb
--- /dev/null
+++ b/sysprop/Android.bp
@@ -0,0 +1,20 @@
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+sysprop_library {
+  name: "com.android.sysprop.bluetooth",
+  host_supported: true,
+  srcs: [
+    "avrcp.sysprop",
+    "bta.sysprop",
+    "hfp.sysprop",
+    "pan.sysprop",
+  ],
+  property_owner: "Platform",
+  api_packages: ["android.sysprop"],
+  cpp: {
+    min_sdk_version: "Tiramisu",
+  },
+  apex_available: ["com.android.btservices"],
+}
diff --git a/sysprop/OWNERS b/sysprop/OWNERS
new file mode 100644
index 0000000..263e053
--- /dev/null
+++ b/sysprop/OWNERS
@@ -0,0 +1,2 @@
+licorne@google.com
+wescande@google.com
diff --git a/sysprop/avrcp.sysprop b/sysprop/avrcp.sysprop
new file mode 100644
index 0000000..6c12087
--- /dev/null
+++ b/sysprop/avrcp.sysprop
@@ -0,0 +1,11 @@
+module: "android.sysprop.bluetooth.Avrcp"
+owner: Platform
+
+prop {
+    api_name: "absolute_volume"
+    type: Boolean
+    scope: Internal
+    access: Readonly
+    prop_name: "bluetooth.avrcp.absolute_volume.enabled"
+}
+
diff --git a/sysprop/bta.sysprop b/sysprop/bta.sysprop
new file mode 100644
index 0000000..a438a10
--- /dev/null
+++ b/sysprop/bta.sysprop
@@ -0,0 +1,12 @@
+module: "android.sysprop.bluetooth.Bta"
+owner: Platform
+
+prop {
+    api_name: "disable_delay"
+    type: Integer
+    scope: Internal
+    access: Readonly
+    prop_name: "bluetooth.bta.disable_delay.millis"
+}
+
+
diff --git a/sysprop/hfp.sysprop b/sysprop/hfp.sysprop
new file mode 100644
index 0000000..f07124f
--- /dev/null
+++ b/sysprop/hfp.sysprop
@@ -0,0 +1,35 @@
+module: "android.sysprop.bluetooth.Hfp"
+owner: Platform
+
+prop {
+    api_name: "hf_client_features"
+    type: Integer
+    scope: Internal
+    access: Readonly
+    prop_name: "bluetooth.hfp.hf_client_features.config"
+}
+
+prop {
+    api_name: "hf_features"
+    type: Integer
+    scope: Internal
+    access: Readonly
+    prop_name: "bluetooth.hfp.hf_features.config"
+}
+
+prop {
+    api_name: "hf_services"
+    type: Integer
+    scope: Internal
+    access: Readonly
+    prop_name: "bluetooth.hfp.hf_services_mask.config"
+}
+
+prop {
+    api_name: "version"
+    type: Integer
+    scope: Internal
+    access: Readonly
+    prop_name: "bluetooth.hfp.version.config"
+}
+
diff --git a/sysprop/pan.sysprop b/sysprop/pan.sysprop
new file mode 100644
index 0000000..fa64bb3
--- /dev/null
+++ b/sysprop/pan.sysprop
@@ -0,0 +1,10 @@
+module: "android.sysprop.bluetooth.Pan"
+owner: Platform
+
+prop {
+    api_name: "nap"
+    type: Boolean
+    scope: Internal
+    access: Readonly
+    prop_name: "bluetooth.pan.nap.enabled"
+}
diff --git a/system/audio_hal_interface/aidl/client_interface_aidl.cc b/system/audio_hal_interface/aidl/client_interface_aidl.cc
index efdc469..9af2803 100644
--- a/system/audio_hal_interface/aidl/client_interface_aidl.cc
+++ b/system/audio_hal_interface/aidl/client_interface_aidl.cc
@@ -55,9 +55,8 @@
 }
 
 bool BluetoothAudioClientInterface::is_aidl_available() {
-  auto service = AServiceManager_checkService(
+  return AServiceManager_isDeclared(
       kDefaultAudioProviderFactoryInterface.c_str());
-  return (service != nullptr);
 }
 
 std::vector<AudioCapabilities>
@@ -72,7 +71,7 @@
     return capabilities;
   }
   auto provider_factory = IBluetoothAudioProviderFactory::fromBinder(
-      ::ndk::SpAIBinder(AServiceManager_getService(
+      ::ndk::SpAIBinder(AServiceManager_waitForService(
           kDefaultAudioProviderFactoryInterface.c_str())));
 
   if (provider_factory == nullptr) {
@@ -91,16 +90,15 @@
 }
 
 void BluetoothAudioClientInterface::FetchAudioProvider() {
-  if (provider_ != nullptr) {
-    LOG(WARNING) << __func__ << ": refetch";
-  } else if (!is_aidl_available()) {
-    // AIDL availability should only be checked at the beginning.
-    // When refetching, AIDL may not be ready *yet* but it's expected to be
-    // available later.
+  if (!is_aidl_available()) {
+    LOG(ERROR) << __func__ << ": aidl is not supported on this platform.";
     return;
   }
+  if (provider_ != nullptr) {
+    LOG(WARNING) << __func__ << ": refetch";
+  }
   auto provider_factory = IBluetoothAudioProviderFactory::fromBinder(
-      ::ndk::SpAIBinder(AServiceManager_getService(
+      ::ndk::SpAIBinder(AServiceManager_waitForService(
           kDefaultAudioProviderFactoryInterface.c_str())));
 
   if (provider_factory == nullptr) {
diff --git a/system/binder/android/bluetooth/BluetoothSinkAudioPolicy.aidl b/system/binder/android/bluetooth/BluetoothSinkAudioPolicy.aidl
new file mode 100644
index 0000000..740249c
--- /dev/null
+++ b/system/binder/android/bluetooth/BluetoothSinkAudioPolicy.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright 2022 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 android.bluetooth;
+
+parcelable BluetoothSinkAudioPolicy;
diff --git a/system/binder/android/bluetooth/IBluetooth.aidl b/system/binder/android/bluetooth/IBluetooth.aidl
index 68237c6..360220c 100644
--- a/system/binder/android/bluetooth/IBluetooth.aidl
+++ b/system/binder/android/bluetooth/IBluetooth.aidl
@@ -25,6 +25,7 @@
 import android.bluetooth.IBluetoothSocketManager;
 import android.bluetooth.IBluetoothStateChangeCallback;
 import android.bluetooth.BluetoothActivityEnergyInfo;
+import android.bluetooth.BluetoothSinkAudioPolicy;
 import android.bluetooth.BluetoothClass;
 import android.bluetooth.BluetoothDevice;
 import android.bluetooth.IncomingRfcommSocketInfo;
@@ -267,6 +268,13 @@
     oneway void allowLowLatencyAudio(in boolean allowed, in BluetoothDevice device, in SynchronousResultReceiver receiver);
 
     @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)")
+    void isRequestAudioPolicyAsSinkSupported(in BluetoothDevice device, in AttributionSource attributionSource, in SynchronousResultReceiver receiver);
+    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)")
+    void requestAudioPolicyAsSink(in BluetoothDevice device, in BluetoothSinkAudioPolicy policies, in AttributionSource attributionSource, in SynchronousResultReceiver receiver);
+    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)")
+    void getRequestedAudioPolicyAsSink(in BluetoothDevice device, in AttributionSource attributionSource, in SynchronousResultReceiver receiver);
+
+    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)")
     oneway void startRfcommListener(String name, in ParcelUuid uuid, in PendingIntent intent, in AttributionSource attributionSource, in SynchronousResultReceiver receiver);
     @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)")
     oneway void stopRfcommListener(in ParcelUuid uuid, in AttributionSource attributionSource, in SynchronousResultReceiver receiver);
diff --git a/system/binder/android/bluetooth/IBluetoothHeadsetClient.aidl b/system/binder/android/bluetooth/IBluetoothHeadsetClient.aidl
index 3e1e83b..ad3316c 100644
--- a/system/binder/android/bluetooth/IBluetoothHeadsetClient.aidl
+++ b/system/binder/android/bluetooth/IBluetoothHeadsetClient.aidl
@@ -17,6 +17,7 @@
 package android.bluetooth;
 
 import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothSinkAudioPolicy;
 import android.bluetooth.BluetoothHeadsetClientCall;
 import android.content.AttributionSource;
 
@@ -86,6 +87,7 @@
     void setAudioRouteAllowed(in BluetoothDevice device, boolean allowed, in AttributionSource attributionSource, in SynchronousResultReceiver receiver);
     @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)")
     void getAudioRouteAllowed(in BluetoothDevice device, in AttributionSource attributionSource, in SynchronousResultReceiver receiver);
+
     @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)")
     void sendVendorAtCommand(in BluetoothDevice device, int vendorId, String atCommand, in AttributionSource attributionSource, in SynchronousResultReceiver receiver);
 
diff --git a/system/blueberry/tests/sl4a_sl4a/l2cap/le_l2cap_coc_test.py b/system/blueberry/tests/sl4a_sl4a/l2cap/le_l2cap_coc_test.py
new file mode 100644
index 0000000..ee94901
--- /dev/null
+++ b/system/blueberry/tests/sl4a_sl4a/l2cap/le_l2cap_coc_test.py
@@ -0,0 +1,169 @@
+#!/usr/bin/env python3
+#
+#   Copyright 2022 - 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.
+
+import binascii
+import io
+import logging
+import os
+import queue
+import time
+
+from blueberry.tests.gd.cert.context import get_current_context
+from blueberry.tests.gd.cert.truth import assertThat
+from blueberry.tests.gd_sl4a.lib.bt_constants import ble_address_types
+from blueberry.tests.sl4a_sl4a.lib import sl4a_sl4a_base_test
+from blueberry.tests.sl4a_sl4a.lib.security import Security
+
+
+class LeL2capCoCTest(sl4a_sl4a_base_test.Sl4aSl4aBaseTestClass):
+
+    def __get_cert_public_address_and_irk_from_bt_config(self):
+        # Pull IRK from SL4A cert side to pass in from SL4A DUT side when scanning
+        bt_config_file_path = os.path.join(get_current_context().get_full_output_path(),
+                                           "DUT_%s_bt_config.conf" % self.cert.serial)
+        try:
+            self.cert.adb.pull(["/data/misc/bluedroid/bt_config.conf", bt_config_file_path])
+        except AdbError as error:
+            logging.error("Failed to pull SL4A cert BT config")
+            return False
+        logging.debug("Reading SL4A cert BT config")
+        with io.open(bt_config_file_path) as f:
+            for line in f.readlines():
+                stripped_line = line.strip()
+                if (stripped_line.startswith("Address")):
+                    address_fields = stripped_line.split(' ')
+                    # API currently requires public address to be capitalized
+                    address = address_fields[2].upper()
+                    logging.debug("Found cert address: %s" % address)
+                    continue
+                if (stripped_line.startswith("LE_LOCAL_KEY_IRK")):
+                    irk_fields = stripped_line.split(' ')
+                    irk = irk_fields[2]
+                    logging.debug("Found cert IRK: %s" % irk)
+                    continue
+
+        return address, irk
+
+    def setup_class(self):
+        super().setup_class()
+
+    def setup_test(self):
+        assertThat(super().setup_test()).isTrue()
+
+    def teardown_test(self):
+        self.dut_scanner_.stop_scanning()
+        self.cert_advertiser_.stop_advertising()
+        self.dut_security_.remove_all_bonded_devices()
+        self.cert_security_.remove_all_bonded_devices()
+        super().teardown_test()
+
+    # Scans for the cert device by name. We expect to get back a RPA.
+    def __scan_for_cert_by_name(self):
+        cert_public_address, irk = self.__get_cert_public_address_and_irk_from_bt_config()
+        self.cert_advertiser_.advertise_public_extended_pdu()
+        advertising_name = self.cert_advertiser_.get_local_advertising_name()
+
+        # Scan with name and verify we get back a scan result with the RPA
+        scan_result_addr = self.dut_scanner_.scan_for_name(advertising_name)
+        assertThat(scan_result_addr).isNotNone()
+        assertThat(scan_result_addr).isNotEqualTo(cert_public_address)
+
+        return scan_result_addr
+
+    def __scan_for_irk(self):
+        cert_public_address, irk = self.__get_cert_public_address_and_irk_from_bt_config()
+        rpa_address = self.cert_advertiser_.advertise_public_extended_pdu()
+        id_addr = self.dut_scanner_.scan_for_address_with_irk(cert_public_address, ble_address_types["public"], irk)
+        self.dut_scanner_.stop_scanning()
+        return id_addr
+
+    def __create_le_bond_oob_single_sided(self,
+                                          wait_for_oob_data=True,
+                                          wait_for_device_bonded=True,
+                                          addr=None,
+                                          addr_type=ble_address_types["random"]):
+        oob_data = self.cert_security_.generate_oob_data(Security.TRANSPORT_LE, wait_for_oob_data)
+        if wait_for_oob_data:
+            assertThat(oob_data[0]).isEqualTo(0)
+            assertThat(oob_data[1]).isNotNone()
+        self.dut_security_.create_bond_out_of_band(oob_data[1], addr, addr_type, wait_for_device_bonded)
+        return oob_data[1].to_sl4a_address()
+
+    def __create_le_bond_oob_double_sided(self,
+                                          wait_for_oob_data=True,
+                                          wait_for_device_bonded=True,
+                                          addr=None,
+                                          addr_type=ble_address_types["random"]):
+        # Genearte OOB data on DUT, but we don't use it
+        self.dut_security_.generate_oob_data(Security.TRANSPORT_LE, wait_for_oob_data)
+        self.__create_le_bond_oob_single_sided(wait_for_oob_data, wait_for_device_bonded, addr, addr_type)
+
+    def __test_le_l2cap_insecure_coc(self):
+        logging.info("Testing insecure L2CAP CoC")
+        cert_rpa = self.__scan_for_cert_by_name()
+
+        # Listen on an insecure l2cap coc on the cert
+        psm = self.cert_l2cap_.listen_using_l2cap_le_coc(False)
+        self.dut_l2cap_.create_l2cap_le_coc(cert_rpa, psm, False)
+
+        # Cleanup
+        self.dut_scanner_.stop_scanning()
+        self.dut_l2cap_.close_l2cap_le_coc_client()
+        self.cert_advertiser_.stop_advertising()
+        self.cert_l2cap_.close_l2cap_le_coc_server()
+
+    def __test_le_l2cap_secure_coc(self):
+        logging.info("Testing secure L2CAP CoC")
+        cert_rpa = self.__create_le_bond_oob_single_sided()
+
+        # Listen on an secure l2cap coc on the cert
+        psm = self.cert_l2cap_.listen_using_l2cap_le_coc(True)
+        self.dut_l2cap_.create_l2cap_le_coc(cert_rpa, psm, True)
+
+        # Cleanup
+        self.dut_scanner_.stop_scanning()
+        self.dut_l2cap_.close_l2cap_le_coc_client()
+        self.cert_advertiser_.stop_advertising()
+        self.cert_l2cap_.close_l2cap_le_coc_server()
+        self.dut_security_.remove_all_bonded_devices()
+        self.cert_security_.remove_all_bonded_devices()
+
+    def __test_le_l2cap_secure_coc_after_irk_scan(self):
+        logging.info("Testing secure L2CAP CoC after IRK scan")
+        cert_config_addr, irk = self.__get_cert_public_address_and_irk_from_bt_config()
+        cert_id_addr = self.__scan_for_irk()
+
+        assertThat(cert_id_addr).isEqualTo(cert_config_addr)
+        self.__create_le_bond_oob_single_sided(True, True, cert_id_addr, ble_address_types["public"])
+        self.cert_advertiser_.stop_advertising()
+        self.__test_le_l2cap_secure_coc()
+
+    def __test_secure_le_l2cap_coc_stress(self):
+        for i in range(0, 10):
+            self.__test_le_l2cap_secure_coc()
+
+    def __test_insecure_le_l2cap_coc_stress(self):
+        for i in range(0, 10):
+            self.__test_le_l2cap_insecure_coc()
+
+    def __test_le_l2cap_coc_stress(self):
+        #for i in range (0, 10):
+        self.__test_le_l2cap_insecure_coc()
+        self.__test_le_l2cap_secure_coc()
+
+    def __test_secure_le_l2cap_coc_after_irk_scan_stress(self):
+        for i in range(0, 10):
+            self.__test_le_l2cap_secure_coc_after_irk_scan()
diff --git a/system/blueberry/tests/sl4a_sl4a/lib/l2cap.py b/system/blueberry/tests/sl4a_sl4a/lib/l2cap.py
index 3957146..9604ab7 100644
--- a/system/blueberry/tests/sl4a_sl4a/lib/l2cap.py
+++ b/system/blueberry/tests/sl4a_sl4a/lib/l2cap.py
@@ -15,24 +15,56 @@
 #   limitations under the License.
 
 import logging
+import queue
 
 from blueberry.tests.gd.cert.truth import assertThat
 
 
 class L2cap:
 
-    __l2cap_connection_timeout = 30  #seconds
+    __l2cap_connection_timeout = 10  #seconds
     __device = None
+    __active_client_coc = False
+    __active_server_coc = False
 
     def __init__(self, device):
         self.__device = device
 
+    def __wait_for_event(self, expected_event_name):
+        try:
+            event_info = self.__device.ed.pop_event(expected_event_name, self.__l2cap_connection_timeout)
+            logging.info(event_info)
+        except queue.Empty as error:
+            logging.error("Failed to find event: %s", expected_event_name)
+            return False
+        return True
+
     def create_l2cap_le_coc(self, address, psm, secure):
         logging.info("creating l2cap channel with secure=%r and psm %s", secure, psm)
         self.__device.sl4a.bluetoothSocketConnBeginConnectThreadPsm(address, True, psm, secure)
+        assertThat(self.__wait_for_event("BluetoothSocketConnectSuccess")).isTrue()
+        self.__active_client_coc = True
 
     # Starts listening on the l2cap server socket, returns the psm
-    def listen_using_l2cap_coc(self, secure):
-        logging.info("Listening for l2cap channel with secure=%r and psm %s", secure, psm)
-        self.__device.sl4a.bluetoothSocketConnBeginAcceptThreadPsm(__l2cap_connection_timeout, True, secure)
+    def listen_using_l2cap_le_coc(self, secure):
+        logging.info("Listening for l2cap channel with secure=%r", secure)
+        self.__device.sl4a.bluetoothSocketConnBeginAcceptThreadPsm(self.__l2cap_connection_timeout, True, secure)
+        self.__active_server_coc = True
         return self.__device.sl4a.bluetoothSocketConnGetPsm()
+
+    def close_l2cap_le_coc_client(self):
+        if self.__active_client_coc:
+            logging.info("Closing LE L2CAP CoC Client")
+            self.__device.sl4a.bluetoothSocketConnKillConnThread()
+            self.__active_client_coc = False
+
+    def close_l2cap_le_coc_server(self):
+        if self.__active_server_coc:
+            logging.info("Closing LE L2CAP CoC Server")
+            self.__device.sl4a.bluetoothSocketConnEndAcceptThread()
+            self.__active_server_coc = False
+
+    def close(self):
+        self.close_l2cap_le_coc_client()
+        self.close_l2cap_le_coc_server()
+        self.__device == None
diff --git a/system/blueberry/tests/sl4a_sl4a/lib/oob_data.py b/system/blueberry/tests/sl4a_sl4a/lib/oob_data.py
index 69f753a..e54c467 100644
--- a/system/blueberry/tests/sl4a_sl4a/lib/oob_data.py
+++ b/system/blueberry/tests/sl4a_sl4a/lib/oob_data.py
@@ -49,4 +49,4 @@
     def to_sl4a_address_type(self):
         if len(self.address) != self.ADDRESS_WITH_TYPE_LENGTH:
             return -1
-        return self.address.upper()[-2]
+        return self.address.upper()[-1]
diff --git a/system/blueberry/tests/sl4a_sl4a/lib/security.py b/system/blueberry/tests/sl4a_sl4a/lib/security.py
index fcfb4ba..78cc758 100644
--- a/system/blueberry/tests/sl4a_sl4a/lib/security.py
+++ b/system/blueberry/tests/sl4a_sl4a/lib/security.py
@@ -37,7 +37,7 @@
     TRANSPORT_LE = "2"
 
     __default_timeout = 10  # seconds
-    __default_bonding_timeout = 30  # seconds
+    __default_bonding_timeout = 60  # seconds
     __device = None
 
     def __init__(self, device):
@@ -70,6 +70,7 @@
                 logging.info("Generating local oob data failed with error code %d", errorcode)
                 return errorcode, None
 
+        logging.info("OOB ADDR with Type: %s", generate_success_event["data"]["address_with_type"])
         return 0, OobData(generate_success_event["data"]["address_with_type"],
                           generate_success_event["data"]["confirmation"], generate_success_event["data"]["randomizer"])
 
@@ -84,13 +85,27 @@
         logging.info("Bonded: %s", bond_state["data"]["bonded_state"])
         assertThat(bond_state["data"]["bonded_state"]).isEqualTo(True)
 
-    def create_bond_out_of_band(self, oob_data, wait_for_device_bonded=True):
+    def create_bond_out_of_band(self,
+                                oob_data,
+                                bt_device_object_address=None,
+                                bt_device_object_address_type=-1,
+                                wait_for_device_bonded=True):
         assertThat(oob_data).isNotNone()
-        address = oob_data.to_sl4a_address()
-        address_type = oob_data.to_sl4a_address_type()
-        logging.info("Bonding OOB with %s and address type=%s", address, address_type)
-        self.__device.sl4a.bluetoothCreateLeBondOutOfBand(address, oob_data.confirmation, oob_data.randomizer,
-                                                          address_type)
+        oob_data_address = oob_data.to_sl4a_address()
+        oob_data_address_type = oob_data.to_sl4a_address_type()
+
+        # If a BT Device object address isn't specified, default to the oob data
+        # address and type
+        if bt_device_object_address is None:
+            bt_device_object_address = oob_data_address
+            bt_device_object_address_type = oob_data_address_type
+
+        logging.info("Bonding OOB with device addr=%s, device addr type=%s, oob addr=%s, oob addr type=%s",
+                     bt_device_object_address, bt_device_object_address_type, oob_data_address, oob_data_address_type)
+        bond_start = self.__device.sl4a.bluetoothCreateLeBondOutOfBand(
+            oob_data_address, oob_data_address_type, oob_data.confirmation, oob_data.randomizer,
+            bt_device_object_address, bt_device_object_address_type)
+        assertThat(bond_start).isTrue()
 
         if wait_for_device_bonded:
             self.ensure_device_bonded()
diff --git a/system/blueberry/tests/sl4a_sl4a/lib/sl4a_sl4a_base_test.py b/system/blueberry/tests/sl4a_sl4a/lib/sl4a_sl4a_base_test.py
index fe37b00..0184288 100644
--- a/system/blueberry/tests/sl4a_sl4a/lib/sl4a_sl4a_base_test.py
+++ b/system/blueberry/tests/sl4a_sl4a/lib/sl4a_sl4a_base_test.py
@@ -26,6 +26,7 @@
 from blueberry.tests.gd_sl4a.lib.ble_lib import enable_bluetooth
 from blueberry.tests.sl4a_sl4a.lib.le_advertiser import LeAdvertiser
 from blueberry.tests.sl4a_sl4a.lib.le_scanner import LeScanner
+from blueberry.tests.sl4a_sl4a.lib.l2cap import L2cap
 from blueberry.tests.sl4a_sl4a.lib.security import Security
 from blueberry.utils.mobly_sl4a_utils import setup_sl4a
 from blueberry.utils.mobly_sl4a_utils import teardown_sl4a
@@ -43,11 +44,13 @@
     dut_advertiser_ = None
     dut_scanner_ = None
     dut_security_ = None
+    dut_l2cap_ = None
 
     # CERT
     cert_advertiser_ = None
     cert_scanner_ = None
     cert_security_ = None
+    cert_l2cap_ = None
 
     SUBPROCESS_WAIT_TIMEOUT_SECONDS = 10
 
@@ -111,9 +114,11 @@
         self.dut_advertiser_ = LeAdvertiser(self.dut)
         self.dut_scanner_ = LeScanner(self.dut)
         self.dut_security_ = Security(self.dut)
+        self.dut_l2cap_ = L2cap(self.dut)
         self.cert_advertiser_ = LeAdvertiser(self.cert)
         self.cert_scanner_ = LeScanner(self.cert)
         self.cert_security_ = Security(self.cert)
+        self.cert_l2cap_ = L2cap(self.cert)
         return True
 
     def teardown_test(self):
@@ -121,13 +126,16 @@
         safeClose(self.dut_advertiser_)
         safeClose(self.dut_scanner_)
         safeClose(self.dut_security_)
+        safeClose(self.dut_l2cap_)
         safeClose(self.cert_advertiser_)
         safeClose(self.cert_scanner_)
         safeClose(self.cert_security_)
+        safeClose(self.cert_l2cap_)
         self.dut_advertiser_ = None
         self.dut_scanner_ = None
         self.dut_security_ = None
         self.cert_advertiser_ = None
+        self.cert_l2cap_ = None
         self.cert_scanner_ = None
         self.cert_security_ = None
 
diff --git a/system/blueberry/tests/sl4a_sl4a/sl4a_sl4a_test_runner.py b/system/blueberry/tests/sl4a_sl4a/sl4a_sl4a_test_runner.py
index 7b2b933..3081aa4 100644
--- a/system/blueberry/tests/sl4a_sl4a/sl4a_sl4a_test_runner.py
+++ b/system/blueberry/tests/sl4a_sl4a/sl4a_sl4a_test_runner.py
@@ -18,6 +18,7 @@
 from blueberry.tests.sl4a_sl4a.gatt.gatt_connect_test import GattConnectTest
 from blueberry.tests.sl4a_sl4a.gatt.gatt_connect_with_irk_test import GattConnectWithIrkTest
 from blueberry.tests.sl4a_sl4a.gatt.gatt_notify_test import GattNotifyTest
+from blueberry.tests.sl4a_sl4a.l2cap.le_l2cap_coc_test import LeL2capCoCTest
 from blueberry.tests.sl4a_sl4a.scanning.le_scanning import LeScanningTest
 from blueberry.tests.sl4a_sl4a.security.irk_rotation_test import IrkRotationTest
 from blueberry.tests.sl4a_sl4a.security.oob_pairing_test import OobPairingTest
@@ -31,6 +32,7 @@
     GattNotifyTest,
     IrkRotationTest,
     LeAdvertisingTest,
+    LeL2capCoCTest,
     LeScanningTest,
     OobPairingTest,
 ]
diff --git a/system/bta/Android.bp b/system/bta/Android.bp
index e22f8b8..513599e 100644
--- a/system/bta/Android.bp
+++ b/system/bta/Android.bp
@@ -126,6 +126,7 @@
         "hh/bta_hh_le.cc",
         "hh/bta_hh_main.cc",
         "hh/bta_hh_utils.cc",
+        "hfp/bta_hfp_api.cc",
         "hd/bta_hd_act.cc",
         "hd/bta_hd_api.cc",
         "hd/bta_hd_main.cc",
@@ -147,6 +148,7 @@
     static_libs: [
         "avrcp-target-service",
         "lib-bt-packets",
+        "libcom.android.sysprop.bluetooth",
     ],
     generated_headers: [
         "LeAudioSetConfigSchemas_h",
@@ -187,6 +189,7 @@
         "libbt-audio-hal-interface",
         "libbluetooth-types",
         "libbt-protos-lite",
+        "libcom.android.sysprop.bluetooth",
         "libosi",
         "libbt-common",
     ],
@@ -297,6 +300,7 @@
         "libbt-common",
         "libbt-protos-lite",
         "libbtcore",
+        "libcom.android.sysprop.bluetooth",
         "libflatbuffers-cpp",
         "libgmock",
     ],
@@ -326,6 +330,7 @@
         "packages/modules/Bluetooth/system/utils/include",
     ],
     srcs: [
+        "hf_client/bta_hf_client_api.cc",
         "test/bta_hf_client_add_record_test.cc",
     ],
     header_libs: ["libbluetooth_headers"],
@@ -335,6 +340,7 @@
     ],
     static_libs: [
         "libbluetooth-types",
+        "libcom.android.sysprop.bluetooth",
         "libosi",
     ],
     cflags: ["-DBUILDCFG"],
@@ -889,6 +895,7 @@
         "test/common/btm_api_mock.cc",
         "test/common/mock_controller.cc",
         "test/common/mock_csis_client.cc",
+        ":TestStubOsi",
     ],
     shared_libs: [
         "libprotobuf-cpp-lite",
diff --git a/system/bta/BUILD.gn b/system/bta/BUILD.gn
index db8db54..22cf22e 100644
--- a/system/bta/BUILD.gn
+++ b/system/bta/BUILD.gn
@@ -74,6 +74,7 @@
     "hh/bta_hh_le.cc",
     "hh/bta_hh_main.cc",
     "hh/bta_hh_utils.cc",
+    "hfp/bta_hfp_api.cc",
     "hd/bta_hd_act.cc",
     "hd/bta_hd_api.cc",
     "hd/bta_hd_main.cc",
diff --git a/system/bta/ag/bta_ag_sdp.cc b/system/bta/ag/bta_ag_sdp.cc
index 46e5825..27503b1 100644
--- a/system/bta/ag/bta_ag_sdp.cc
+++ b/system/bta/ag/bta_ag_sdp.cc
@@ -161,7 +161,7 @@
   /* add profile descriptor list */
   if (service_uuid == UUID_SERVCLASS_AG_HANDSFREE) {
     profile_uuid = UUID_SERVCLASS_HF_HANDSFREE;
-    version = BTA_HFP_VERSION;
+    version = get_default_hfp_version();
   } else {
     profile_uuid = UUID_SERVCLASS_HEADSET;
     version = HSP_VERSION_1_2;
diff --git a/system/bta/av/bta_av_cfg.cc b/system/bta/av/bta_av_cfg.cc
index d2a7d8a..bebd05e 100644
--- a/system/bta/av/bta_av_cfg.cc
+++ b/system/bta/av/bta_av_cfg.cc
@@ -92,16 +92,16 @@
   (sizeof(bta_av_meta_caps_evt_ids) / sizeof(bta_av_meta_caps_evt_ids[0]))
 #endif /* BTA_AV_NUM_RC_EVT_IDS */
 
-const uint8_t bta_avk_meta_caps_evt_ids[] = {
-#if (AVRC_ADV_CTRL_INCLUDED == TRUE)
-    AVRC_EVT_VOLUME_CHANGE,
-#endif
-};
-
-#ifndef BTA_AVK_NUM_RC_EVT_IDS
-#define BTA_AVK_NUM_RC_EVT_IDS \
-  (sizeof(bta_avk_meta_caps_evt_ids) / sizeof(bta_avk_meta_caps_evt_ids[0]))
-#endif /* BTA_AVK_NUM_RC_EVT_IDS */
+const uint8_t* get_bta_avk_meta_caps_evt_ids() {
+  if (avrcp_absolute_volume_is_enabled()) {
+    static const uint8_t bta_avk_meta_caps_evt_ids[] = {
+        AVRC_EVT_VOLUME_CHANGE,
+    };
+    return bta_avk_meta_caps_evt_ids;
+  } else {
+    return {};
+  }
+}
 
 // These are the only events used with AVRCP1.3
 const uint8_t bta_av_meta_caps_evt_ids_avrcp13[] = {
@@ -113,7 +113,7 @@
 #define BTA_AV_NUM_RC_EVT_IDS_AVRCP13         \
   (sizeof(bta_av_meta_caps_evt_ids_avrcp13) / \
    sizeof(bta_av_meta_caps_evt_ids_avrcp13[0]))
-#endif /* BTA_AVK_NUM_RC_EVT_IDS_AVRCP13 */
+#endif /* BTA_AV_NUM_RC_EVT_IDS_AVRCP13 */
 
 /* This configuration to be used when we are Src + TG + CT( only for abs vol) */
 extern const tBTA_AV_CFG bta_av_cfg = {
@@ -136,23 +136,29 @@
 
 /* This configuration to be used when we are Sink + CT + TG( only for abs vol)
  */
-extern const tBTA_AV_CFG bta_avk_cfg = {
-    AVRC_CO_METADATA,   /* AVRCP Company ID */
-    BTA_AVK_RC_SUPF_CT, /* AVRCP controller categories */
-    BTA_AVK_RC_SUPF_TG, /* AVRCP target categories */
-    6,                  /* AVDTP audio channel max data queue size */
-    false,              /* true, to accept AVRC 1.3 group nevigation command */
-    2,                  /* company id count in p_meta_co_ids */
-    BTA_AVK_NUM_RC_EVT_IDS,    /* event id count in p_meta_evt_ids */
-    BTA_AV_RC_PASS_RSP_CODE,   /* the default response code for pass
-                                  through commands */
-    bta_av_meta_caps_co_ids,   /* the metadata Get Capabilities response
-                                  for company id */
-    bta_avk_meta_caps_evt_ids, /* the the metadata Get Capabilities
-                                  response for event id */
-    {0},                       /* Default AVRCP controller name */
-    {0},                       /* Default AVRCP target name */
-};
+
+const tBTA_AV_CFG* get_bta_avk_cfg() {
+  static const tBTA_AV_CFG bta_avk_cfg = {
+      AVRC_CO_METADATA,   /* AVRCP Company ID */
+      BTA_AVK_RC_SUPF_CT, /* AVRCP controller categories */
+      BTA_AVK_RC_SUPF_TG, /* AVRCP target categories */
+      6,                  /* AVDTP audio channel max data queue size */
+      false, /* true, to accept AVRC 1.3 group nevigation command */
+      2,     /* company id count in p_meta_co_ids */
+      (uint8_t)(avrcp_absolute_volume_is_enabled()
+                    ? 1
+                    : 0),              /* event id count in p_meta_evt_ids */
+      BTA_AV_RC_PASS_RSP_CODE,         /* the default response code for pass
+                                          through commands */
+      bta_av_meta_caps_co_ids,         /* the metadata Get Capabilities response
+                                          for company id */
+      get_bta_avk_meta_caps_evt_ids(), /* the metadata Get Capabilities response
+                                          for event id */
+      {0},                             /* Default AVRCP controller name */
+      {0},                             /* Default AVRCP target name */
+  };
+  return &bta_avk_cfg;
+}
 
 /* This configuration to be used when we are using AVRCP1.3 */
 extern const tBTA_AV_CFG bta_av_cfg_compatibility = {
diff --git a/system/bta/av/bta_av_int.h b/system/bta/av/bta_av_int.h
index 94e2f5e..f50f5bc 100644
--- a/system/bta/av/bta_av_int.h
+++ b/system/bta/av/bta_av_int.h
@@ -690,7 +690,7 @@
 
 /* config struct */
 extern const tBTA_AV_CFG* p_bta_av_cfg;
-extern const tBTA_AV_CFG bta_avk_cfg;
+const tBTA_AV_CFG* get_bta_avk_cfg();
 extern const tBTA_AV_CFG bta_av_cfg;
 extern const tBTA_AV_CFG bta_av_cfg_compatibility;
 
diff --git a/system/bta/av/bta_av_main.cc b/system/bta/av/bta_av_main.cc
index b4f9e8e..dfbd90d 100644
--- a/system/bta/av/bta_av_main.cc
+++ b/system/bta/av/bta_av_main.cc
@@ -432,7 +432,7 @@
 
   uint16_t profile_initialized = p_data->api_reg.service_uuid;
   if (profile_initialized == UUID_SERVCLASS_AUDIO_SINK) {
-    p_bta_av_cfg = &bta_avk_cfg;
+    p_bta_av_cfg = get_bta_avk_cfg();
   } else if (profile_initialized == UUID_SERVCLASS_AUDIO_SOURCE) {
     p_bta_av_cfg = &bta_av_cfg;
 
diff --git a/system/bta/csis/csis_client.cc b/system/bta/csis/csis_client.cc
index a1a1f45..d24c02c 100644
--- a/system/bta/csis/csis_client.cc
+++ b/system/bta/csis/csis_client.cc
@@ -338,11 +338,16 @@
       }
 
       /* In case of GATT ERROR */
-      LOG(ERROR) << __func__ << " Incorrect write status "
-                 << loghex((int)(status));
+      LOG_ERROR("Incorrect write status=0x%02x", (int)(status));
 
       /* Unlock previous devices */
       HandleCsisLockProcedureError(csis_group, device);
+
+      if (status == GATT_DATABASE_OUT_OF_SYNC) {
+        LOG_INFO("Database out of sync for %s",
+                 device->addr.ToString().c_str());
+        ClearDeviceInformationAndStartSearch(device);
+      }
       return;
     }
 
@@ -839,6 +844,11 @@
       BtaGattQueue::Clean(conn_id);
       return;
     }
+
+    if (status == GATT_DATABASE_OUT_OF_SYNC) {
+      LOG_INFO("Database out of sync for %s", device->addr.ToString().c_str());
+      ClearDeviceInformationAndStartSearch(device);
+    }
   }
 
   void OnCsisNotification(uint16_t conn_id, uint16_t handle, uint16_t len,
@@ -955,13 +965,17 @@
       return;
     }
 
-    DLOG(INFO) << __func__ << " " << device->addr
-               << " status: " << loghex(+status);
+    LOG_DEBUG("%s, status: 0x%02x", device->addr.ToString().c_str(), status);
 
     if (status != GATT_SUCCESS) {
-      LOG(ERROR) << __func__ << " Could not read characteristic at handle="
-                 << loghex(handle);
-      BTA_GATTC_Close(device->conn_id);
+      if (status == GATT_DATABASE_OUT_OF_SYNC) {
+        LOG_INFO("Database out of sync for %s",
+                 device->addr.ToString().c_str());
+        ClearDeviceInformationAndStartSearch(device);
+      } else {
+        LOG_ERROR("Could not read characteristic at handle=0x%04x", handle);
+        BTA_GATTC_Close(device->conn_id);
+      }
       return;
     }
 
@@ -999,13 +1013,17 @@
       return;
     }
 
-    LOG(INFO) << __func__ << " " << device->addr
-              << " status: " << loghex(+status);
+    LOG_INFO("%s, status 0x%02x", device->addr.ToString().c_str(), status);
 
     if (status != GATT_SUCCESS) {
-      LOG(ERROR) << __func__ << " Could not read characteristic at handle="
-                 << loghex(handle);
-      BTA_GATTC_Close(device->conn_id);
+      if (status == GATT_DATABASE_OUT_OF_SYNC) {
+        LOG_INFO("Database out of sync for %s",
+                 device->addr.ToString().c_str());
+        ClearDeviceInformationAndStartSearch(device);
+      } else {
+        LOG_ERROR("Could not read characteristic at handle=0x%04x", handle);
+        BTA_GATTC_Close(device->conn_id);
+      }
       return;
     }
 
@@ -1034,13 +1052,18 @@
       return;
     }
 
-    DLOG(INFO) << __func__ << " " << device->addr
-               << " status: " << loghex(+status) << " rank:" << int(value[0]);
+    LOG_DEBUG("%s, status: 0x%02x, rank: %d", device->addr.ToString().c_str(),
+              status, value[0]);
 
     if (status != GATT_SUCCESS) {
-      LOG(ERROR) << __func__ << " Could not read characteristic at handle="
-                 << loghex(handle);
-      BTA_GATTC_Close(device->conn_id);
+      if (status == GATT_DATABASE_OUT_OF_SYNC) {
+        LOG_INFO("Database out of sync for %s",
+                 device->addr.ToString().c_str());
+        ClearDeviceInformationAndStartSearch(device);
+      } else {
+        LOG_ERROR("Could not read characteristic at handle=0x%04x", handle);
+        BTA_GATTC_Close(device->conn_id);
+      }
       return;
     }
 
@@ -1131,14 +1154,14 @@
   std::vector<RawAddress> GetAllRsiFromAdvertising(
       const tBTA_DM_INQ_RES* result) {
     const uint8_t* p_service_data = result->p_eir;
-    uint16_t remaining_data_len = result->eir_len;
     std::vector<RawAddress> devices;
     uint8_t service_data_len = 0;
 
     while ((p_service_data = AdvertiseDataParser::GetFieldByType(
                 p_service_data + service_data_len,
-                (remaining_data_len -= service_data_len), BTM_BLE_AD_TYPE_RSI,
-                &service_data_len))) {
+                result->eir_len - (p_service_data - result->p_eir) -
+                    service_data_len,
+                BTM_BLE_AD_TYPE_RSI, &service_data_len))) {
       RawAddress bda;
       STREAM_TO_BDADDR(bda, p_service_data);
       devices.push_back(std::move(bda));
@@ -1333,17 +1356,21 @@
       return;
     }
 
-    DLOG(INFO) << __func__ << " " << device->addr
-               << " status: " << loghex(+status);
+    LOG_DEBUG("%s, status: 0x%02x", device->addr.ToString().c_str(), status);
 
     if (status != GATT_SUCCESS) {
       /* TODO handle error codes:
        * kCsisErrorCodeLockAccessSirkRejected
        * kCsisErrorCodeLockOobSirkOnly
        */
-      LOG(ERROR) << __func__ << " Could not read characteristic at handle="
-                 << loghex(handle);
-      BTA_GATTC_Close(device->conn_id);
+      if (status == GATT_DATABASE_OUT_OF_SYNC) {
+        LOG_INFO("Database out of sync for %s",
+                 device->addr.ToString().c_str());
+        ClearDeviceInformationAndStartSearch(device);
+      } else {
+        LOG_ERROR("Could not read characteristic at handle=0x%04x", handle);
+        BTA_GATTC_Close(device->conn_id);
+      }
       return;
     }
 
@@ -1438,9 +1465,7 @@
       CsisActiveDiscovery(csis_group);
   }
 
-  void DoDisconnectCleanUp(std::shared_ptr<CsisDevice> device) {
-    DLOG(INFO) << __func__ << ": device=" << device->addr;
-
+  void DeregisterNotifications(std::shared_ptr<CsisDevice> device) {
     device->ForEachCsisInstance(
         [&](const std::shared_ptr<CsisInstance>& csis_inst) {
           DisableGattNotification(device->conn_id, device->addr,
@@ -1450,6 +1475,12 @@
           DisableGattNotification(device->conn_id, device->addr,
                                   csis_inst->svc_data.size_handle.val_hdl);
         });
+  }
+
+  void DoDisconnectCleanUp(std::shared_ptr<CsisDevice> device) {
+    LOG_INFO("%s", device->addr.ToString().c_str());
+
+    DeregisterNotifications(device);
 
     if (device->IsConnected()) {
       BtaGattQueue::Clean(device->conn_id);
@@ -1845,6 +1876,22 @@
     }
   }
 
+  void ClearDeviceInformationAndStartSearch(
+      std::shared_ptr<CsisDevice> device) {
+    LOG_INFO("%s ", device->addr.ToString().c_str());
+    if (device->is_gatt_service_valid == false) {
+      LOG_DEBUG("Device database already invalidated.");
+      return;
+    }
+
+    /* Invalidate service discovery results */
+    BtaGattQueue::Clean(device->conn_id);
+    device->first_connection = true;
+    DeregisterNotifications(device);
+    device->ClearSvcData();
+    BTA_GATTC_ServiceSearchRequest(device->conn_id, &kCsisServiceUuid);
+  }
+
   void OnGattServiceChangeEvent(const RawAddress& address) {
     auto device = FindDeviceByAddress(address);
     if (!device) {
@@ -1852,12 +1899,8 @@
       return;
     }
 
-    DLOG(INFO) << __func__ << ": address=" << address;
-
-    /* Invalidate service discovery results */
-    BtaGattQueue::Clean(device->conn_id);
-    device->first_connection = true;
-    device->ClearSvcData();
+    LOG_INFO("%s", address.ToString().c_str());
+    ClearDeviceInformationAndStartSearch(device);
   }
 
   void OnGattServiceDiscoveryDoneEvent(const RawAddress& address) {
diff --git a/system/bta/csis/csis_client_test.cc b/system/bta/csis/csis_client_test.cc
index 6cac395..b7ca1db 100644
--- a/system/bta/csis/csis_client_test.cc
+++ b/system/bta/csis/csis_client_test.cc
@@ -1124,6 +1124,42 @@
   TestAppUnregister();
 }
 
+TEST_F(CsisClientTest, test_database_out_of_sync) {
+  auto test_address = GetTestAddress(0);
+  auto conn_id = 1;
+
+  TestAppRegister();
+  SetSampleDatabaseCsis(conn_id, 1);
+  TestConnect(test_address);
+  InjectConnectedEvent(test_address, conn_id);
+  GetSearchCompleteEvent(conn_id);
+  ASSERT_EQ(1, CsisClient::Get()->GetGroupId(
+                   test_address, bluetooth::Uuid::From16Bit(0x0000)));
+
+  // Simulated database changed on the remote side.
+  ON_CALL(gatt_queue, WriteCharacteristic(_, _, _, _, _, _))
+      .WillByDefault(
+          Invoke([this](uint16_t conn_id, uint16_t handle,
+                        std::vector<uint8_t> value, tGATT_WRITE_TYPE write_type,
+                        GATT_WRITE_OP_CB cb, void* cb_data) {
+            auto* svc = gatt::FindService(services_map[conn_id], handle);
+            if (svc == nullptr) return;
+
+            tGATT_STATUS status = GATT_DATABASE_OUT_OF_SYNC;
+            if (cb)
+              cb(conn_id, status, handle, value.size(), value.data(), cb_data);
+          }));
+
+  ON_CALL(gatt_interface, ServiceSearchRequest(_, _)).WillByDefault(Return());
+  EXPECT_CALL(gatt_interface, ServiceSearchRequest(_, _));
+  CsisClient::Get()->LockGroup(
+      1, true,
+      base::BindOnce([](int group_id, bool locked, CsisGroupLockStatus status) {
+        csis_lock_callback_mock->CsisGroupLockCb(group_id, locked, status);
+      }));
+  TestAppUnregister();
+}
+
 }  // namespace
 }  // namespace internal
 }  // namespace csis
diff --git a/system/bta/dm/bta_dm_act.cc b/system/bta/dm/bta_dm_act.cc
index 1896cc9..711014b 100644
--- a/system/bta/dm/bta_dm_act.cc
+++ b/system/bta/dm/bta_dm_act.cc
@@ -26,6 +26,9 @@
 #define LOG_TAG "bt_bta_dm"
 
 #include <base/logging.h>
+#ifdef OS_ANDROID
+#include <bta.sysprop.h>
+#endif
 
 #include <cstdint>
 
@@ -157,16 +160,18 @@
 #define BTA_DM_SWITCH_DELAY_TIMER_MS 500
 #endif
 
-namespace {
-
 // Time to wait after receiving shutdown request to delay the actual shutdown
 // process. This time may be zero which invokes immediate shutdown.
-#ifndef BTA_DISABLE_DELAY
-constexpr uint64_t kDisableDelayTimerInMs = 0;
+static uint64_t get_DisableDelayTimerInMs() {
+#ifndef OS_ANDROID
+  return 200;
 #else
-constexpr uint64_t kDisableDelayTimerInMs =
-    static_cast<uint64_t>(BTA_DISABLE_DELAY);
+  static const uint64_t kDisableDelayTimerInMs =
+      android::sysprop::bluetooth::Bta::disable_delay().value_or(200);
+  return kDisableDelayTimerInMs;
 #endif
+}
+namespace {
 
 struct WaitForAllAclConnectionsToDrain {
   uint64_t time_to_wait_in_ms;
@@ -353,8 +358,10 @@
    * graceful shutdown.
    */
   bta_dm_search_cb.search_timer = alarm_new("bta_dm_search.search_timer");
+  bool delay_close_gatt =
+      osi_property_get_bool("bluetooth.gatt.delay_close.enabled", true);
   bta_dm_search_cb.gatt_close_timer =
-      alarm_new("bta_dm_search.gatt_close_timer");
+      delay_close_gatt ? alarm_new("bta_dm_search.gatt_close_timer") : nullptr;
   bta_dm_search_cb.pending_discovery_queue = fixed_queue_new(SIZE_MAX);
 
   memset(&bta_dm_conn_srvcs, 0, sizeof(bta_dm_conn_srvcs));
@@ -448,15 +455,15 @@
 
   if (BTM_GetNumAclLinks() == 0) {
     // We can shut down faster if there are no ACL links
-    switch (kDisableDelayTimerInMs) {
+    switch (get_DisableDelayTimerInMs()) {
       case 0:
         LOG_DEBUG("Immediately disabling device manager");
         bta_dm_disable_conn_down_timer_cback(nullptr);
         break;
       default:
         LOG_DEBUG("Set timer to delay disable initiation:%lu ms",
-                  static_cast<unsigned long>(kDisableDelayTimerInMs));
-        alarm_set_on_mloop(bta_dm_cb.disable_timer, kDisableDelayTimerInMs,
+                  static_cast<unsigned long>(get_DisableDelayTimerInMs()));
+        alarm_set_on_mloop(bta_dm_cb.disable_timer, get_DisableDelayTimerInMs(),
                            bta_dm_disable_conn_down_timer_cback, nullptr);
     }
   } else {
@@ -1861,6 +1868,8 @@
   result.inq_res.p_eir = const_cast<uint8_t*>(p_eir);
   result.inq_res.eir_len = eir_len;
 
+  result.inq_res.ble_evt_type = p_inq->ble_evt_type;
+
   p_inq_info = BTM_InqDbRead(p_inq->remote_bd_addr);
   if (p_inq_info != NULL) {
     /* initialize remt_name_not_required to false so that we get the name by
@@ -2545,9 +2554,6 @@
       memset(&bta_dm_cb.device_list.peer_device[clear_index], 0,
              sizeof(bta_dm_cb.device_list.peer_device[clear_index]));
     }
-
-    device->conn_state = BTA_DM_NOT_CONNECTED;
-
     break;
   }
   if (bta_dm_cb.device_list.count) bta_dm_cb.device_list.count--;
@@ -4002,11 +4008,25 @@
   bta_sys_sendmsg(p_msg);
 
   if (conn_id != GATT_INVALID_CONN_ID) {
-    /* start a GATT channel close delay timer */
-    bta_sys_start_timer(bta_dm_search_cb.gatt_close_timer,
-                        BTA_DM_GATT_CLOSE_DELAY_TOUT,
-                        BTA_DM_DISC_CLOSE_TOUT_EVT, 0);
     bta_dm_search_cb.pending_close_bda = bta_dm_search_cb.peer_bdaddr;
+    // Gatt will be close immediately if bluetooth.gatt.delay_close.enabled is
+    // set to false. If property is true / unset there will be a delay
+    if (bta_dm_search_cb.gatt_close_timer != nullptr) {
+      /* start a GATT channel close delay timer */
+      bta_sys_start_timer(bta_dm_search_cb.gatt_close_timer,
+                          BTA_DM_GATT_CLOSE_DELAY_TOUT,
+                          BTA_DM_DISC_CLOSE_TOUT_EVT, 0);
+    } else {
+      p_msg = (tBTA_DM_MSG*)osi_malloc(sizeof(tBTA_DM_MSG));
+      p_msg->hdr.event = BTA_DM_DISC_CLOSE_TOUT_EVT;
+      p_msg->hdr.layer_specific = 0;
+      bta_sys_sendmsg(p_msg);
+    }
+  } else {
+    if (bluetooth::common::init_flags::
+            bta_dm_clear_conn_id_on_client_close_is_enabled()) {
+      bta_dm_search_cb.conn_id = GATT_INVALID_CONN_ID;
+    }
   }
   bta_dm_search_cb.gatt_disc_active = false;
 }
@@ -4139,7 +4159,8 @@
       break;
 
     case BTA_GATTC_CLOSE_EVT:
-      LOG_DEBUG("BTA_GATTC_CLOSE_EVT reason = %d", p_data->close.reason);
+      LOG_INFO("BTA_GATTC_CLOSE_EVT reason = %d", p_data->close.reason);
+
       /* in case of disconnect before search is completed */
       if ((bta_dm_search_cb.state != BTA_DM_SEARCH_IDLE) &&
           (bta_dm_search_cb.state != BTA_DM_SEARCH_ACTIVE) &&
diff --git a/system/bta/dm/bta_dm_main.cc b/system/bta/dm/bta_dm_main.cc
index e844790..59bf00c 100644
--- a/system/bta/dm/bta_dm_main.cc
+++ b/system/bta/dm/bta_dm_main.cc
@@ -61,8 +61,8 @@
  *
  ******************************************************************************/
 bool bta_dm_search_sm_execute(BT_HDR_RIGID* p_msg) {
-  APPL_TRACE_EVENT("bta_dm_search_sm_execute state:%d, event:0x%x",
-                   bta_dm_search_cb.state, p_msg->event);
+  LOG_INFO("bta_dm_search_sm_execute state:%d, event:0x%x",
+           bta_dm_search_get_state(), p_msg->event);
 
   tBTA_DM_MSG* message = (tBTA_DM_MSG*)p_msg;
   switch (bta_dm_search_cb.state) {
@@ -123,6 +123,16 @@
           bta_dm_search_cancel_notify();
           bta_dm_execute_queued_request();
           break;
+        case BTA_DM_DISC_CLOSE_TOUT_EVT:
+          if (bluetooth::common::init_flags::
+                  bta_dm_clear_conn_id_on_client_close_is_enabled()) {
+            bta_dm_close_gatt_conn(message);
+            break;
+          }
+          [[fallthrough]];
+        default:
+          LOG_INFO("Received unexpected event 0x%x in state %d", p_msg->event,
+                   bta_dm_search_cb.state);
       }
       break;
     case BTA_DM_DISCOVER_ACTIVE:
@@ -145,6 +155,16 @@
         case BTA_DM_API_DISCOVER_EVT:
           bta_dm_queue_disc(message);
           break;
+        case BTA_DM_DISC_CLOSE_TOUT_EVT:
+          if (bluetooth::common::init_flags::
+                  bta_dm_clear_conn_id_on_client_close_is_enabled()) {
+            bta_dm_close_gatt_conn(message);
+            break;
+          }
+          [[fallthrough]];
+        default:
+          LOG_INFO("Received unexpected event 0x%x in state %d", p_msg->event,
+                   bta_dm_search_cb.state);
       }
       break;
   }
diff --git a/system/bta/gatt/bta_gattc_act.cc b/system/bta/gatt/bta_gattc_act.cc
index 48aa7b9..c9c791a 100644
--- a/system/bta/gatt/bta_gattc_act.cc
+++ b/system/bta/gatt/bta_gattc_act.cc
@@ -34,6 +34,7 @@
 #include "bta/hh/bta_hh_int.h"
 #include "btif/include/btif_debug_conn.h"
 #include "device/include/controller.h"
+#include "device/include/interop.h"
 #include "main/shim/dumpsys.h"
 #include "osi/include/allocator.h"
 #include "osi/include/log.h"
@@ -595,6 +596,7 @@
     cb_data.close.conn_id = p_data->hdr.layer_specific;
     cb_data.close.remote_bda = p_clcb->bda;
     cb_data.close.reason = BTA_GATT_CONN_NONE;
+    cb_data.close.status = GATT_ERROR;
 
     LOG(WARNING) << __func__ << ": conn_id=" << loghex(cb_data.close.conn_id)
                  << ". Returns GATT_ERROR(" << +GATT_ERROR << ").";
@@ -645,7 +647,7 @@
   bta_gattc_clcb_dealloc(p_clcb);
 
   if (p_data->hdr.event == BTA_GATTC_API_CLOSE_EVT) {
-    GATT_Disconnect(p_data->hdr.layer_specific);
+    cb_data.close.status = GATT_Disconnect(p_data->hdr.layer_specific);
     cb_data.close.reason = GATT_CONN_TERMINATE_LOCAL_HOST;
     LOG_DEBUG("Local close event client_if:%hu conn_id:%hu reason:%s",
               cb_data.close.client_if, cb_data.close.conn_id,
@@ -653,6 +655,7 @@
                   static_cast<tGATT_DISCONN_REASON>(cb_data.close.reason))
                   .c_str());
   } else if (p_data->hdr.event == BTA_GATTC_INT_DISCONN_EVT) {
+    cb_data.close.status = static_cast<tGATT_STATUS>(p_data->int_conn.reason);
     cb_data.close.reason = p_data->int_conn.reason;
     LOG_DEBUG("Peer close disconnect event client_if:%hu conn_id:%hu reason:%s",
               cb_data.close.client_if, cb_data.close.conn_id,
@@ -802,6 +805,18 @@
         p_clcb->p_srcb->srvc_hdl_db_hash = false;
       }
 
+      // Some LMP 5.2 devices also don't support robust caching. This workaround
+      // conditionally disables the feature based on a combination of LMP
+      // version and OUI prefix.
+      if (lmp_version < 0x0c &&
+          interop_match_addr(INTEROP_DISABLE_ROBUST_CACHING, &p_clcb->bda)) {
+        LOG_WARN(
+            "Device LMP version 0x%02x <= Bluetooth 5.2 and MAC addr on "
+            "interop list, skipping robust caching",
+            lmp_version);
+        p_clcb->p_srcb->srvc_hdl_db_hash = false;
+      }
+
       /* read db hash if db hash characteristic exists */
       if (bta_gattc_is_robust_caching_enabled() &&
           p_clcb->p_srcb->srvc_hdl_db_hash &&
diff --git a/system/bta/gatt/bta_gatts_act.cc b/system/bta/gatt/bta_gatts_act.cc
index 7f2457e..800fbf0 100644
--- a/system/bta/gatt/bta_gatts_act.cc
+++ b/system/bta/gatt/bta_gatts_act.cc
@@ -120,6 +120,8 @@
 
     p_cb->enabled = true;
 
+    gatt_load_bonded();
+
     if (!GATTS_NVRegister(&bta_gatts_nv_cback)) {
       LOG(ERROR) << "BTA GATTS NV register failed.";
     }
diff --git a/system/bta/gatt/bta_gatts_api.cc b/system/bta/gatt/bta_gatts_api.cc
index 0de36db..5c69ab4 100644
--- a/system/bta/gatt/bta_gatts_api.cc
+++ b/system/bta/gatt/bta_gatts_api.cc
@@ -242,6 +242,12 @@
 void BTA_GATTS_HandleValueIndication(uint16_t conn_id, uint16_t attr_id,
                                      std::vector<uint8_t> value,
                                      bool need_confirm) {
+
+  if (value.size() > sizeof(tBTA_GATTS_API_INDICATION::value)) {
+    LOG(ERROR) << __func__ << "data to indicate is too long";
+    return;
+  }
+
   tBTA_GATTS_API_INDICATION* p_buf =
       (tBTA_GATTS_API_INDICATION*)osi_calloc(sizeof(tBTA_GATTS_API_INDICATION));
 
@@ -368,3 +374,11 @@
 
   bta_sys_sendmsg(p_buf);
 }
+
+void BTA_GATTS_InitBonded(void) {
+  LOG(INFO) << __func__;
+
+  BT_HDR_RIGID* p_buf = (BT_HDR_RIGID*)osi_malloc(sizeof(BT_HDR_RIGID));
+  p_buf->event = BTA_GATTS_API_INIT_BONDED_EVT;
+  bta_sys_sendmsg(p_buf);
+}
diff --git a/system/bta/gatt/bta_gatts_int.h b/system/bta/gatt/bta_gatts_int.h
index f989e09..7672130 100644
--- a/system/bta/gatt/bta_gatts_int.h
+++ b/system/bta/gatt/bta_gatts_int.h
@@ -51,7 +51,9 @@
   BTA_GATTS_API_OPEN_EVT,
   BTA_GATTS_API_CANCEL_OPEN_EVT,
   BTA_GATTS_API_CLOSE_EVT,
-  BTA_GATTS_API_DISABLE_EVT
+  BTA_GATTS_API_DISABLE_EVT,
+
+  BTA_GATTS_API_INIT_BONDED_EVT,
 };
 typedef uint16_t tBTA_GATTS_INT_EVT;
 
diff --git a/system/bta/gatt/bta_gatts_main.cc b/system/bta/gatt/bta_gatts_main.cc
index 97e9a3d..458f5e2 100644
--- a/system/bta/gatt/bta_gatts_main.cc
+++ b/system/bta/gatt/bta_gatts_main.cc
@@ -105,6 +105,10 @@
       break;
     }
 
+    case BTA_GATTS_API_INIT_BONDED_EVT:
+      gatt_load_bonded();
+      break;
+
     default:
       break;
   }
diff --git a/system/bta/has/has_client.cc b/system/bta/has/has_client.cc
index 43389e1..02d0a23 100644
--- a/system/bta/has/has_client.cc
+++ b/system/bta/has/has_client.cc
@@ -84,6 +84,7 @@
                                            uint8_t features);
 void btif_storage_set_leaudio_has_active_preset(const RawAddress& address,
                                                 uint8_t active_preset);
+void btif_storage_remove_leaudio_has(const RawAddress& address);
 
 extern bool gatt_profile_get_eatt_support(const RawAddress& remote_bda);
 
@@ -309,6 +310,11 @@
     auto op = op_opt.value();
     callbacks_->OnActivePresetSelectError(op.addr_or_group,
                                           GattStatus2SvcErrorCode(status));
+
+    if (status == GATT_DATABASE_OUT_OF_SYNC) {
+      LOG_INFO("Database out of sync for %s", device->addr.ToString().c_str());
+      ClearDeviceInformationAndStartSearch(device);
+    }
   }
 
   void OnHasPresetNameSetStatus(uint16_t conn_id, tGATT_STATUS status,
@@ -339,6 +345,10 @@
     auto op = op_opt.value();
     callbacks_->OnSetPresetNameError(device->addr, op.index,
                                      GattStatus2SvcErrorCode(status));
+    if (status == GATT_DATABASE_OUT_OF_SYNC) {
+      LOG_INFO("Database out of sync for %s", device->addr.ToString().c_str());
+      ClearDeviceInformationAndStartSearch(device);
+    }
   }
 
   void OnHasPresetNameGetStatus(uint16_t conn_id, tGATT_STATUS status,
@@ -367,9 +377,14 @@
     callbacks_->OnPresetInfoError(device->addr, op.index,
                                   GattStatus2SvcErrorCode(status));
 
-    LOG_ERROR("Devices %s: Control point not usable. Disconnecting!",
-              device->addr.ToString().c_str());
-    BTA_GATTC_Close(conn_id);
+    if (status == GATT_DATABASE_OUT_OF_SYNC) {
+      LOG_INFO("Database out of sync for %s", device->addr.ToString().c_str());
+      ClearDeviceInformationAndStartSearch(device);
+    } else {
+      LOG_ERROR("Devices %s: Control point not usable. Disconnecting!",
+                device->addr.ToString().c_str());
+      BTA_GATTC_Close(device->conn_id);
+    }
   }
 
   void OnHasPresetIndexOperation(uint16_t conn_id, tGATT_STATUS status,
@@ -409,9 +424,15 @@
       callbacks_->OnActivePresetSelectError(op.addr_or_group,
                                             GattStatus2SvcErrorCode(status));
     }
-    LOG_ERROR("Devices %s: Control point not usable. Disconnecting!",
-              device->addr.ToString().c_str());
-    BTA_GATTC_Close(conn_id);
+
+    if (status == GATT_DATABASE_OUT_OF_SYNC) {
+      LOG_INFO("Database out of sync for %s", device->addr.ToString().c_str());
+      ClearDeviceInformationAndStartSearch(device);
+    } else {
+      LOG_ERROR("Devices %s: Control point not usable. Disconnecting!",
+                device->addr.ToString().c_str());
+      BTA_GATTC_Close(device->conn_id);
+    }
   }
 
   void CpReadAllPresetsOperation(HasCtpOp operation) {
@@ -988,6 +1009,12 @@
       return;
     }
 
+    if (status == GATT_DATABASE_OUT_OF_SYNC) {
+      LOG_INFO("Database out of sync for %s", device->addr.ToString().c_str());
+      ClearDeviceInformationAndStartSearch(device);
+      return;
+    }
+
     HasGattOpContext context(user_data);
     bool enabling_ntf = context.context_flags &
                         HasGattOpContext::kContextFlagsEnableNotification;
@@ -1058,9 +1085,14 @@
     }
 
     if (status != GATT_SUCCESS) {
-      LOG(ERROR) << __func__ << ": Could not read characteristic at handle="
-                 << loghex(handle);
-      BTA_GATTC_Close(device->conn_id);
+      if (status == GATT_DATABASE_OUT_OF_SYNC) {
+        LOG_INFO("Database out of sync for %s",
+                 device->addr.ToString().c_str());
+        ClearDeviceInformationAndStartSearch(device);
+      } else {
+        LOG_ERROR("Could not read characteristic at handle=0x%04x", handle);
+        BTA_GATTC_Close(device->conn_id);
+      }
       return;
     }
 
@@ -1434,10 +1466,14 @@
     }
 
     if (status != GATT_SUCCESS) {
-      LOG(ERROR) << __func__ << ": Could not read characteristic at handle="
-                 << loghex(handle);
-      BTA_GATTC_Close(device->conn_id);
-      return;
+      if (status == GATT_DATABASE_OUT_OF_SYNC) {
+        LOG_INFO("Database out of sync for %s",
+                 device->addr.ToString().c_str());
+        ClearDeviceInformationAndStartSearch(device);
+      } else {
+        LOG_ERROR("Could not read characteristic at handle=0x%04x", handle);
+        BTA_GATTC_Close(device->conn_id);
+      }
     }
 
     if (len != 1) {
@@ -1507,11 +1543,7 @@
     }
   }
 
-  /* Cleans up after the device disconnection */
-  void DoDisconnectCleanUp(HasDevice& device,
-                           bool invalidate_gatt_service = true) {
-    DLOG(INFO) << __func__ << ": device=" << device.addr;
-
+  void DeregisterNotifications(HasDevice& device) {
     /* Deregister from optional features notifications */
     if (device.features_ccc_handle != GAP_INVALID_HANDLE) {
       BTA_GATTC_DeregisterForNotifications(gatt_if_, device.addr,
@@ -1529,6 +1561,14 @@
       BTA_GATTC_DeregisterForNotifications(gatt_if_, device.addr,
                                            device.cp_handle);
     }
+  }
+
+  /* Cleans up after the device disconnection */
+  void DoDisconnectCleanUp(HasDevice& device,
+                           bool invalidate_gatt_service = true) {
+    LOG_DEBUG(": device=%s", device.addr.ToString().c_str());
+
+    DeregisterNotifications(device);
 
     if (device.conn_id != GATT_INVALID_CONN_ID) {
       BtaGattQueue::Clean(device.conn_id);
@@ -1940,6 +1980,27 @@
     }
   }
 
+  void ClearDeviceInformationAndStartSearch(HasDevice* device) {
+    if (!device) {
+      LOG_ERROR("Device is null");
+      return;
+    }
+
+    LOG_INFO("%s", device->addr.ToString().c_str());
+
+    if (!device->isGattServiceValid()) {
+      LOG_INFO("Service already invalidated");
+      return;
+    }
+
+    /* Invalidate service discovery results */
+    DeregisterNotifications(*device);
+    BtaGattQueue::Clean(device->conn_id);
+    device->ClearSvcData();
+    btif_storage_remove_leaudio_has(device->addr);
+    BTA_GATTC_ServiceSearchRequest(device->conn_id, &kUuidHearingAccessService);
+  }
+
   void OnGattServiceChangeEvent(const RawAddress& address) {
     auto device = std::find_if(devices_.begin(), devices_.end(),
                                HasDevice::MatchAddress(address));
@@ -1947,12 +2008,8 @@
       LOG(WARNING) << "Skipping unknown device" << address;
       return;
     }
-
-    DLOG(INFO) << __func__ << ": address=" << address;
-
-    /* Invalidate service discovery results */
-    BtaGattQueue::Clean(device->conn_id);
-    device->ClearSvcData();
+    LOG_INFO("%s", address.ToString().c_str());
+    ClearDeviceInformationAndStartSearch(&(*device));
   }
 
   void OnGattServiceDiscoveryDoneEvent(const RawAddress& address) {
diff --git a/system/bta/has/has_client_test.cc b/system/bta/has/has_client_test.cc
index 520959e..ca8ca63 100644
--- a/system/bta/has/has_client_test.cc
+++ b/system/bta/has/has_client_test.cc
@@ -21,7 +21,6 @@
 #include <gmock/gmock.h>
 #include <gtest/gtest.h>
 #include <osi/include/alarm.h>
-#include <osi/test/alarm_mock.h>
 #include <sys/socket.h>
 
 #include <variant>
@@ -39,19 +38,10 @@
 #include "mock_controller.h"
 #include "mock_csis_client.h"
 
-static std::map<const char*, bool> fake_osi_bool_props;
-
-bool osi_property_get_bool(const char* key, bool default_value) {
-  if (fake_osi_bool_props.count(key)) return fake_osi_bool_props.at(key);
-
-  return default_value;
-}
-
-void osi_property_set_bool(const char* key, bool value) {
-  fake_osi_bool_props.insert_or_assign(key, value);
-}
-
 bool gatt_profile_get_eatt_support(const RawAddress& addr) { return true; }
+void osi_property_set_bool(const char* key, bool value);
+
+std::map<std::string, int> mock_function_count_map;
 
 namespace bluetooth {
 namespace has {
@@ -655,8 +645,7 @@
   }
 
   void SetUp(void) override {
-    fake_osi_bool_props.clear();
-
+    mock_function_count_map.clear();
     controller::SetMockControllerInterface(&controller_interface_);
     bluetooth::manager::SetMockBtmInterface(&btm_interface);
     bluetooth::storage::SetMockBtifStorageInterface(&btif_storage_interface_);
@@ -2937,9 +2926,52 @@
   ASSERT_TRUE(SimpleJsonValidator(sv[1], &dumpsys_byte_cnt));
 }
 
+TEST_F(HasClientTest, test_connect_database_out_of_sync) {
+  osi_property_set_bool("persist.bluetooth.has.always_use_preset_cache", false);
+
+  const RawAddress test_address = GetTestAddress(1);
+  std::set<HasPreset, HasPreset::ComparatorDesc> has_presets = {{
+      HasPreset(1, HasPreset::kPropertyAvailable, "Universal"),
+      HasPreset(2, HasPreset::kPropertyAvailable | HasPreset::kPropertyWritable,
+                "Preset2"),
+  }};
+  SetSampleDatabaseHasPresetsNtf(
+      test_address,
+      bluetooth::has::kFeatureBitHearingAidTypeBanded |
+          bluetooth::has::kFeatureBitWritablePresets |
+          bluetooth::has::kFeatureBitDynamicPresets,
+      has_presets);
+
+  EXPECT_CALL(*callbacks, OnDeviceAvailable(
+                              test_address,
+                              bluetooth::has::kFeatureBitHearingAidTypeBanded |
+                                  bluetooth::has::kFeatureBitWritablePresets |
+                                  bluetooth::has::kFeatureBitDynamicPresets));
+  EXPECT_CALL(*callbacks,
+              OnConnectionState(ConnectionState::CONNECTED, test_address));
+  TestConnect(test_address);
+
+  ON_CALL(gatt_queue, WriteCharacteristic(_, _, _, _, _, _))
+      .WillByDefault(
+          Invoke([this](uint16_t conn_id, uint16_t handle,
+                        std::vector<uint8_t> value, tGATT_WRITE_TYPE write_type,
+                        GATT_WRITE_OP_CB cb, void* cb_data) {
+            auto* svc = gatt::FindService(services_map[conn_id], handle);
+            if (svc == nullptr) return;
+
+            tGATT_STATUS status = GATT_DATABASE_OUT_OF_SYNC;
+            if (cb)
+              cb(conn_id, status, handle, value.size(), value.data(), cb_data);
+          }));
+
+  ON_CALL(gatt_interface, ServiceSearchRequest(_, _)).WillByDefault(Return());
+  EXPECT_CALL(gatt_interface, ServiceSearchRequest(_, _));
+  HasClient::Get()->GetPresetInfo(test_address, 1);
+}
+
 class HasTypesTest : public ::testing::Test {
  protected:
-  void SetUp(void) override { fake_osi_bool_props.clear(); }
+  void SetUp(void) override { mock_function_count_map.clear(); }
 
   void TearDown(void) override {}
 };  // namespace
@@ -3076,15 +3108,16 @@
   auto address1 = GetTestAddress(1);
   auto address2 = GetTestAddress(2);
 
-  EXPECT_CALL(*AlarmMock::Get(), AlarmNew(_)).Times(1);
   HasCtpGroupOpCoordinator wrapper(
       {address1, address2},
       HasCtpOp(0x01, ::le_audio::has::PresetCtpOpcode::READ_PRESETS, 6));
   ASSERT_EQ(2u, wrapper.ref_cnt);
 
-  EXPECT_CALL(*AlarmMock::Get(), AlarmFree(_)).Times(1);
   HasCtpGroupOpCoordinator::Cleanup();
   ASSERT_EQ(0u, wrapper.ref_cnt);
+
+  ASSERT_EQ(1, mock_function_count_map["alarm_free"]);
+  ASSERT_EQ(1, mock_function_count_map["alarm_new"]);
 }
 
 TEST_F(HasTypesTest, test_group_op_coordinator_copy) {
@@ -3095,7 +3128,6 @@
   auto address1 = GetTestAddress(1);
   auto address2 = GetTestAddress(2);
 
-  EXPECT_CALL(*AlarmMock::Get(), AlarmNew(_)).Times(1);
   HasCtpGroupOpCoordinator wrapper(
       {address1, address2},
       HasCtpOp(0x01, ::le_audio::has::PresetCtpOpcode::READ_PRESETS, 6));
@@ -3111,9 +3143,11 @@
   delete wrapper4;
   ASSERT_EQ(4u, wrapper.ref_cnt);
 
-  EXPECT_CALL(*AlarmMock::Get(), AlarmFree(_)).Times(1);
   HasCtpGroupOpCoordinator::Cleanup();
   ASSERT_EQ(0u, wrapper.ref_cnt);
+
+  ASSERT_EQ(1, mock_function_count_map["alarm_free"]);
+  ASSERT_EQ(1, mock_function_count_map["alarm_new"]);
 }
 
 TEST_F(HasTypesTest, test_group_op_coordinator_completion) {
@@ -3126,7 +3160,6 @@
   auto address2 = GetTestAddress(2);
   auto address3 = GetTestAddress(3);
 
-  EXPECT_CALL(*AlarmMock::Get(), AlarmNew(_)).Times(1);
   HasCtpGroupOpCoordinator wrapper(
       {address1, address3},
       HasCtpOp(0x01, ::le_audio::has::PresetCtpOpcode::READ_PRESETS, 6));
@@ -3135,7 +3168,6 @@
       HasCtpOp(0x01, ::le_audio::has::PresetCtpOpcode::READ_PRESETS, 6));
   ASSERT_EQ(3u, wrapper.ref_cnt);
 
-  EXPECT_CALL(*AlarmMock::Get(), AlarmFree(_)).Times(0);
   ASSERT_FALSE(wrapper.IsFullyCompleted());
 
   wrapper.SetCompleted(address1);
@@ -3144,23 +3176,20 @@
   wrapper.SetCompleted(address3);
   ASSERT_EQ(1u, wrapper.ref_cnt);
   ASSERT_FALSE(wrapper.IsFullyCompleted());
-  Mock::VerifyAndClearExpectations(&*AlarmMock::Get());
 
   /* Non existing address completion */
-  EXPECT_CALL(*AlarmMock::Get(), AlarmFree(_)).Times(0);
   wrapper.SetCompleted(address2);
-  Mock::VerifyAndClearExpectations(&*AlarmMock::Get());
   ASSERT_EQ(1u, wrapper.ref_cnt);
 
   /* Last device address completion */
-  EXPECT_CALL(*AlarmMock::Get(), AlarmFree(_)).Times(1);
   wrapper2.SetCompleted(address2);
-  Mock::VerifyAndClearExpectations(&*AlarmMock::Get());
   ASSERT_TRUE(wrapper.IsFullyCompleted());
   ASSERT_EQ(0u, wrapper.ref_cnt);
 
-  EXPECT_CALL(*AlarmMock::Get(), AlarmFree(_)).Times(0);
   HasCtpGroupOpCoordinator::Cleanup();
+
+  ASSERT_EQ(1, mock_function_count_map["alarm_free"]);
+  ASSERT_EQ(1, mock_function_count_map["alarm_new"]);
 }
 
 }  // namespace
diff --git a/system/bta/hd/bta_hd_api.cc b/system/bta/hd/bta_hd_api.cc
index a0d4802..93a9155 100644
--- a/system/bta/hd/bta_hd_api.cc
+++ b/system/bta/hd/bta_hd_api.cc
@@ -79,7 +79,10 @@
 void BTA_HdDisable(void) {
   APPL_TRACE_API("%s", __func__);
 
-  bta_sys_deregister(BTA_ID_HD);
+  if (!bluetooth::common::init_flags::
+          delay_hidh_cleanup_until_hidh_ready_start_is_enabled()) {
+    bta_sys_deregister(BTA_ID_HD);
+  }
 
   BT_HDR_RIGID* p_buf = (BT_HDR_RIGID*)osi_malloc(sizeof(BT_HDR_RIGID));
   p_buf->event = BTA_HD_API_DISABLE_EVT;
diff --git a/system/bta/hearing_aid/hearing_aid.cc b/system/bta/hearing_aid/hearing_aid.cc
index d79e33d..8637f89 100644
--- a/system/bta/hearing_aid/hearing_aid.cc
+++ b/system/bta/hearing_aid/hearing_aid.cc
@@ -182,7 +182,8 @@
     int read_rssi_start_interval_count = 0;
 
     for (auto& d : devices) {
-      VLOG(1) << __func__ << ": device=" << d.address << ", read_rssi_count=" << d.read_rssi_count;
+      LOG_DEBUG("device=%s, read_rssi_count=%d",
+                d.address.ToStringForLogging().c_str(), d.read_rssi_count);
 
       // Reset the count
       if (d.read_rssi_count <= 0) {
@@ -211,9 +212,8 @@
                                  uint16_t handle, uint16_t len,
                                  const uint8_t* value, void* data) {
   if (status != GATT_SUCCESS) {
-    LOG(ERROR) << __func__ << ": handle=" << handle << ", conn_id=" << conn_id
-               << ", status=" << loghex(static_cast<uint8_t>(status))
-               << ", length=" << len;
+    LOG_ERROR("handle= %hu, conn_id=%hu, status= %s, length=%u", handle,
+              conn_id, loghex(static_cast<uint8_t>(status)).c_str(), len);
   }
 }
 
@@ -222,7 +222,7 @@
 
 inline void encoder_state_init() {
   if (encoder_state_left != nullptr) {
-    LOG(WARNING) << __func__ << ": encoder already initialized";
+    LOG_WARN("encoder already initialized");
     return;
   }
   encoder_state_left = g722_encode_init(nullptr, 64000, G722_PACKED);
@@ -261,19 +261,16 @@
         "persist.bluetooth.hearingaid.interval", (int32_t)HA_INTERVAL_20_MS);
     if ((default_data_interval_ms != HA_INTERVAL_10_MS) &&
         (default_data_interval_ms != HA_INTERVAL_20_MS)) {
-      LOG(ERROR) << __func__
-                 << ": invalid interval=" << default_data_interval_ms
-                 << "ms. Overwriting back to default";
+      LOG_ERROR("invalid interval= %ums. Overwrriting back to default",
+                default_data_interval_ms);
       default_data_interval_ms = HA_INTERVAL_20_MS;
     }
-    VLOG(2) << __func__
-            << ", default_data_interval_ms=" << default_data_interval_ms;
+    LOG_DEBUG("default_data_interval_ms=%u", default_data_interval_ms);
 
     overwrite_min_ce_len = (uint16_t)osi_property_get_int32(
         "persist.bluetooth.hearingaidmincelen", 0);
     if (overwrite_min_ce_len) {
-      LOG(INFO) << __func__
-                << ": Overwrites MIN_CE_LEN=" << overwrite_min_ce_len;
+      LOG_INFO("Overwrites MIN_CE_LEN=%u", overwrite_min_ce_len);
     }
 
     BTA_GATTC_AppRegister(
@@ -281,14 +278,15 @@
         base::Bind(
             [](Closure initCb, uint8_t client_id, uint8_t status) {
               if (status != GATT_SUCCESS) {
-                LOG(ERROR) << "Can't start Hearing Aid profile - no gatt "
-                              "clients left!";
+                LOG_ERROR(
+                    "Can't start Hearing Aid profile - no gatt clients left!");
                 return;
               }
               instance->gatt_if = client_id;
               initCb.Run();
             },
-            initCb), false);
+            initCb),
+        false);
   }
 
   uint16_t UpdateBleConnParams(const RawAddress& address) {
@@ -306,15 +304,15 @@
         connection_interval = CONNECTION_INTERVAL_20MS_PARAM;
         break;
       default:
-        LOG(ERROR) << __func__ << ":Error: invalid default_data_interval_ms="
-                   << default_data_interval_ms;
+        LOG_ERROR("invalid default_data_interval_ms=%u",
+                  default_data_interval_ms);
         min_ce_len = MIN_CE_LEN_10MS_CI;
         connection_interval = CONNECTION_INTERVAL_10MS_PARAM;
     }
 
     if (overwrite_min_ce_len != 0) {
-      VLOG(2) << __func__ << ": min_ce_len=" << min_ce_len
-              << " is overwritten to " << overwrite_min_ce_len;
+      LOG_DEBUG("min_ce_len=%u is overwritten to %u", min_ce_len,
+                overwrite_min_ce_len);
       min_ce_len = overwrite_min_ce_len;
     }
 
@@ -323,22 +321,22 @@
     return connection_interval;
   }
 
-  void Connect(const RawAddress& address) override {
-    DVLOG(2) << __func__ << " " << address;
+  void Connect(const RawAddress& address) {
+    LOG_DEBUG("%s", address.ToStringForLogging().c_str());
     hearingDevices.Add(HearingDevice(address, true));
     BTA_GATTC_Open(gatt_if, address, BTM_BLE_DIRECT_CONNECTION, false);
   }
 
-  void AddToAcceptlist(const RawAddress& address) override {
-    VLOG(2) << __func__ << " address: " << address;
+  void AddToAcceptlist(const RawAddress& address) {
+    LOG_DEBUG("%s", address.ToStringForLogging().c_str());
     hearingDevices.Add(HearingDevice(address, true));
     BTA_GATTC_Open(gatt_if, address, BTM_BLE_BKG_CONNECT_ALLOW_LIST, false);
   }
 
   void AddFromStorage(const HearingDevice& dev_info, uint16_t is_acceptlisted) {
-    DVLOG(2) << __func__ << " " << dev_info.address
-             << ", hiSyncId=" << loghex(dev_info.hi_sync_id)
-             << ", isAcceptlisted=" << is_acceptlisted;
+    LOG_DEBUG("%s, hiSyncId=%s, isAcceptlisted=%u",
+              dev_info.address.ToStringForLogging().c_str(),
+              loghex(dev_info.hi_sync_id).c_str(), is_acceptlisted);
     if (is_acceptlisted) {
       hearingDevices.Add(dev_info);
 
@@ -364,13 +362,14 @@
     if (!hearingDevice) {
       /* When Hearing Aid is quickly disabled and enabled in settings, this case
        * might happen */
-      LOG(WARNING) << "Closing connection to non hearing-aid device, address="
-                   << address;
+      LOG_WARN("Closing connection to non hearing-aid device, address=%s",
+               address.ToStringForLogging().c_str());
       BTA_GATTC_Close(conn_id);
       return;
     }
 
-    LOG(INFO) << __func__ << ": address=" << address << ", conn_id=" << conn_id;
+    LOG_INFO("address=%s, conn_id=%u", address.ToStringForLogging().c_str(),
+             conn_id);
 
     if (status != GATT_SUCCESS) {
       if (!hearingDevice->connecting_actively) {
@@ -378,7 +377,7 @@
         return;
       }
 
-      LOG(INFO) << "Failed to connect to Hearing Aid device";
+      LOG_INFO("Failed to connect to Hearing Aid device");
       hearingDevices.Remove(address);
       callbacks->OnConnectionState(ConnectionState::DISCONNECTED, address);
       return;
@@ -404,7 +403,7 @@
     }
 
     if (controller_get_interface()->supports_ble_2m_phy()) {
-      LOG(INFO) << address << " set preferred 2M PHY";
+      LOG_INFO("%s set preferred 2M PHY", address.ToStringForLogging().c_str());
       BTM_BleSetPhy(address, PHY_LE_2M, PHY_LE_2M, 0);
     }
 
@@ -439,7 +438,7 @@
   void OnConnectionUpdateComplete(uint16_t conn_id, tBTA_GATTC* p_data) {
     HearingDevice* hearingDevice = hearingDevices.FindByConnId(conn_id);
     if (!hearingDevice) {
-      DVLOG(2) << "Skipping unknown device, conn_id=" << loghex(conn_id);
+      LOG_DEBUG("Skipping unknown device, conn_id=%s", loghex(conn_id).c_str());
       return;
     }
 
@@ -452,31 +451,29 @@
         switch (hearingDevice->connection_update_status) {
           case COMPLETED:
             if (!same_conn_interval) {
-              LOG(WARNING) << __func__
-                           << ": Unexpected change. Redo. connection interval="
-                           << p_data->conn_update.interval << ", expected="
-                           << hearingDevice->requested_connection_interval
-                           << ", conn_id=" << conn_id
-                           << ", connection_update_status="
-                           << hearingDevice->connection_update_status;
+              LOG_WARN(
+                  "Unexpected change. Redo. connection interval=%u, "
+                  "expected=%u, conn_id=%u, connection_update_status=%u",
+                  p_data->conn_update.interval,
+                  hearingDevice->requested_connection_interval, conn_id,
+                  hearingDevice->connection_update_status);
               // Redo this connection interval change.
               hearingDevice->connection_update_status = AWAITING;
             }
             break;
           case STARTED:
             if (same_conn_interval) {
-              LOG(INFO) << __func__
-                        << ": Connection update completed. conn_id=" << conn_id
-                        << ", device=" << hearingDevice->address;
+              LOG_INFO("Connection update completed. conn_id=%u, device=%s",
+                       conn_id,
+                       hearingDevice->address.ToStringForLogging().c_str());
               hearingDevice->connection_update_status = COMPLETED;
             } else {
-              LOG(WARNING) << __func__
-                           << ": Ignored. Different connection interval="
-                           << p_data->conn_update.interval << ", expected="
-                           << hearingDevice->requested_connection_interval
-                           << ", conn_id=" << conn_id
-                           << ", connection_update_status="
-                           << hearingDevice->connection_update_status;
+              LOG_WARN(
+                  "Ignored. Different connection interval=%u, expected=%u, "
+                  "conn_id=%u, connection_update_status=%u",
+                  p_data->conn_update.interval,
+                  hearingDevice->requested_connection_interval, conn_id,
+                  hearingDevice->connection_update_status);
               // Wait for the right Connection Update Completion.
               return;
             }
@@ -494,16 +491,15 @@
         send_state_change_to_other_side(hearingDevice, conn_update);
         send_state_change(hearingDevice, conn_update);
       } else {
-        LOG(INFO) << __func__ << ": error status="
-                  << loghex(static_cast<uint8_t>(p_data->conn_update.status))
-                  << ", conn_id=" << conn_id
-                  << ", device=" << hearingDevice->address
-                  << ", connection_update_status="
-                  << hearingDevice->connection_update_status;
-
+        LOG_INFO(
+            "error status=%s, conn_id=%u,device=%s, "
+            "connection_update_status=%u",
+            loghex(static_cast<uint8_t>(p_data->conn_update.status)).c_str(),
+            conn_id, hearingDevice->address.ToStringForLogging().c_str(),
+            hearingDevice->connection_update_status);
         if (hearingDevice->connection_update_status == STARTED) {
           // Redo this connection interval change.
-          LOG(ERROR) << __func__ << ": Redo Connection Interval change";
+          LOG_ERROR("Redo Connection Interval change");
           hearingDevice->connection_update_status = AWAITING;
         }
       }
@@ -531,15 +527,18 @@
   void OnReadRssiComplete(const RawAddress& address, int8_t rssi_value) {
     HearingDevice* hearingDevice = hearingDevices.FindByAddress(address);
     if (!hearingDevice) {
-      LOG(INFO) << "Skipping unknown device" << address;
+      LOG_INFO("Skipping unknown device %s",
+               address.ToStringForLogging().c_str());
       return;
     }
 
-    VLOG(1) << __func__ << ": device=" << address << ", rssi=" << (int)rssi_value;
+    LOG_DEBUG("device=%s, rss=%d", address.ToStringForLogging().c_str(),
+              (int)rssi_value);
 
     if (hearingDevice->read_rssi_count <= 0) {
-      LOG(ERROR) << __func__ << ": device=" << address
-                 << ", invalid read_rssi_count=" << hearingDevice->read_rssi_count;
+      LOG_ERROR(" device=%s, invalid read_rssi_count=%d",
+                address.ToStringForLogging().c_str(),
+                hearingDevice->read_rssi_count);
       return;
     }
 
@@ -548,7 +547,8 @@
     if (hearingDevice->read_rssi_count == READ_RSSI_NUM_TRIES) {
       // Store the timestamp only for the first one after packet flush
       clock_gettime(CLOCK_REALTIME, &last_log_set.timestamp);
-      LOG(INFO) << __func__ << ": store time. device=" << address << ", rssi=" << (int)rssi_value;
+      LOG_INFO("store time, device=%s, rssi=%d",
+               address.ToStringForLogging().c_str(), (int)rssi_value);
     }
 
     last_log_set.rssi.emplace_back(rssi_value);
@@ -558,12 +558,13 @@
   void OnEncryptionComplete(const RawAddress& address, bool success) {
     HearingDevice* hearingDevice = hearingDevices.FindByAddress(address);
     if (!hearingDevice) {
-      DVLOG(2) << "Skipping unknown device" << address;
+      LOG_DEBUG("Skipping unknown device %s",
+                address.ToStringForLogging().c_str());
       return;
     }
 
     if (!success) {
-      LOG(ERROR) << "encryption failed";
+      LOG_ERROR("encryption failed");
       BTA_GATTC_Close(hearingDevice->conn_id);
       if (hearingDevice->first_connection) {
         callbacks->OnConnectionState(ConnectionState::DISCONNECTED, address);
@@ -571,7 +572,7 @@
       return;
     }
 
-    LOG(INFO) << __func__ << ": " << address;
+    LOG_INFO("%s", address.ToStringForLogging().c_str());
 
     if (hearingDevice->audio_control_point_handle &&
         hearingDevice->audio_status_handle &&
@@ -580,8 +581,8 @@
       // Use cached data, jump to read PSM
       ReadPSM(hearingDevice);
     } else {
-      LOG(INFO) << __func__ << ": " << address
-                << ": do BTA_GATTC_ServiceSearchRequest";
+      LOG_INFO("%s: do BTA_GATTC_ServiceSearchRequest",
+               address.ToStringForLogging().c_str());
       hearingDevice->first_connection = true;
       BTA_GATTC_ServiceSearchRequest(hearingDevice->conn_id, &HEARING_AID_UUID);
     }
@@ -592,32 +593,34 @@
                         tGATT_STATUS status) {
     HearingDevice* hearingDevice = hearingDevices.FindByConnId(conn_id);
     if (!hearingDevice) {
-      DVLOG(2) << "Skipping unknown device, conn_id=" << loghex(conn_id);
+      LOG_DEBUG("Skipping unknown device, conn_id=%s", loghex(conn_id).c_str());
       return;
     }
     if (status != GATT_SUCCESS) {
-      LOG(WARNING) << hearingDevice->address
-                   << " phy update fail with status: " << status;
+      LOG_WARN("%s phy update fail with status: %hu",
+               hearingDevice->address.ToStringForLogging().c_str(), status);
       return;
     }
     if (tx_phys == PHY_LE_2M && rx_phys == PHY_LE_2M) {
-      LOG(INFO) << hearingDevice->address << " phy update to 2M successful";
+      LOG_INFO("%s phy update to 2M successful",
+               hearingDevice->address.ToStringForLogging().c_str());
       return;
     }
-    LOG(INFO)
-        << hearingDevice->address
-        << " phy update successful but not target phy, try again. tx_phys: "
-        << tx_phys << ", rx_phys: " << rx_phys;
+    LOG_INFO(
+        "%s phy update successful but not target phy, try again. tx_phys: "
+        "%u,rx_phys: %u",
+        hearingDevice->address.ToStringForLogging().c_str(), tx_phys, rx_phys);
     BTM_BleSetPhy(hearingDevice->address, PHY_LE_2M, PHY_LE_2M, 0);
   }
 
   void OnServiceChangeEvent(const RawAddress& address) {
     HearingDevice* hearingDevice = hearingDevices.FindByAddress(address);
     if (!hearingDevice) {
-      VLOG(2) << "Skipping unknown device" << address;
+      LOG_DEBUG("Skipping unknown device %s",
+                address.ToStringForLogging().c_str());
       return;
     }
-    LOG(INFO) << __func__ << ": address=" << address;
+    LOG_INFO("address=%s", address.ToStringForLogging().c_str());
     hearingDevice->first_connection = true;
     hearingDevice->service_changed_rcvd = true;
     BtaGattQueue::Clean(hearingDevice->conn_id);
@@ -630,17 +633,18 @@
   void OnServiceDiscDoneEvent(const RawAddress& address) {
     HearingDevice* hearingDevice = hearingDevices.FindByAddress(address);
     if (!hearingDevice) {
-      VLOG(2) << "Skipping unknown device" << address;
+      LOG_DEBUG("Skipping unknown device %s",
+                address.ToStringForLogging().c_str());
       return;
     }
-    LOG(INFO) << __func__ << ": " << address;
+    LOG_INFO("%s", address.ToStringForLogging().c_str());
     if (hearingDevice->service_changed_rcvd ||
         !(hearingDevice->audio_control_point_handle &&
           hearingDevice->audio_status_handle &&
           hearingDevice->audio_status_ccc_handle &&
           hearingDevice->volume_handle && hearingDevice->read_psm_handle)) {
-      LOG(INFO) << __func__ << ": " << address
-                << ": do BTA_GATTC_ServiceSearchRequest";
+      LOG_INFO("%s: do BTA_GATTC_ServiceSearchRequest",
+               address.ToStringForLogging().c_str());
       BTA_GATTC_ServiceSearchRequest(hearingDevice->conn_id, &HEARING_AID_UUID);
     }
   }
@@ -648,7 +652,7 @@
   void OnServiceSearchComplete(uint16_t conn_id, tGATT_STATUS status) {
     HearingDevice* hearingDevice = hearingDevices.FindByConnId(conn_id);
     if (!hearingDevice) {
-      DVLOG(2) << "Skipping unknown device, conn_id=" << loghex(conn_id);
+      LOG_DEBUG("Skipping unknown device, conn_id=%s", loghex(conn_id).c_str());
       return;
     }
 
@@ -657,7 +661,7 @@
 
     if (status != GATT_SUCCESS) {
       /* close connection and report service discovery complete with error */
-      LOG(ERROR) << "Service discovery failed";
+      LOG_ERROR("Service discovery failed");
       if (hearingDevice->first_connection) {
         callbacks->OnConnectionState(ConnectionState::DISCONNECTED,
                                      hearingDevice->address);
@@ -670,18 +674,19 @@
     const gatt::Service* service = nullptr;
     for (const gatt::Service& tmp : *services) {
       if (tmp.uuid == Uuid::From16Bit(UUID_SERVCLASS_GATT_SERVER)) {
-        LOG(INFO) << "Found UUID_SERVCLASS_GATT_SERVER, handle="
-                  << loghex(tmp.handle);
+        LOG_INFO("Found UUID_SERVCLASS_GATT_SERVER, handle=%s",
+                 loghex(tmp.handle).c_str());
         const gatt::Service* service_changed_service = &tmp;
         find_server_changed_ccc_handle(conn_id, service_changed_service);
       } else if (tmp.uuid == HEARING_AID_UUID) {
-        LOG(INFO) << "Found Hearing Aid service, handle=" << loghex(tmp.handle);
+        LOG_INFO("Found Hearing Aid service, handle=%s",
+                 loghex(tmp.handle).c_str());
         service = &tmp;
       }
     }
 
     if (!service) {
-      LOG(ERROR) << "No Hearing Aid service found";
+      LOG_ERROR("No Hearing Aid service found");
       callbacks->OnConnectionState(ConnectionState::DISCONNECTED,
                                    hearingDevice->address);
       return;
@@ -693,8 +698,8 @@
                 hearingDevice->address, &hearingDevice->capabilities,
                 &hearingDevice->hi_sync_id, &hearingDevice->render_delay,
                 &hearingDevice->preparation_delay, &hearingDevice->codecs)) {
-          VLOG(2) << "Reading read only properties "
-                  << loghex(charac.value_handle);
+          LOG_DEBUG("Reading read only properties %s",
+                    loghex(charac.value_handle).c_str());
           BtaGattQueue::ReadCharacteristic(
               conn_id, charac.value_handle,
               HearingAidImpl::OnReadOnlyPropertiesReadStatic, nullptr);
@@ -708,19 +713,20 @@
         hearingDevice->audio_status_ccc_handle =
             find_ccc_handle(conn_id, charac.value_handle);
         if (!hearingDevice->audio_status_ccc_handle) {
-          LOG(ERROR) << __func__ << ": cannot find Audio Status CCC descriptor";
+          LOG_ERROR("cannot find Audio Status CCC descriptor");
           continue;
         }
 
-        LOG(INFO) << __func__
-                  << ": audio_status_handle=" << loghex(charac.value_handle)
-                  << ", ccc=" << loghex(hearingDevice->audio_status_ccc_handle);
+        LOG_INFO("audio_status_handle=%s, ccc=%s",
+                 loghex(charac.value_handle).c_str(),
+                 loghex(hearingDevice->audio_status_ccc_handle).c_str());
       } else if (charac.uuid == VOLUME_UUID) {
         hearingDevice->volume_handle = charac.value_handle;
       } else if (charac.uuid == LE_PSM_UUID) {
         hearingDevice->read_psm_handle = charac.value_handle;
       } else {
-        LOG(WARNING) << "Unknown characteristic found:" << charac.uuid;
+        LOG_WARN("Unknown characteristic found:%s",
+                 charac.uuid.ToString().c_str());
       }
     }
 
@@ -733,8 +739,9 @@
 
   void ReadPSM(HearingDevice* hearingDevice) {
     if (hearingDevice->read_psm_handle) {
-      LOG(INFO) << "Reading PSM " << loghex(hearingDevice->read_psm_handle)
-                << ", device=" << hearingDevice->address;
+      LOG_INFO("Reading PSM %s, device=%s",
+               loghex(hearingDevice->read_psm_handle).c_str(),
+               hearingDevice->address.ToStringForLogging().c_str());
       BtaGattQueue::ReadCharacteristic(
           hearingDevice->conn_id, hearingDevice->read_psm_handle,
           HearingAidImpl::OnPsmReadStatic, nullptr);
@@ -745,33 +752,29 @@
                            uint8_t* value) {
     HearingDevice* device = hearingDevices.FindByConnId(conn_id);
     if (!device) {
-      LOG(INFO) << __func__
-                << ": Skipping unknown device, conn_id=" << loghex(conn_id);
+      LOG_INFO("Skipping unknown device, conn_id=%s", loghex(conn_id).c_str());
       return;
     }
 
     if (device->audio_status_handle != handle) {
-      LOG(INFO) << __func__ << ": Mismatched handle, "
-                << loghex(device->audio_status_handle)
-                << "!=" << loghex(handle);
+      LOG_INFO("Mismatched handle, %s!=%s",
+               loghex(device->audio_status_handle).c_str(),
+               loghex(handle).c_str());
       return;
     }
 
     if (len < 1) {
-      LOG(ERROR) << __func__ << ": Data Length too small, len=" << len
-                 << ", expecting at least 1";
+      LOG_ERROR("Data Length too small, len=%u, expecting at least 1", len);
       return;
     }
 
     if (value[0] != 0) {
-      LOG(INFO) << __func__
-                << ": Invalid returned status. data=" << loghex(value[0]);
+      LOG_INFO("Invalid returned status. data=%s", loghex(value[0]).c_str());
       return;
     }
 
-    LOG(INFO) << __func__
-              << ": audio status success notification. command_acked="
-              << device->command_acked;
+    LOG_INFO("audio status success notification. command_acked=%u",
+             device->command_acked);
     device->command_acked = true;
   }
 
@@ -780,11 +783,11 @@
                                 void* data) {
     HearingDevice* hearingDevice = hearingDevices.FindByConnId(conn_id);
     if (!hearingDevice) {
-      DVLOG(2) << __func__ << "unknown conn_id=" << loghex(conn_id);
+      LOG_DEBUG("unknown conn_id=%s", loghex(conn_id).c_str());
       return;
     }
 
-    VLOG(2) << __func__ << " " << base::HexEncode(value, len);
+    LOG_DEBUG("%s", base::HexEncode(value, len).c_str());
 
     uint8_t* p = value;
 
@@ -792,13 +795,13 @@
     STREAM_TO_UINT8(version, p);
 
     if (version != 0x01) {
-      LOG(WARNING) << "Unknown version: " << loghex(version);
+      LOG_WARN("Unknown version: %s", loghex(version).c_str());
       return;
     }
 
     // version 0x01 of read only properties:
     if (len < 17) {
-      LOG(WARNING) << "Read only properties too short: " << loghex(len);
+      LOG_WARN("Read only properties too short: %s", loghex(len).c_str());
       return;
     }
     uint8_t capabilities;
@@ -806,35 +809,34 @@
     hearingDevice->capabilities = capabilities;
     bool side = capabilities & CAPABILITY_SIDE;
     bool standalone = capabilities & CAPABILITY_BINAURAL;
-    VLOG(2) << __func__ << " capabilities: " << (side ? "right" : "left")
-            << ", " << (standalone ? "binaural" : "monaural");
+    LOG_DEBUG("capabilities: %s, %s", (side ? "right" : "left"),
+              (standalone ? "binaural" : "monaural"));
 
     if (capabilities & CAPABILITY_RESERVED) {
-      LOG(WARNING) << __func__ << " reserved capabilities are set";
+      LOG_WARN("reserved capabilities are set");
     }
 
     STREAM_TO_UINT64(hearingDevice->hi_sync_id, p);
-    VLOG(2) << __func__ << " hiSyncId: " << loghex(hearingDevice->hi_sync_id);
+    LOG_DEBUG("hiSyncId: %s", loghex(hearingDevice->hi_sync_id).c_str());
     uint8_t feature_map;
     STREAM_TO_UINT8(feature_map, p);
 
     STREAM_TO_UINT16(hearingDevice->render_delay, p);
-    VLOG(2) << __func__
-            << " render delay: " << loghex(hearingDevice->render_delay);
+    LOG_DEBUG("render delay: %s", loghex(hearingDevice->render_delay).c_str());
 
     STREAM_TO_UINT16(hearingDevice->preparation_delay, p);
-    VLOG(2) << __func__ << " preparation delay: "
-            << loghex(hearingDevice->preparation_delay);
+    LOG_DEBUG("preparation delay: %s",
+              loghex(hearingDevice->preparation_delay).c_str());
 
     uint16_t codecs;
     STREAM_TO_UINT16(codecs, p);
     hearingDevice->codecs = codecs;
-    VLOG(2) << __func__ << " supported codecs: " << loghex(codecs);
-    if (codecs & (1 << CODEC_G722_16KHZ)) VLOG(2) << "\tG722@16kHz";
-    if (codecs & (1 << CODEC_G722_24KHZ)) VLOG(2) << "\tG722@24kHz";
+    LOG_DEBUG("supported codecs: %s", loghex(codecs).c_str());
+    if (codecs & (1 << CODEC_G722_16KHZ)) LOG_INFO("%s\tG722@16kHz", __func__);
+    if (codecs & (1 << CODEC_G722_24KHZ)) LOG_INFO("%s\tG722@24kHz", __func__);
 
     if (!(codecs & (1 << CODEC_G722_16KHZ))) {
-      LOG(WARNING) << __func__ << " Mandatory codec, G722@16kHz not supported";
+      LOG_WARN("Mandatory codec, G722@16kHz not supported");
     }
   }
 
@@ -883,29 +885,31 @@
 
   void OnAudioStatus(uint16_t conn_id, tGATT_STATUS status, uint16_t handle,
                      uint16_t len, uint8_t* value, void* data) {
-    LOG(INFO) << __func__ << " " << base::HexEncode(value, len);
+    LOG_INFO("%s", base::HexEncode(value, len).c_str());
   }
 
   void OnPsmRead(uint16_t conn_id, tGATT_STATUS status, uint16_t handle,
                  uint16_t len, uint8_t* value, void* data) {
     HearingDevice* hearingDevice = hearingDevices.FindByConnId(conn_id);
     if (!hearingDevice) {
-      DVLOG(2) << "Skipping unknown read event, conn_id=" << loghex(conn_id);
+      LOG_DEBUG("Skipping unknown read event, conn_id=%s",
+                loghex(conn_id).c_str());
       return;
     }
 
     if (status != GATT_SUCCESS) {
-      LOG(ERROR) << "Error reading PSM for device" << hearingDevice->address;
+      LOG_ERROR("Error reading PSM for device %s",
+                hearingDevice->address.ToStringForLogging().c_str());
       return;
     }
 
     if (len > 2) {
-      LOG(ERROR) << "Bad PSM length";
+      LOG_ERROR("Bad PSM Lengh");
       return;
     }
 
     uint16_t psm = *((uint16_t*)value);
-    VLOG(2) << "read psm:" << loghex(psm);
+    LOG_DEBUG("read psm:%s", loghex(psm).c_str());
 
     if (hearingDevice->gap_handle == GAP_INVALID_HANDLE &&
         BTM_IsEncrypted(hearingDevice->address, BT_TRANSPORT_LE)) {
@@ -926,12 +930,12 @@
         &cfg_info, nullptr, BTM_SEC_NONE /* TODO: request security ? */,
         HearingAidImpl::GapCallbackStatic, BT_TRANSPORT_LE);
     if (gap_handle == GAP_INVALID_HANDLE) {
-      LOG(ERROR) << "UNABLE TO GET gap_handle";
+      LOG_ERROR("UNABLE TO GET gap_handle");
       return;
     }
 
     hearingDevice->gap_handle = gap_handle;
-    LOG(INFO) << "Successfully sent GAP connect request";
+    LOG_INFO("Successfully sent GAP connect request");
   }
 
   static void OnReadOnlyPropertiesReadStatic(uint16_t conn_id,
@@ -960,7 +964,8 @@
   void OnDeviceReady(const RawAddress& address) {
     HearingDevice* hearingDevice = hearingDevices.FindByAddress(address);
     if (!hearingDevice) {
-      LOG(INFO) << "Device not connected to profile" << address;
+      LOG_INFO("Device not connected to profile %s",
+               address.ToStringForLogging().c_str());
       return;
     }
 
@@ -970,19 +975,17 @@
       hearingDevice->first_connection = false;
     }
 
-    LOG(INFO) << __func__ << ": audio_status_handle="
-              << loghex(hearingDevice->audio_status_handle)
-              << ", audio_status_ccc_handle="
-              << loghex(hearingDevice->audio_status_ccc_handle);
+    LOG_INFO("audio_status_handle=%s, audio_status_ccc_handle=%s",
+             loghex(hearingDevice->audio_status_handle).c_str(),
+             loghex(hearingDevice->audio_status_ccc_handle).c_str());
 
     /* Register and enable the Audio Status Notification */
     tGATT_STATUS register_status;
     register_status = BTA_GATTC_RegisterForNotifications(
         gatt_if, address, hearingDevice->audio_status_handle);
     if (register_status != GATT_SUCCESS) {
-      LOG(ERROR) << __func__
-                 << ": BTA_GATTC_RegisterForNotifications failed, status="
-                 << loghex(static_cast<uint8_t>(register_status));
+      LOG_ERROR("BTA_GATTC_RegisterForNotifications failed, status=%s",
+                loghex(static_cast<uint8_t>(register_status)).c_str());
       return;
     }
     std::vector<uint8_t> value(2);
@@ -1005,10 +1008,10 @@
 
     hearingDevice->connecting_actively = false;
     hearingDevice->accepting_audio = true;
-    LOG(INFO) << __func__ << ": address=" << address
-              << ", hi_sync_id=" << loghex(hearingDevice->hi_sync_id)
-              << ", codec_in_use=" << loghex(codec_in_use)
-              << ", audio_running=" << audio_running;
+    LOG_INFO("address=%s, hi_sync_id=%s, codec_in_use=%s, audio_running=%i",
+             address.ToStringForLogging().c_str(),
+             loghex(hearingDevice->hi_sync_id).c_str(),
+             loghex(codec_in_use).c_str(), audio_running);
 
     StartSendingAudio(*hearingDevice);
 
@@ -1018,7 +1021,7 @@
   }
 
   void StartSendingAudio(const HearingDevice& hearingDevice) {
-    VLOG(0) << __func__ << ": device=" << hearingDevice.address;
+    LOG_DEBUG("device=%s", hearingDevice.address.ToStringForLogging().c_str());
 
     if (encoder_state_left == nullptr) {
       encoder_state_init();
@@ -1048,9 +1051,9 @@
     CHECK(stop_audio_ticks) << "stop_audio_ticks is empty";
 
     if (!audio_running) {
-      LOG(WARNING) << __func__ << ": Unexpected audio suspend";
+      LOG_WARN("Unexpected audio suspend");
     } else {
-      LOG(INFO) << __func__ << ": audio_running=" << audio_running;
+      LOG_INFO("audio_running=%i", audio_running);
     }
     audio_running = false;
     stop_audio_ticks();
@@ -1060,11 +1063,11 @@
       if (!device.accepting_audio) continue;
 
       if (!device.playback_started) {
-        LOG(WARNING) << __func__
-                     << ": Playback not started, skip send Stop cmd, device="
-                     << device.address;
+        LOG_WARN("Playback not started, skip send Stop cmd, device=%s",
+                 device.address.ToStringForLogging().c_str());
       } else {
-        LOG(INFO) << __func__ << ": send Stop cmd, device=" << device.address;
+        LOG_INFO("send Stop cmd, device=%s",
+                 device.address.ToStringForLogging().c_str());
         device.playback_started = false;
         device.command_acked = false;
         BtaGattQueue::WriteCharacteristic(device.conn_id,
@@ -1078,9 +1081,9 @@
     CHECK(start_audio_ticks) << "start_audio_ticks is empty";
 
     if (audio_running) {
-      LOG(ERROR) << __func__ << ": Unexpected Audio Resume";
+      LOG_ERROR("Unexpected Audio Resume");
     } else {
-      LOG(INFO) << __func__ << ": audio_running=" << audio_running;
+      LOG_INFO("audio_running=%i", audio_running);
     }
 
     for (auto& device : hearingDevices.devices) {
@@ -1090,8 +1093,7 @@
     }
 
     if (!audio_running) {
-      LOG(INFO) << __func__ << ": No device (0/" << GetDeviceCount()
-                << ") ready to start";
+      LOG_INFO("No device (0/%d) ready to start", GetDeviceCount());
       return;
     }
 
@@ -1119,8 +1121,8 @@
   }
 
   void SendEnableServiceChangedInd(HearingDevice* device) {
-    VLOG(2) << __func__ << " Enable " << device->address
-            << "service changed ind.";
+    LOG_DEBUG("Enable service changed ind.%s",
+              device->address.ToStringForLogging().c_str());
     std::vector<uint8_t> value(2);
     uint8_t* ptr = value.data();
     UINT16_TO_STREAM(ptr, GATT_CHAR_CLIENT_CONFIG_INDICTION);
@@ -1136,13 +1138,11 @@
 
     if (!audio_running) {
       if (!device->playback_started) {
-        LOG(INFO) << __func__
-                  << ": Skip Send Start since audio is not running, device="
-                  << device->address;
+        LOG_INFO("Skip Send Start since audio is not running, device=%s",
+                 device->address.ToStringForLogging().c_str());
       } else {
-        LOG(ERROR) << __func__
-                   << ": Audio not running but Playback has started, device="
-                   << device->address;
+        LOG_ERROR("Audio not running but Playback has started, device=%s",
+                  device->address.ToStringForLogging().c_str());
       }
       return;
     }
@@ -1150,15 +1150,16 @@
     if (current_volume == VOLUME_UNKNOWN) start[3] = (uint8_t)VOLUME_MIN;
 
     if (device->playback_started) {
-      LOG(ERROR) << __func__
-                 << ": Playback already started, skip send Start cmd, device="
-                 << device->address;
+      LOG_ERROR("Playback already started, skip send Start cmd, device=%s",
+                device->address.ToStringForLogging().c_str());
     } else {
       start[4] = GetOtherSideStreamStatus(device);
-      LOG(INFO) << __func__ << ": send Start cmd, volume=" << loghex(start[3])
-                << ", audio type=" << loghex(start[2])
-                << ", device=" << device->address
-                << ", other side streaming=" << loghex(start[4]);
+      LOG_INFO(
+          "send Start cmd, volume=%s, audio type=%s, device=%s, other side "
+          "streaming=%s",
+          loghex(start[3]).c_str(), loghex(start[2]).c_str(),
+          device->address.ToStringForLogging().c_str(),
+          loghex(start[4]).c_str());
       device->command_acked = false;
       BtaGattQueue::WriteCharacteristic(
           device->conn_id, device->audio_control_point_handle, start,
@@ -1171,12 +1172,12 @@
                                            uint16_t len, const uint8_t* value,
                                            void* data) {
     if (status != GATT_SUCCESS) {
-      LOG(ERROR) << __func__ << ": handle=" << handle << ", conn_id=" << conn_id
-                 << ", status=" << loghex(static_cast<uint8_t>(status));
+      LOG_ERROR("handle=%u, conn_id=%u, status=%s", handle, conn_id,
+                loghex(static_cast<uint8_t>(status)).c_str());
       return;
     }
     if (!instance) {
-      LOG(ERROR) << __func__ << ": instance is null.";
+      LOG_ERROR("instance is null");
       return;
     }
     instance->StartAudioCtrlCallback(conn_id);
@@ -1185,10 +1186,10 @@
   void StartAudioCtrlCallback(uint16_t conn_id) {
     HearingDevice* hearingDevice = hearingDevices.FindByConnId(conn_id);
     if (!hearingDevice) {
-      LOG(ERROR) << "Skipping unknown device, conn_id=" << loghex(conn_id);
+      LOG_ERROR("Skipping unknown device, conn_id=%s", loghex(conn_id).c_str());
       return;
     }
-    LOG(INFO) << __func__ << ": device: " << hearingDevice->address;
+    LOG_INFO("device: %s", hearingDevice->address.ToStringForLogging().c_str());
     hearingDevice->playback_started = true;
   }
 
@@ -1203,7 +1204,7 @@
   bool NeedToDropPacket(HearingDevice* target_side, HearingDevice* other_side) {
     // Just drop packet if the other side does not exist.
     if (!other_side) {
-      VLOG(2) << __func__ << ": other side not connected to profile";
+      LOG_DEBUG("other side not connected to profile");
       return true;
     }
 
@@ -1212,14 +1213,14 @@
     uint16_t target_current_credit = L2CA_GetPeerLECocCredit(
         target_side->address, GAP_ConnGetL2CAPCid(target_side->gap_handle));
     if (target_current_credit == L2CAP_LE_CREDIT_MAX) {
-      LOG(ERROR) << __func__ << ": Get target side credit value fail.";
+      LOG_ERROR("Get target side credit value fail.");
       return true;
     }
 
     uint16_t other_current_credit = L2CA_GetPeerLECocCredit(
         other_side->address, GAP_ConnGetL2CAPCid(other_side->gap_handle));
     if (other_current_credit == L2CAP_LE_CREDIT_MAX) {
-      LOG(ERROR) << __func__ << ": Get other side credit value fail.";
+      LOG_ERROR("Get other side credit value fail.");
       return true;
     }
 
@@ -1228,24 +1229,23 @@
     } else {
       diff_credit = other_current_credit - target_current_credit;
     }
-    VLOG(2) << __func__ << ": Target(" << target_side->address
-            << ") Credit: " << target_current_credit << ", Other("
-            << other_side->address << ") Credit: " << other_current_credit
-            << ", Init Credit: " << init_credit;
+    LOG_DEBUG("Target(%s) Credit: %u, Other(%s) Credit: %u, Init Credit: %u",
+              target_side->address.ToStringForLogging().c_str(),
+              target_current_credit,
+              other_side->address.ToStringForLogging().c_str(),
+              other_current_credit, init_credit);
     return diff_credit < (init_credit / 2 - 1);
   }
 
   void OnAudioDataReady(const std::vector<uint8_t>& data) {
     /* For now we assume data comes in as 16bit per sample 16kHz PCM stereo */
-    DVLOG(2) << __func__;
-
     bool need_drop = false;
     int num_samples =
         data.size() / (2 /*bytes_per_sample*/ * 2 /*number of channels*/);
 
     // The G.722 codec accept only even number of samples for encoding
     if (num_samples % 2 != 0)
-      LOG(FATAL) << "num_samples is not even: " << num_samples;
+      LOG_ALWAYS_FATAL("num_samples is not even: %d", num_samples);
 
     // TODO: we should cache left/right and current state, instad of recomputing
     // it for each packet, 100 times a second.
@@ -1261,8 +1261,7 @@
     }
 
     if (left == nullptr && right == nullptr) {
-      LOG(WARNING) << __func__ << ": No more (0/" << GetDeviceCount()
-                   << ") devices ready";
+      LOG_WARN("No more (0/%d) devices ready", GetDeviceCount());
       DoDisconnectAudioStop();
       return;
     }
@@ -1318,13 +1317,15 @@
         // Compare the two sides LE CoC credit value to confirm need to drop or
         // skip audio packet.
         if (NeedToDropPacket(left, right)) {
-          LOG(INFO) << left->address << " triggers dropping, "
-                    << packets_in_chans << " packets in channel";
+          LOG_INFO("%s triggers dropping, %u packets in channel",
+                   left->address.ToStringForLogging().c_str(),
+                   packets_in_chans);
           need_drop = true;
           left->audio_stats.trigger_drop_count++;
         } else {
-          LOG(INFO) << left->address << " skipping " << packets_in_chans
-                    << " packets";
+          LOG_INFO("%s skipping %u packets",
+                   left->address.ToStringForLogging().c_str(),
+                   packets_in_chans);
           left->audio_stats.packet_flush_count += packets_in_chans;
           left->audio_stats.frame_flush_count++;
           L2CA_FlushChannel(cid, 0xffff);
@@ -1350,13 +1351,15 @@
         // Compare the two sides LE CoC credit value to confirm need to drop or
         // skip audio packet.
         if (NeedToDropPacket(right, left)) {
-          LOG(INFO) << right->address << " triggers dropping, "
-                    << packets_in_chans << " packets in channel";
+          LOG_INFO("%s triggers dropping, %u packets in channel",
+                   right->address.ToStringForLogging().c_str(),
+                   packets_in_chans);
           need_drop = true;
           right->audio_stats.trigger_drop_count++;
         } else {
-          LOG(INFO) << right->address << " skipping " << packets_in_chans
-                    << " packets";
+          LOG_INFO("%s skipping %u packets",
+                   right->address.ToStringForLogging().c_str(),
+                   packets_in_chans);
           right->audio_stats.packet_flush_count += packets_in_chans;
           right->audio_stats.frame_flush_count++;
           L2CA_FlushChannel(cid, 0xffff);
@@ -1400,10 +1403,9 @@
   void SendAudio(uint8_t* encoded_data, uint16_t packet_size,
                  HearingDevice* hearingAid) {
     if (!hearingAid->playback_started || !hearingAid->command_acked) {
-      VLOG(2) << __func__
-              << ": Playback stalled, device=" << hearingAid->address
-              << ", cmd send=" << hearingAid->playback_started
-              << ", cmd acked=" << hearingAid->command_acked;
+      LOG_DEBUG("Playback stalled, device=%s,cmd send=%i, cmd acked=%i",
+                hearingAid->address.ToStringForLogging().c_str(),
+                hearingAid->playback_started, hearingAid->command_acked);
       return;
     }
 
@@ -1413,19 +1415,20 @@
     p++;
     memcpy(p, encoded_data, packet_size);
 
-    DVLOG(2) << hearingAid->address << " : " << base::HexEncode(p, packet_size);
+    LOG_DEBUG("%s : %s", hearingAid->address.ToStringForLogging().c_str(),
+              base::HexEncode(p, packet_size).c_str());
 
     uint16_t result = GAP_ConnWriteData(hearingAid->gap_handle, audio_packet);
 
     if (result != BT_PASS) {
-      LOG(ERROR) << " Error sending data: " << loghex(result);
+      LOG_ERROR("Error sending data: %s", loghex(result).c_str());
     }
   }
 
   void GapCallback(uint16_t gap_handle, uint16_t event, tGAP_CB_DATA* data) {
     HearingDevice* hearingDevice = hearingDevices.FindByGapHandle(gap_handle);
     if (!hearingDevice) {
-      LOG(INFO) << "Skipping unknown device, gap_handle=" << gap_handle;
+      LOG_INFO("Skipping unknown device, gap_handle=%u", gap_handle);
       return;
     }
 
@@ -1437,12 +1440,13 @@
         init_credit =
             L2CA_GetPeerLECocCredit(address, GAP_ConnGetL2CAPCid(gap_handle));
 
-        LOG(INFO) << "GAP_EVT_CONN_OPENED " << address << ", tx_mtu=" << tx_mtu
-                  << ", init_credit=" << init_credit;
+        LOG_INFO("GAP_EVT_CONN_OPENED %s, tx_mtu=%u, init_credit=%u",
+                 address.ToStringForLogging().c_str(), tx_mtu, init_credit);
 
         HearingDevice* hearingDevice = hearingDevices.FindByAddress(address);
         if (!hearingDevice) {
-          LOG(INFO) << "Skipping unknown device" << address;
+          LOG_INFO("Skipping unknown device %s",
+                   address.ToStringForLogging().c_str());
           return;
         }
         hearingDevice->gap_opened = true;
@@ -1453,10 +1457,11 @@
       }
 
       case GAP_EVT_CONN_CLOSED:
-        LOG(INFO) << __func__
-                  << ": GAP_EVT_CONN_CLOSED: " << hearingDevice->address
-                  << ", playback_started=" << hearingDevice->playback_started
-                  << ", accepting_audio=" << hearingDevice->accepting_audio;
+        LOG_INFO(
+            "GAP_EVT_CONN_CLOSED: %s, playback_started=%i, "
+            "accepting_audio=%i",
+            hearingDevice->address.ToStringForLogging().c_str(),
+            hearingDevice->playback_started, hearingDevice->accepting_audio);
         if (!hearingDevice->accepting_audio) {
           /* Disconnect connection when data channel is not available */
           BTA_GATTC_Close(hearingDevice->conn_id);
@@ -1471,7 +1476,7 @@
         }
         break;
       case GAP_EVT_CONN_DATA_AVAIL: {
-        DVLOG(2) << "GAP_EVT_CONN_DATA_AVAIL";
+        LOG_DEBUG("GAP_EVT_CONN_DATA_AVAIL");
 
         // only data we receive back from hearing aids are some stats, not
         // really important, but useful now for debugging.
@@ -1484,28 +1489,28 @@
         GAP_ConnReadData(gap_handle, buffer.data(), buffer.size(), &bytes_read);
 
         if (bytes_read < 4) {
-          LOG(WARNING) << " Wrong data length";
+          LOG_WARN("Wrong data length");
           return;
         }
 
         uint8_t* p = buffer.data();
 
-        DVLOG(1) << "stats from the hearing aid:";
+        LOG_DEBUG("stats from the hearing aid:");
         for (size_t i = 0; i + 4 <= buffer.size(); i += 4) {
           uint16_t event_counter, frame_index;
           STREAM_TO_UINT16(event_counter, p);
           STREAM_TO_UINT16(frame_index, p);
-          DVLOG(1) << "event_counter=" << event_counter
-                   << " frame_index: " << frame_index;
+          LOG_DEBUG("event_counter=%u frame_index: %u", event_counter,
+                    frame_index);
         }
         break;
       }
 
       case GAP_EVT_TX_EMPTY:
-        DVLOG(2) << "GAP_EVT_TX_EMPTY";
+        LOG_DEBUG("GAP_EVT_TX_EMPTY");
         break;
       case GAP_EVT_CONN_CONGESTED:
-        DVLOG(2) << "GAP_EVT_CONN_CONGESTED";
+        LOG_DEBUG("GAP_EVT_CONN_CONGESTED");
 
         // TODO: make it into function
         HearingAidAudioSource::Stop();
@@ -1515,7 +1520,7 @@
         // encoder_state_right = nulllptr;
         break;
       case GAP_EVT_CONN_UNCONGESTED:
-        DVLOG(2) << "GAP_EVT_CONN_UNCONGESTED";
+        LOG_DEBUG("GAP_EVT_CONN_UNCONGESTED");
         break;
     }
   }
@@ -1544,8 +1549,8 @@
       char temptime[20];
       struct tm* tstamp = localtime(&rssi_logs.timestamp.tv_sec);
       if (!strftime(temptime, sizeof(temptime), "%H:%M:%S", tstamp)) {
-        LOG(ERROR) << __func__ << ": strftime fails. tm_sec=" << tstamp->tm_sec << ", tm_min=" << tstamp->tm_min
-                   << ", tm_hour=" << tstamp->tm_hour;
+        LOG_ERROR("strftime fails. tm_sec=%d, tm_min=%d, tm_hour=%d",
+                  tstamp->tm_sec, tstamp->tm_min, tstamp->tm_hour);
         strlcpy(temptime, "UNKNOWN TIME", sizeof(temptime));
       }
       snprintf(eventtime, sizeof(eventtime), "%s.%03ld", temptime, rssi_logs.timestamp.tv_nsec / 1000000);
@@ -1586,22 +1591,22 @@
     dprintf(fd, "%s", stream.str().c_str());
   }
 
-  void Disconnect(const RawAddress& address) override {
-    DVLOG(2) << __func__;
+  void Disconnect(const RawAddress& address) {
     HearingDevice* hearingDevice = hearingDevices.FindByAddress(address);
     if (!hearingDevice) {
-      LOG(INFO) << "Device not connected to profile" << address;
+      LOG_INFO("Device not connected to profile %s",
+               address.ToStringForLogging().c_str());
       return;
     }
 
-    VLOG(2) << __func__ << ": " << address;
+    LOG_DEBUG("%s", address.ToStringForLogging().c_str());
 
     bool connected = hearingDevice->accepting_audio;
     bool connecting_by_user = hearingDevice->connecting_actively;
 
-    LOG(INFO) << __func__ << ": " << hearingDevice->address
-              << ", playback_started=" << hearingDevice->playback_started
-              << ", accepting_audio=" << hearingDevice->accepting_audio;
+    LOG_INFO("%s, playback_started=%i, accepting_audio=%i",
+             hearingDevice->address.ToStringForLogging().c_str(),
+             hearingDevice->playback_started, hearingDevice->accepting_audio);
 
     if (hearingDevice->connecting_actively) {
       // cancel pending direct connect
@@ -1634,8 +1639,7 @@
     for (const auto& device : hearingDevices.devices) {
       if (device.accepting_audio) return;
     }
-    LOG(INFO) << __func__ << ": No more (0/" << GetDeviceCount()
-              << ") devices ready";
+    LOG_INFO("No more (0/%d) devices ready", GetDeviceCount());
     DoDisconnectAudioStop();
   }
 
@@ -1643,12 +1647,12 @@
                           RawAddress remote_bda) {
     HearingDevice* hearingDevice = hearingDevices.FindByConnId(conn_id);
     if (!hearingDevice) {
-      VLOG(2) << "Skipping unknown device disconnect, conn_id="
-              << loghex(conn_id);
+      LOG_DEBUG("Skipping unknown device disconnect, conn_id=%s",
+                loghex(conn_id).c_str());
       return;
     }
-    VLOG(2) << __func__ << ": conn_id=" << loghex(conn_id)
-            << ", remote_bda=" << remote_bda;
+    LOG_DEBUG("conn_id=%s, remote_bda=%s", loghex(conn_id).c_str(),
+              remote_bda.ToStringForLogging().c_str());
 
     // Inform the other side (if any) of this disconnection
     std::vector<uint8_t> inform_disconn_state(
@@ -1667,16 +1671,15 @@
     for (const auto& device : hearingDevices.devices) {
       if (device.accepting_audio) return;
     }
-    LOG(INFO) << __func__ << ": No more (0/" << GetDeviceCount()
-              << ") devices ready";
+    LOG_INFO("No more (0/%d) devices ready", GetDeviceCount());
     DoDisconnectAudioStop();
   }
 
   void DoDisconnectCleanUp(HearingDevice* hearingDevice) {
     if (hearingDevice->connection_update_status != COMPLETED) {
-      LOG(INFO) << __func__ << ": connection update not completed. Current="
-                << hearingDevice->connection_update_status
-                << ", device=" << hearingDevice->address;
+      LOG_INFO("connection update not completed. Current=%u, device=%s",
+               hearingDevice->connection_update_status,
+               hearingDevice->address.ToStringForLogging().c_str());
 
       if (hearingDevice->connection_update_status == STARTED) {
         OnConnectionUpdateComplete(hearingDevice->conn_id, NULL);
@@ -1697,8 +1700,9 @@
     }
 
     hearingDevice->accepting_audio = false;
-    LOG(INFO) << __func__ << ": device=" << hearingDevice->address
-              << ", playback_started=" << hearingDevice->playback_started;
+    LOG_INFO("device=%s, playback_started=%i",
+             hearingDevice->address.ToStringForLogging().c_str(),
+             hearingDevice->playback_started);
     hearingDevice->playback_started = false;
     hearingDevice->command_acked = false;
   }
@@ -1710,8 +1714,8 @@
     current_volume = VOLUME_UNKNOWN;
   }
 
-  void SetVolume(int8_t volume) override {
-    VLOG(2) << __func__ << ": " << +volume;
+  void SetVolume(int8_t volume) {
+    LOG_DEBUG("%d", volume);
     current_volume = volume;
     for (HearingDevice& device : hearingDevices.devices) {
       if (!device.accepting_audio) continue;
@@ -1754,7 +1758,7 @@
                                       const gatt::Service* service) {
     HearingDevice* hearingDevice = hearingDevices.FindByConnId(conn_id);
     if (!hearingDevice) {
-      DVLOG(2) << "Skipping unknown device, conn_id=" << loghex(conn_id);
+      LOG_DEBUG("Skipping unknown device, conn_id=%s", loghex(conn_id).c_str());
       return;
     }
     for (const gatt::Characteristic& charac : service->characteristics) {
@@ -1762,12 +1766,11 @@
         hearingDevice->service_changed_ccc_handle =
             find_ccc_handle(conn_id, charac.value_handle);
         if (!hearingDevice->service_changed_ccc_handle) {
-          LOG(ERROR) << __func__
-                     << ": cannot find service changed CCC descriptor";
+          LOG_ERROR("cannot find service changed CCC descriptor");
           continue;
         }
-        LOG(INFO) << __func__ << " service_changed_ccc="
-                  << loghex(hearingDevice->service_changed_ccc_handle);
+        LOG_INFO("service_changed_ccc=%s",
+                 loghex(hearingDevice->service_changed_ccc_handle).c_str());
         break;
       }
     }
@@ -1780,7 +1783,7 @@
         BTA_GATTC_GetCharacteristic(conn_id, char_handle);
 
     if (!p_char) {
-      LOG(WARNING) << __func__ << ": No such characteristic: " << char_handle;
+      LOG_WARN("No such characteristic: %u", char_handle);
       return 0;
     }
 
@@ -1795,14 +1798,14 @@
   void send_state_change(HearingDevice* device, std::vector<uint8_t> payload) {
     if (device->conn_id != 0) {
       if (device->service_changed_rcvd) {
-        LOG(INFO)
-            << __func__
-            << ": service discover is in progress, skip send State Change cmd.";
+        LOG_INFO(
+            "service discover is in progress, skip send State Change cmd.");
         return;
       }
       // Send the data packet
-      LOG(INFO) << __func__ << ": Send State Change. device=" << device->address
-                << ", status=" << loghex(payload[1]);
+      LOG_INFO("Send State Change. device=%s, status=%s",
+               device->address.ToStringForLogging().c_str(),
+               loghex(payload[1]).c_str());
       BtaGattQueue::WriteCharacteristic(
           device->conn_id, device->audio_control_point_handle, payload,
           GATT_WRITE_NO_RSP, nullptr, nullptr);
@@ -1825,7 +1828,7 @@
       device->num_intervals_since_last_rssi_read++;
       if (device->num_intervals_since_last_rssi_read >= PERIOD_TO_READ_RSSI_IN_INTERVALS) {
         device->num_intervals_since_last_rssi_read = 0;
-        VLOG(1) << __func__ << ": device=" << device->address;
+        LOG_DEBUG("device=%s", device->address.ToStringForLogging().c_str());
         BTM_ReadRSSI(device->address, read_rssi_cb);
       }
     }
@@ -1843,7 +1846,7 @@
 }
 
 void hearingaid_gattc_callback(tBTA_GATTC_EVT event, tBTA_GATTC* p_data) {
-  VLOG(2) << __func__ << " event = " << +event;
+  LOG_DEBUG("event = %u", event);
 
   if (p_data == nullptr) return;
 
@@ -1874,9 +1877,8 @@
     case BTA_GATTC_NOTIF_EVT:
       if (!instance) return;
       if (!p_data->notify.is_notify || p_data->notify.len > GATT_MAX_ATTR_LEN) {
-        LOG(ERROR) << __func__ << ": rejected BTA_GATTC_NOTIF_EVT. is_notify="
-                   << p_data->notify.is_notify
-                   << ", len=" << p_data->notify.len;
+        LOG_ERROR("rejected BTA_GATTC_NOTIF_EVT. is_notify=%i, len=%u",
+                  p_data->notify.is_notify, p_data->notify.len);
         break;
       }
       instance->OnNotificationEvent(p_data->notify.conn_id,
@@ -1945,7 +1947,8 @@
 void HearingAid::Initialize(
     bluetooth::hearing_aid::HearingAidCallbacks* callbacks, Closure initCb) {
   if (instance) {
-    LOG(ERROR) << "Already initialized!";
+    LOG_ERROR("Already initialized!");
+    return;
   }
 
   audioReceiver = &audioReceiverImpl;
@@ -1955,15 +1958,42 @@
 
 bool HearingAid::IsHearingAidRunning() { return instance; }
 
-HearingAid* HearingAid::Get() {
-  CHECK(instance);
-  return instance;
-};
+void HearingAid::Connect(const RawAddress& address) {
+  if (!instance) {
+    LOG_ERROR("Hearing Aid instance is not available");
+    return;
+  }
+  instance->Connect(address);
+}
+
+void HearingAid::Disconnect(const RawAddress& address) {
+  if (!instance) {
+    LOG_ERROR("Hearing Aid instance is not available");
+    return;
+  }
+  instance->Disconnect(address);
+}
+
+void HearingAid::AddToAcceptlist(const RawAddress& address) {
+  if (!instance) {
+    LOG_ERROR("Hearing Aid instance is not available");
+    return;
+  }
+  instance->AddToAcceptlist(address);
+}
+
+void HearingAid::SetVolume(int8_t volume) {
+  if (!instance) {
+    LOG_ERROR("Hearing Aid instance is not available");
+    return;
+  }
+  instance->SetVolume(volume);
+}
 
 void HearingAid::AddFromStorage(const HearingDevice& dev_info,
                                 uint16_t is_acceptlisted) {
   if (!instance) {
-    LOG(ERROR) << "Not initialized yet";
+    LOG_ERROR("Not initialized yet");
   }
 
   instance->AddFromStorage(dev_info, is_acceptlisted);
@@ -1971,7 +2001,7 @@
 
 int HearingAid::GetDeviceCount() {
   if (!instance) {
-    LOG(INFO) << __func__ << ": Not initialized yet";
+    LOG_INFO("Not initialized yet");
     return 0;
   }
 
diff --git a/system/bta/hearing_aid/hearing_aid_audio_source.cc b/system/bta/hearing_aid/hearing_aid_audio_source.cc
index c7dc6ae..e347f87 100644
--- a/system/bta/hearing_aid/hearing_aid_audio_source.cc
+++ b/system/bta/hearing_aid/hearing_aid_audio_source.cc
@@ -18,6 +18,7 @@
 
 #include <base/files/file_util.h>
 #include <base/logging.h>
+
 #include <cstdint>
 #include <memory>
 #include <sstream>
@@ -28,6 +29,7 @@
 #include "bta/include/bta_hearing_aid_api.h"
 #include "common/repeating_timer.h"
 #include "common/time_util.h"
+#include "osi/include/log.h"
 #include "osi/include/wakelock.h"
 #include "stack/include/btu.h"  // get_main_thread
 #include "udrv/include/uipc.h"
@@ -98,7 +100,7 @@
                            bytes_per_tick);
   }
 
-  VLOG(2) << "bytes_read: " << bytes_read;
+  LOG_DEBUG("bytes_read: %u", bytes_read);
   if (bytes_read < bytes_per_tick) {
     stats.media_read_total_underflow_bytes += bytes_per_tick - bytes_read;
     stats.media_read_total_underflow_count++;
@@ -115,14 +117,14 @@
 
 void hearing_aid_send_ack(tHEARING_AID_CTRL_ACK status) {
   uint8_t ack = status;
-  DVLOG(2) << "Hearing Aid audio ctrl ack: " << status;
+  LOG_DEBUG("Hearing Aid audio ctrl ack: %u", status);
   UIPC_Send(*uipc_hearing_aid, UIPC_CH_ID_AV_CTRL, 0, &ack, sizeof(ack));
 }
 
 void start_audio_ticks() {
   if (data_interval_ms != HA_INTERVAL_10_MS &&
       data_interval_ms != HA_INTERVAL_20_MS) {
-    LOG(FATAL) << " Unsupported data interval: " << data_interval_ms;
+    LOG_ALWAYS_FATAL("Unsupported data interval: %d", data_interval_ms);
   }
 
   wakelock_acquire();
@@ -133,20 +135,20 @@
 #else
       base::Milliseconds(data_interval_ms));
 #endif
-  LOG(INFO) << __func__ << ": running with data interval: " << data_interval_ms;
+  LOG_INFO("running with data interval: %d", data_interval_ms);
 }
 
 void stop_audio_ticks() {
-  LOG(INFO) << __func__ << ": stopped";
+  LOG_INFO("stopped");
   audio_timer.CancelAndWait();
   wakelock_release();
 }
 
 void hearing_aid_data_cb(tUIPC_CH_ID, tUIPC_EVENT event) {
-  DVLOG(2) << "Hearing Aid audio data event: " << event;
+  LOG_DEBUG("Hearing Aid audio data event: %u", event);
   switch (event) {
     case UIPC_OPEN_EVT:
-      LOG(INFO) << __func__ << ": UIPC_OPEN_EVT";
+      LOG_INFO("UIPC_OPEN_EVT");
       /*
        * Read directly from media task from here on (keep callback for
        * connection events.
@@ -159,12 +161,12 @@
       do_in_main_thread(FROM_HERE, base::BindOnce(start_audio_ticks));
       break;
     case UIPC_CLOSE_EVT:
-      LOG(INFO) << __func__ << ": UIPC_CLOSE_EVT";
+      LOG_INFO("UIPC_CLOSE_EVT");
       hearing_aid_send_ack(HEARING_AID_CTRL_ACK_SUCCESS);
       do_in_main_thread(FROM_HERE, base::BindOnce(stop_audio_ticks));
       break;
     default:
-      LOG(ERROR) << "Hearing Aid audio data event not recognized:" << event;
+      LOG_ERROR("Hearing Aid audio data event not recognized: %u", event);
   }
 }
 
@@ -178,12 +180,12 @@
 
   /* detach on ctrl channel means audioflinger process was terminated */
   if (n == 0) {
-    LOG(WARNING) << __func__ << "CTRL CH DETACHED";
+    LOG_WARN("CTRL CH DETACHED");
     UIPC_Close(*uipc_hearing_aid, UIPC_CH_ID_AV_CTRL);
     return;
   }
 
-  LOG(INFO) << __func__ << " " << audio_ha_hw_dump_ctrl_event(cmd);
+  LOG_INFO("%s", audio_ha_hw_dump_ctrl_event(cmd));
   //  a2dp_cmd_pending = cmd;
 
   tHEARING_AID_CTRL_ACK ctrl_ack_status;
@@ -207,7 +209,9 @@
 
     case HEARING_AID_CTRL_CMD_STOP:
       if (!hearing_aid_on_suspend_req()) {
-        LOG(INFO) << __func__ << ":HEARING_AID_CTRL_CMD_STOP: hearing_aid_on_suspend_req() errs, but ignored.";
+        LOG_INFO(
+            "HEARING_AID_CTRL_CMD_STOP: hearing_aid_on_suspend_req() errs, but "
+            "ignored.");
       }
       hearing_aid_send_ack(HEARING_AID_CTRL_ACK_SUCCESS);
       break;
@@ -230,7 +234,7 @@
         codec_config.sample_rate = BTAV_A2DP_CODEC_SAMPLE_RATE_24000;
         codec_capability.sample_rate = BTAV_A2DP_CODEC_SAMPLE_RATE_24000;
       } else {
-        LOG(FATAL) << "unsupported sample rate: " << sample_rate;
+        LOG_ALWAYS_FATAL("unsupported sample rate: %d", sample_rate);
       }
 
       codec_config.bits_per_sample = BTAV_A2DP_CODEC_BITS_PER_SAMPLE_16;
@@ -278,15 +282,14 @@
                     reinterpret_cast<uint8_t*>(&codec_config.sample_rate),
                     sizeof(btav_a2dp_codec_sample_rate_t)) !=
           sizeof(btav_a2dp_codec_sample_rate_t)) {
-        LOG(ERROR) << __func__ << "Error reading sample rate from audio HAL";
+        LOG_ERROR("Error reading sample rate from audio HAL");
         break;
       }
       if (UIPC_Read(*uipc_hearing_aid, UIPC_CH_ID_AV_CTRL,
                     reinterpret_cast<uint8_t*>(&codec_config.bits_per_sample),
                     sizeof(btav_a2dp_codec_bits_per_sample_t)) !=
           sizeof(btav_a2dp_codec_bits_per_sample_t)) {
-        LOG(ERROR) << __func__
-                   << "Error reading bits per sample from audio HAL";
+        LOG_ERROR("Error reading bits per sample from audio HAL");
 
         break;
       }
@@ -294,29 +297,28 @@
                     reinterpret_cast<uint8_t*>(&codec_config.channel_mode),
                     sizeof(btav_a2dp_codec_channel_mode_t)) !=
           sizeof(btav_a2dp_codec_channel_mode_t)) {
-        LOG(ERROR) << __func__ << "Error reading channel mode from audio HAL";
+        LOG_ERROR("Error reading channel mode from audio HAL");
 
         break;
       }
-      LOG(INFO) << __func__ << " HEARING_AID_CTRL_SET_OUTPUT_AUDIO_CONFIG: "
-                << "sample_rate=" << codec_config.sample_rate
-                << "bits_per_sample=" << codec_config.bits_per_sample
-                << "channel_mode=" << codec_config.channel_mode;
+      LOG_INFO(
+          "HEARING_AID_CTRL_SET_OUTPUT_AUDIO_CONFIG: sample_rate=%u, "
+          "bits_per_sample=%u,channel_mode=%u",
+          codec_config.sample_rate, codec_config.bits_per_sample,
+          codec_config.channel_mode);
       break;
     }
 
     default:
-      LOG(ERROR) << __func__ << "UNSUPPORTED CMD: " << cmd;
+      LOG_ERROR("UNSUPPORTED CMD: %u", cmd);
       hearing_aid_send_ack(HEARING_AID_CTRL_ACK_FAILURE);
       break;
   }
-  LOG(INFO) << __func__
-            << " a2dp-ctrl-cmd : " << audio_ha_hw_dump_ctrl_event(cmd)
-            << " DONE";
+  LOG_INFO("a2dp-ctrl-cmd : %s DONE", audio_ha_hw_dump_ctrl_event(cmd));
 }
 
 void hearing_aid_ctrl_cb(tUIPC_CH_ID, tUIPC_EVENT event) {
-  VLOG(2) << "Hearing Aid audio ctrl event: " << event;
+  LOG_DEBUG("Hearing Aid audio ctrl event: %u", event);
   switch (event) {
     case UIPC_OPEN_EVT:
       break;
@@ -331,14 +333,13 @@
       hearing_aid_recv_ctrl_data();
       break;
     default:
-      LOG(ERROR) << "Hearing Aid audio ctrl unrecognized event: " << event;
+      LOG_ERROR("Hearing Aid audio ctrl unrecognized event: %u", event);
   }
 }
 
 bool hearing_aid_on_resume_req(bool start_media_task) {
   if (localAudioReceiver == nullptr) {
-    LOG(ERROR) << __func__
-               << ": HEARING_AID_CTRL_CMD_START: audio receiver not started";
+    LOG_ERROR("HEARING_AID_CTRL_CMD_START: audio receiver not started");
     return false;
   }
   bt_status_t status;
@@ -349,7 +350,7 @@
                                   start_audio_ticks));
   } else {
     auto start_dummy_ticks = []() {
-      LOG(INFO) << "start_audio_ticks: waiting for data path opened";
+      LOG_INFO("start_audio_ticks: waiting for data path opened");
     };
     status = do_in_main_thread(
         FROM_HERE, base::BindOnce(&HearingAidAudioReceiver::OnAudioResume,
@@ -357,9 +358,7 @@
                                   start_dummy_ticks));
   }
   if (status != BT_STATUS_SUCCESS) {
-    LOG(ERROR) << __func__
-               << ": HEARING_AID_CTRL_CMD_START: do_in_main_thread err="
-               << status;
+    LOG_ERROR("HEARING_AID_CTRL_CMD_START: do_in_main_thread err=%u", status);
     return false;
   }
   return true;
@@ -367,8 +366,7 @@
 
 bool hearing_aid_on_suspend_req() {
   if (localAudioReceiver == nullptr) {
-    LOG(ERROR) << __func__
-               << ": HEARING_AID_CTRL_CMD_SUSPEND: audio receiver not started";
+    LOG_ERROR("HEARING_AID_CTRL_CMD_SUSPEND: audio receiver not started");
     return false;
   }
   bt_status_t status = do_in_main_thread(
@@ -376,9 +374,7 @@
       base::BindOnce(&HearingAidAudioReceiver::OnAudioSuspend,
                      base::Unretained(localAudioReceiver), stop_audio_ticks));
   if (status != BT_STATUS_SUCCESS) {
-    LOG(ERROR) << __func__
-               << ": HEARING_AID_CTRL_CMD_SUSPEND: do_in_main_thread err="
-               << status;
+    LOG_ERROR("HEARING_AID_CTRL_CMD_SUSPEND: do_in_main_thread err=%u", status);
     return false;
   }
   return true;
@@ -388,7 +384,7 @@
 void HearingAidAudioSource::Start(const CodecConfiguration& codecConfiguration,
                                   HearingAidAudioReceiver* audioReceiver,
                                   uint16_t remote_delay_ms) {
-  LOG(INFO) << __func__ << ": Hearing Aid Source Open";
+  LOG_INFO("Hearing Aid Source Open");
 
   bit_rate = codecConfiguration.bit_rate;
   sample_rate = codecConfiguration.sample_rate;
@@ -404,7 +400,7 @@
 }
 
 void HearingAidAudioSource::Stop() {
-  LOG(INFO) << __func__ << ": Hearing Aid Source Close";
+  LOG_INFO("Hearing Aid Source Close");
 
   localAudioReceiver = nullptr;
   if (bluetooth::audio::hearing_aid::is_hal_enabled()) {
@@ -420,7 +416,7 @@
       .on_suspend_ = hearing_aid_on_suspend_req,
   };
   if (!bluetooth::audio::hearing_aid::init(stream_cb, get_main_thread())) {
-    LOG(WARNING) << __func__ << ": Using legacy HAL";
+    LOG_WARN("Using legacy HAL");
     uipc_hearing_aid = UIPC_Init();
     UIPC_Open(*uipc_hearing_aid, UIPC_CH_ID_AV_CTRL, hearing_aid_ctrl_cb, HEARING_AID_CTRL_PATH);
   }
diff --git a/system/bta/hf_client/bta_hf_client_api.cc b/system/bta/hf_client/bta_hf_client_api.cc
index 15d57fe..d64b5ed 100644
--- a/system/bta/hf_client/bta_hf_client_api.cc
+++ b/system/bta/hf_client/bta_hf_client_api.cc
@@ -28,6 +28,10 @@
 
 #include <cstdint>
 
+#ifdef OS_ANDROID
+#include <hfp.sysprop.h>
+#endif
+
 #include "bt_trace.h"  // Legacy trace logging
 #include "bta/hf_client/bta_hf_client_int.h"
 #include "bta/sys/bta_sys.h"
@@ -80,17 +84,17 @@
  * Description      Opens up a RF connection to the remote device and
  *                  subsequently set it up for a HF SLC
  *
- * Returns          void
+ * Returns          bt_status_t
  *
  ******************************************************************************/
-void BTA_HfClientOpen(const RawAddress& bd_addr, uint16_t* p_handle) {
+bt_status_t BTA_HfClientOpen(const RawAddress& bd_addr, uint16_t* p_handle) {
   APPL_TRACE_DEBUG("%s", __func__);
   tBTA_HF_CLIENT_API_OPEN* p_buf =
       (tBTA_HF_CLIENT_API_OPEN*)osi_malloc(sizeof(tBTA_HF_CLIENT_API_OPEN));
 
   if (!bta_hf_client_allocate_handle(bd_addr, p_handle)) {
     APPL_TRACE_ERROR("%s: could not allocate handle", __func__);
-    return;
+    return BT_STATUS_FAIL;
   }
 
   p_buf->hdr.event = BTA_HF_CLIENT_API_OPEN_EVT;
@@ -98,6 +102,7 @@
   p_buf->bd_addr = bd_addr;
 
   bta_sys_sendmsg(p_buf);
+  return BT_STATUS_SUCCESS;
 }
 
 /*******************************************************************************
@@ -203,3 +208,29 @@
  *
  ******************************************************************************/
 void BTA_HfClientDumpStatistics(int fd) { bta_hf_client_dump_statistics(fd); }
+
+/*******************************************************************************
+ *
+ * function         get_default_hf_client_features
+ *
+ * description      return the hf_client features.
+ *                  value can be override via system property
+ *
+ * returns          int
+ *
+ ******************************************************************************/
+int get_default_hf_client_features() {
+#define DEFAULT_BTIF_HF_CLIENT_FEATURES                                        \
+  (BTA_HF_CLIENT_FEAT_ECNR | BTA_HF_CLIENT_FEAT_3WAY |                         \
+   BTA_HF_CLIENT_FEAT_CLI | BTA_HF_CLIENT_FEAT_VREC | BTA_HF_CLIENT_FEAT_VOL | \
+   BTA_HF_CLIENT_FEAT_ECS | BTA_HF_CLIENT_FEAT_ECC | BTA_HF_CLIENT_FEAT_CODEC)
+
+#ifdef OS_ANDROID
+  static const int features =
+      android::sysprop::bluetooth::Hfp::hf_client_features().value_or(
+          DEFAULT_BTIF_HF_CLIENT_FEATURES);
+  return features;
+#else
+  return DEFAULT_BTIF_HF_CLIENT_FEATURES;
+#endif
+}
diff --git a/system/bta/hf_client/bta_hf_client_at.cc b/system/bta/hf_client/bta_hf_client_at.cc
index bb9eec3..97fcb97 100644
--- a/system/bta/hf_client/bta_hf_client_at.cc
+++ b/system/bta/hf_client/bta_hf_client_at.cc
@@ -20,11 +20,11 @@
 #define LOG_TAG "bt_hf_client"
 
 #include "bt_trace.h"  // Legacy trace logging
-
 #include "bta/hf_client/bta_hf_client_int.h"
 #include "osi/include/allocator.h"
 #include "osi/include/compat.h"
 #include "osi/include/log.h"
+#include "osi/include/properties.h"
 #include "stack/include/port_api.h"
 
 /* Uncomment to enable AT traffic dumping */
@@ -124,7 +124,7 @@
   tBTA_HF_CLIENT_AT_QCMD* new_cmd =
       (tBTA_HF_CLIENT_AT_QCMD*)osi_malloc(sizeof(tBTA_HF_CLIENT_AT_QCMD));
 
-  APPL_TRACE_DEBUG("%s", __func__);
+  APPL_TRACE_DEBUG("%s: cmd:%d", __func__, (int)cmd);
 
   new_cmd->cmd = cmd;
   new_cmd->buf_len = buf_len;
@@ -169,7 +169,7 @@
 static void bta_hf_client_send_at(tBTA_HF_CLIENT_CB* client_cb,
                                   tBTA_HF_CLIENT_AT_CMD cmd, const char* buf,
                                   uint16_t buf_len) {
-  APPL_TRACE_DEBUG("%s", __func__);
+  APPL_TRACE_DEBUG("%s %d", __func__, cmd);
   if ((client_cb->at_cb.current_cmd == BTA_HF_CLIENT_AT_NONE ||
        !client_cb->svc_conn) &&
       !alarm_is_scheduled(client_cb->at_cb.hold_timer)) {
@@ -197,6 +197,7 @@
     return;
   }
 
+  APPL_TRACE_DEBUG("%s: busy! queued: %d", __func__, cmd);
   bta_hf_client_queue_at(client_cb, cmd, buf, buf_len);
 }
 
@@ -240,7 +241,8 @@
  ******************************************************************************/
 
 static void bta_hf_client_handle_ok(tBTA_HF_CLIENT_CB* client_cb) {
-  APPL_TRACE_DEBUG("%s", __func__);
+  APPL_TRACE_DEBUG("%s: current_cmd:%d", __func__,
+                   client_cb->at_cb.current_cmd);
 
   bta_hf_client_stop_at_resp_timer(client_cb);
 
@@ -265,6 +267,9 @@
     case BTA_HF_CLIENT_AT_NONE:
       bta_hf_client_stop_at_hold_timer(client_cb);
       break;
+    case BTA_HF_CLIENT_AT_ANDROID:
+      bta_hf_client_at_result(client_cb, BTA_HF_CLIENT_AT_RESULT_OK, 0);
+      break;
     default:
       if (client_cb->send_at_reply) {
         bta_hf_client_at_result(client_cb, BTA_HF_CLIENT_AT_RESULT_OK, 0);
@@ -280,7 +285,8 @@
 static void bta_hf_client_handle_error(tBTA_HF_CLIENT_CB* client_cb,
                                        tBTA_HF_CLIENT_AT_RESULT_TYPE type,
                                        uint16_t cme) {
-  APPL_TRACE_DEBUG("%s: %u %u", __func__, type, cme);
+  APPL_TRACE_DEBUG("%s: type:%u cme:%u current_cmd:%d", __func__, type, cme,
+                   client_cb->at_cb.current_cmd);
 
   bta_hf_client_stop_at_resp_timer(client_cb);
 
@@ -301,6 +307,9 @@
         client_cb->send_at_reply = true;
       }
       break;
+    case BTA_HF_CLIENT_AT_ANDROID:
+      bta_hf_client_at_result(client_cb, type, cme);
+      break;
     default:
       if (client_cb->send_at_reply) {
         bta_hf_client_at_result(client_cb, type, cme);
@@ -2139,19 +2148,19 @@
 
   at_len = snprintf(buf, sizeof(buf), "AT+BIA=");
 
+  const int32_t position = osi_property_get_int32(
+      "bluetooth.headset_client.disable_indicator.position", -1);
+
   for (i = 0; i < BTA_HF_CLIENT_AT_INDICATOR_COUNT; i++) {
     int sup = client_cb->at_cb.indicator_lookup[i] == -1 ? 0 : 1;
 
-/* If this value matches the position of SIGNAL in the indicators array,
- * then hardcode disable signal strength indicators.
- * indicator_lookup[i] points to the position in the bta_hf_client_indicators
- * array defined at the top of this file */
-#ifdef BTA_HF_CLIENT_INDICATOR_SIGNAL_POS
-    if (client_cb->at_cb.indicator_lookup[i] ==
-        BTA_HF_CLIENT_INDICATOR_SIGNAL_POS) {
+    /* If this value matches the position of SIGNAL in the indicators array,
+     * then hardcode disable signal strength indicators.
+     * indicator_lookup[i] points to the position in the
+     * bta_hf_client_indicators array defined at the top of this file */
+    if (client_cb->at_cb.indicator_lookup[i] == position) {
       sup = 0;
     }
-#endif
 
     at_len += snprintf(buf + at_len, sizeof(buf) - at_len, "%u,", sup);
   }
@@ -2185,6 +2194,22 @@
                         at_len);
 }
 
+void bta_hf_client_send_at_android(tBTA_HF_CLIENT_CB* client_cb,
+                                   const char* str) {
+  char buf[BTA_HF_CLIENT_AT_MAX_LEN];
+  int at_len;
+
+  APPL_TRACE_DEBUG("%s", __func__);
+
+  at_len = snprintf(buf, sizeof(buf), "AT%s\r", str);
+  if (at_len < 0) {
+    APPL_TRACE_ERROR("%s: AT command Framing error", __func__);
+    return;
+  }
+
+  bta_hf_client_send_at(client_cb, BTA_HF_CLIENT_AT_ANDROID, buf, at_len);
+}
+
 void bta_hf_client_at_init(tBTA_HF_CLIENT_CB* client_cb) {
   alarm_free(client_cb->at_cb.resp_timer);
   alarm_free(client_cb->at_cb.hold_timer);
@@ -2284,6 +2309,9 @@
     case BTA_HF_CLIENT_AT_CMD_VENDOR_SPECIFIC_CMD:
       bta_hf_client_send_at_vendor_specific_cmd(client_cb, p_val->str);
       break;
+    case BTA_HF_CLIENT_AT_CMD_ANDROID:
+      bta_hf_client_send_at_android(client_cb, p_val->str);
+      break;
     default:
       APPL_TRACE_ERROR("Default case");
       snprintf(buf, BTA_HF_CLIENT_AT_MAX_LEN,
diff --git a/system/bta/hf_client/bta_hf_client_int.h b/system/bta/hf_client/bta_hf_client_int.h
index 3c13e6c..cfa63bf 100755
--- a/system/bta/hf_client/bta_hf_client_int.h
+++ b/system/bta/hf_client/bta_hf_client_int.h
@@ -105,6 +105,7 @@
   BTA_HF_CLIENT_AT_BIND_READ_ENABLED_IND,
   BTA_HF_CLIENT_AT_BIEV,
   BTA_HF_CLIENT_AT_VENDOR_SPECIFIC,
+  BTA_HF_CLIENT_AT_ANDROID,
 };
 
 /*****************************************************************************
diff --git a/system/bta/hf_client/bta_hf_client_sdp.cc b/system/bta/hf_client/bta_hf_client_sdp.cc
index 4625b6d..cfb88d3 100755
--- a/system/bta/hf_client/bta_hf_client_sdp.cc
+++ b/system/bta/hf_client/bta_hf_client_sdp.cc
@@ -46,6 +46,22 @@
 /* Number of elements in service class id list. */
 #define BTA_HF_CLIENT_NUM_SVC_ELEMS 2
 
+#ifdef OS_ANDROID
+#include <hfp.sysprop.h>
+#endif
+
+#define DEFAULT_BTA_HFP_VERSION HFP_VERSION_1_7
+int get_default_hfp_version() {
+#ifdef OS_ANDROID
+  static const int version =
+      android::sysprop::bluetooth::Hfp::version().value_or(
+          DEFAULT_BTA_HFP_VERSION);
+  return version;
+#else
+  return DEFAULT_BTA_HFP_VERSION;
+#endif
+}
+
 /*******************************************************************************
  *
  * Function         bta_hf_client_sdp_cback
@@ -124,7 +140,7 @@
 
   /* add profile descriptor list */
   profile_uuid = UUID_SERVCLASS_HF_HANDSFREE;
-  version = BTA_HFP_VERSION;
+  version = get_default_hfp_version();
 
   result &= SDP_AddProfileDescriptorList(sdp_handle, profile_uuid, version);
 
diff --git a/system/bta/hfp/bta_hfp_api.cc b/system/bta/hfp/bta_hfp_api.cc
new file mode 100644
index 0000000..63ece44
--- /dev/null
+++ b/system/bta/hfp/bta_hfp_api.cc
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "bta_hfp_api.h"
+
+#ifdef OS_ANDROID
+#include <hfp.sysprop.h>
+#endif
+
+#define DEFAULT_BTA_HFP_VERSION HFP_VERSION_1_7
+
+int get_default_hfp_version() {
+#ifdef OS_ANDROID
+  static const int version =
+      android::sysprop::bluetooth::Hfp::version().value_or(
+          DEFAULT_BTA_HFP_VERSION);
+  return version;
+#else
+  return DEFAULT_BTA_HFP_VERSION;
+#endif
+}
diff --git a/system/bta/hh/bta_hh_act.cc b/system/bta/hh/bta_hh_act.cc
index 38415c3..e768975 100644
--- a/system/bta/hh/bta_hh_act.cc
+++ b/system/bta/hh/bta_hh_act.cc
@@ -159,14 +159,13 @@
  ******************************************************************************/
 void bta_hh_disc_cmpl(void) {
   LOG_DEBUG("Disconnect complete");
-
-  HID_HostDeregister();
-  bta_hh_le_deregister();
   tBTA_HH_STATUS status = BTA_HH_OK;
 
   /* Deregister with lower layer */
   if (HID_HostDeregister() != HID_SUCCESS) status = BTA_HH_ERR;
 
+  bta_hh_le_deregister();
+
   bta_hh_cleanup_disable(status);
 }
 
diff --git a/system/bta/include/bta_gatt_api.h b/system/bta/include/bta_gatt_api.h
index d334bcc..9fd4db8 100644
--- a/system/bta/include/bta_gatt_api.h
+++ b/system/bta/include/bta_gatt_api.h
@@ -999,4 +999,7 @@
  ******************************************************************************/
 extern void BTA_GATTS_Close(uint16_t conn_id);
 
+// Adds bonded device for GATT server tracking service changes
+extern void BTA_GATTS_InitBonded(void);
+
 #endif /* BTA_GATT_API_H */
diff --git a/system/bta/include/bta_hearing_aid_api.h b/system/bta/include/bta_hearing_aid_api.h
index 90803c5..d1b930b 100644
--- a/system/bta/include/bta_hearing_aid_api.h
+++ b/system/bta/include/bta_hearing_aid_api.h
@@ -228,7 +228,6 @@
                          base::Closure initCb);
   static void CleanUp();
   static bool IsHearingAidRunning();
-  static HearingAid* Get();
   static void DebugDump(int fd);
 
   static void AddFromStorage(const HearingDevice& dev_info,
@@ -236,10 +235,10 @@
 
   static int GetDeviceCount();
 
-  virtual void Connect(const RawAddress& address) = 0;
-  virtual void Disconnect(const RawAddress& address) = 0;
-  virtual void AddToAcceptlist(const RawAddress& address) = 0;
-  virtual void SetVolume(int8_t volume) = 0;
+  static void Connect(const RawAddress& address);
+  static void Disconnect(const RawAddress& address);
+  static void AddToAcceptlist(const RawAddress& address);
+  static void SetVolume(int8_t volume);
 };
 
 /* Represents configuration of audio codec, as exchanged between hearing aid and
diff --git a/system/bta/include/bta_hf_client_api.h b/system/bta/include/bta_hf_client_api.h
old mode 100755
new mode 100644
index 32822c2..fe0728e
--- a/system/bta/include/bta_hf_client_api.h
+++ b/system/bta/include/bta_hf_client_api.h
@@ -172,6 +172,7 @@
 #define BTA_HF_CLIENT_AT_CMD_NREC 15
 #define BTA_HF_CLIENT_AT_CMD_VENDOR_SPECIFIC_CMD 16
 #define BTA_HF_CLIENT_AT_CMD_BIEV 17
+#define BTA_HF_CLIENT_AT_CMD_ANDROID 18
 
 typedef uint8_t tBTA_HF_CLIENT_AT_CMD_TYPE;
 
@@ -321,10 +322,10 @@
  *                  calls to do any AT operations
  *
  *
- * Returns          void
+ * Returns          bt_status_t
  *
  ******************************************************************************/
-void BTA_HfClientOpen(const RawAddress& bd_addr, uint16_t* p_handle);
+bt_status_t BTA_HfClientOpen(const RawAddress& bd_addr, uint16_t* p_handle);
 
 /*******************************************************************************
  *
@@ -390,4 +391,15 @@
  ******************************************************************************/
 void BTA_HfClientDumpStatistics(int fd);
 
+/*******************************************************************************
+ *
+ * function         get_default_hf_client_features
+ *
+ * description      return the hf_client features.
+ *                  value can be override via system property
+ *
+ * returns          int
+ *
+ ******************************************************************************/
+int get_default_hf_client_features();
 #endif /* BTA_HF_CLIENT_API_H */
diff --git a/system/bta/include/bta_hfp_api.h b/system/bta/include/bta_hfp_api.h
index a3dd3a9..5c3b59b 100644
--- a/system/bta/include/bta_hfp_api.h
+++ b/system/bta/include/bta_hfp_api.h
@@ -24,6 +24,7 @@
 #define HFP_VERSION_1_5 0x0105
 #define HFP_VERSION_1_6 0x0106
 #define HFP_VERSION_1_7 0x0107
+#define HFP_VERSION_1_8 0x0108
 
 #define HSP_VERSION_1_0 0x0100
 #define HSP_VERSION_1_2 0x0102
@@ -31,9 +32,6 @@
 #define HFP_VERSION_CONFIG_KEY "HfpVersion"
 #define HFP_SDP_FEATURES_CONFIG_KEY "HfpSdpFeatures"
 
-/* Default HFP Version */
-#ifndef BTA_HFP_VERSION
-#define BTA_HFP_VERSION HFP_VERSION_1_7
-#endif
+int get_default_hfp_version();
 
 #endif /* BTA_HFP_API_H */
\ No newline at end of file
diff --git a/system/bta/le_audio/audio_hal_client/audio_hal_client_test.cc b/system/bta/le_audio/audio_hal_client/audio_hal_client_test.cc
index f55eee0..dfbb026 100644
--- a/system/bta/le_audio/audio_hal_client/audio_hal_client_test.cc
+++ b/system/bta/le_audio/audio_hal_client/audio_hal_client_test.cc
@@ -34,6 +34,7 @@
 using ::testing::Assign;
 using ::testing::AtLeast;
 using ::testing::DoAll;
+using ::testing::DoDefault;
 using ::testing::Invoke;
 using ::testing::Mock;
 using ::testing::Return;
@@ -476,6 +477,13 @@
         // Return exactly as much data as requested
         promise.set_value();
         return len;
+      }))
+      .WillRepeatedly(Invoke([](uint8_t* p_buf, uint32_t len) -> uint32_t {
+        // fake some data from audio framework
+        for (uint32_t i = 0u; i < len; ++i) {
+          p_buf[i] = i;
+        }
+        return len;
       }));
 
   std::promise<void> data_promise;
@@ -484,10 +492,12 @@
   /* Expect this callback to be called to Client by the HAL glue layer */
   std::vector<uint8_t> media_data_to_send;
   EXPECT_CALL(mock_hal_sink_event_receiver_, OnAudioDataReady(_))
+      .Times(AtLeast(1))
       .WillOnce(Invoke([&](const std::vector<uint8_t>& data) -> void {
         media_data_to_send = std::move(data);
         data_promise.set_value();
-      }));
+      }))
+      .WillRepeatedly(DoDefault());
 
   /* Expect LeAudio registered event listener to get called when HAL calls the
    * audio_hal_client's internal resume callback.
diff --git a/system/bta/le_audio/client.cc b/system/bta/le_audio/client.cc
index b5061ef..1d3cb3f 100644
--- a/system/bta/le_audio/client.cc
+++ b/system/bta/le_audio/client.cc
@@ -460,6 +460,8 @@
         ToString(group->GetTargetState()).c_str());
     group->SetTargetState(AseState::BTA_LE_AUDIO_ASE_STATE_IDLE);
 
+    group->PrintDebugState();
+
     /* There is an issue with a setting up stream or any other operation which
      * are gatt operations. It means peer is not responsable. Lets close ACL
      */
@@ -483,6 +485,12 @@
 
   void UpdateContextAndLocations(LeAudioDeviceGroup* group,
                                  LeAudioDevice* leAudioDevice) {
+    if (leAudioDevice->GetConnectionState() != DeviceConnectState::CONNECTED) {
+      LOG_DEBUG("%s not yet connected ",
+                leAudioDevice->address_.ToString().c_str());
+      return;
+    }
+
     /* Make sure location and direction are updated for the group. */
     auto location_update = group->ReloadAudioLocations();
     group->ReloadAudioDirections();
@@ -1342,15 +1350,15 @@
 
     leAudioDevice->SetConnectionState(DeviceConnectState::DISCONNECTING);
 
-    if (acl_force_disconnect) {
-      leAudioDevice->DisconnectAcl();
-      return;
-    }
-
     BtaGattQueue::Clean(leAudioDevice->conn_id_);
     BTA_GATTC_Close(leAudioDevice->conn_id_);
     leAudioDevice->conn_id_ = GATT_INVALID_CONN_ID;
     leAudioDevice->mtu_ = 0;
+
+    /* Remote in bad state, force ACL Disconnection. */
+    if (acl_force_disconnect) {
+      leAudioDevice->DisconnectAcl();
+    }
   }
 
   void DeregisterNotifications(LeAudioDevice* leAudioDevice) {
@@ -1910,19 +1918,43 @@
     return iter == charac.descriptors.end() ? 0 : (*iter).handle;
   }
 
-  void OnServiceChangeEvent(const RawAddress& address) {
-    LeAudioDevice* leAudioDevice = leAudioDevices_.FindByAddress(address);
+  void ClearDeviceInformationAndStartSearch(LeAudioDevice* leAudioDevice) {
     if (!leAudioDevice) {
-      DLOG(ERROR) << __func__
-                  << ", skipping unknown leAudioDevice, address: " << address;
+      LOG_WARN("leAudioDevice is null");
       return;
     }
 
-    LOG(INFO) << __func__ << ": address=" << address;
+    LOG_INFO("%s", leAudioDevice->address_.ToString().c_str());
+
+    if (leAudioDevice->known_service_handles_ == false) {
+      LOG_DEBUG("Database already invalidated");
+      return;
+    }
+
     leAudioDevice->known_service_handles_ = false;
     leAudioDevice->csis_member_ = false;
     BtaGattQueue::Clean(leAudioDevice->conn_id_);
     DeregisterNotifications(leAudioDevice);
+
+    if (leAudioDevice->GetConnectionState() == DeviceConnectState::CONNECTED) {
+      leAudioDevice->SetConnectionState(
+          DeviceConnectState::CONNECTED_BY_USER_GETTING_READY);
+    }
+
+    btif_storage_remove_leaudio(leAudioDevice->address_);
+
+    BTA_GATTC_ServiceSearchRequest(
+        leAudioDevice->conn_id_,
+        &le_audio::uuid::kPublishedAudioCapabilityServiceUuid);
+  }
+
+  void OnServiceChangeEvent(const RawAddress& address) {
+    LeAudioDevice* leAudioDevice = leAudioDevices_.FindByAddress(address);
+    if (!leAudioDevice) {
+      LOG_WARN("Skipping unknown leAudioDevice %s", address.ToString().c_str());
+      return;
+    }
+    ClearDeviceInformationAndStartSearch(leAudioDevice);
   }
 
   void OnMtuChanged(uint16_t conn_id, uint16_t mtu) {
@@ -2325,6 +2357,13 @@
       return;
     }
 
+    if (status == GATT_DATABASE_OUT_OF_SYNC) {
+      LOG_INFO("Database out of sync for %s, conn_id: 0x%04x",
+               leAudioDevice->address_.ToString().c_str(), conn_id);
+      ClearDeviceInformationAndStartSearch(leAudioDevice);
+      return;
+    }
+
     if (status == GATT_SUCCESS) {
       LOG(INFO) << __func__
                 << ", successfully registered on ccc: " << loghex(hdl);
@@ -3919,14 +3958,18 @@
                                   void* data) {
     if (!instance) return;
 
+    LeAudioDevice* leAudioDevice =
+        instance->leAudioDevices_.FindByConnId(conn_id);
+
     if (status == GATT_SUCCESS) {
       instance->LeAudioCharValueHandle(conn_id, hdl, len, value);
+    } else if (status == GATT_DATABASE_OUT_OF_SYNC) {
+      instance->ClearDeviceInformationAndStartSearch(leAudioDevice);
+      return;
     }
 
     /* We use data to keep notify connected flag. */
     if (data && !!PTR_TO_INT(data)) {
-      LeAudioDevice* leAudioDevice =
-          instance->leAudioDevices_.FindByConnId(conn_id);
       leAudioDevice->notify_connected_after_read_ = false;
 
       /* Update PACs and ASEs when all is read.*/
diff --git a/system/bta/le_audio/devices.cc b/system/bta/le_audio/devices.cc
index 16c79b5..13cfa78 100644
--- a/system/bta/le_audio/devices.cc
+++ b/system/bta/le_audio/devices.cc
@@ -826,7 +826,9 @@
       codec_spec_conf::kLeAudioLocationNotAllowed;
 
   for (const auto& device : leAudioDevices_) {
-    if (device.expired()) continue;
+    if (device.expired() || (device.lock().get()->GetConnectionState() !=
+                             DeviceConnectState::CONNECTED))
+      continue;
     updated_snk_audio_locations_ |= device.lock().get()->snk_audio_locations_;
     updated_src_audio_locations_ |= device.lock().get()->src_audio_locations_;
   }
@@ -846,7 +848,9 @@
   uint8_t updated_audio_directions = 0x00;
 
   for (const auto& device : leAudioDevices_) {
-    if (device.expired()) continue;
+    if (device.expired() || (device.lock().get()->GetConnectionState() !=
+                             DeviceConnectState::CONNECTED))
+      continue;
     updated_audio_directions |= device.lock().get()->audio_directions_;
   }
 
@@ -863,7 +867,8 @@
 }
 
 bool LeAudioDeviceGroup::IsReleasingOrIdle(void) {
-  return target_state_ == AseState::BTA_LE_AUDIO_ASE_STATE_IDLE;
+  return (target_state_ == AseState::BTA_LE_AUDIO_ASE_STATE_IDLE) ||
+         (current_state_ == AseState::BTA_LE_AUDIO_ASE_STATE_IDLE);
 }
 
 bool LeAudioDeviceGroup::IsGroupStreamReady(void) {
@@ -878,16 +883,12 @@
   return iter == leAudioDevices_.end();
 }
 
-bool LeAudioDeviceGroup::HaveAllActiveDevicesCisDisc(void) {
-  auto iter =
-      std::find_if(leAudioDevices_.begin(), leAudioDevices_.end(), [](auto& d) {
-        if (d.expired())
-          return false;
-        else
-          return !(((d.lock()).get())->HaveAllAsesCisDisc());
-      });
-
-  return iter == leAudioDevices_.end();
+bool LeAudioDeviceGroup::HaveAllCisesDisconnected(void) {
+  for (auto const dev : leAudioDevices_) {
+    if (dev.expired()) continue;
+    if (dev.lock().get()->HaveAnyCisConnected()) return false;
+  }
+  return true;
 }
 
 uint8_t LeAudioDeviceGroup::GetFirstFreeCisId(void) {
@@ -1380,7 +1381,6 @@
   if (audio_location_ulong & codec_spec_conf::kLeAudioLocationLeftSurround)
     return codec_spec_conf::kLeAudioLocationLeftSurround;
 
-  LOG_WARN("Can't find device able to render left audio channel");
   return 0;
 }
 
@@ -1418,15 +1418,15 @@
   if (audio_location_ulong & codec_spec_conf::kLeAudioLocationRightSurround)
     return codec_spec_conf::kLeAudioLocationRightSurround;
 
-  LOG_WARN("Can't find device able to render right audio channel");
   return 0;
 }
 
 uint32_t PickAudioLocation(types::LeAudioConfigurationStrategy strategy,
                            types::AudioLocations device_locations,
                            types::AudioLocations* group_locations) {
-  LOG_DEBUG("strategy: %d, locations: %lx, group locations: %lx", (int)strategy,
-            device_locations.to_ulong(), group_locations->to_ulong());
+  LOG_DEBUG("strategy: %d, locations: 0x%lx, group locations: 0x%lx",
+            (int)strategy, device_locations.to_ulong(),
+            group_locations->to_ulong());
 
   auto is_left_not_yet_assigned =
       !(group_locations->to_ulong() & codec_spec_conf::kLeAudioLocationAnyLeft);
@@ -1435,6 +1435,10 @@
   uint32_t left_device_loc = GetFirstLeft(device_locations);
   uint32_t right_device_loc = GetFirstRight(device_locations);
 
+  if (left_device_loc == 0 && right_device_loc == 0) {
+    LOG_WARN("Can't find device able to render left  and right audio channel");
+  }
+
   switch (strategy) {
     case types::LeAudioConfigurationStrategy::MONO_ONE_CIS_PER_DEVICE:
     case types::LeAudioConfigurationStrategy::STEREO_TWO_CISES_PER_DEVICE:
@@ -1492,6 +1496,12 @@
     return false;
   }
 
+  /* The number_of_already_active_group_ase keeps all the active ases
+   * in other devices in the group.
+   * This function counts active ases only for this device, and we count here
+   * new active ases and already active ases which we want to reuse in the
+   * scenario
+   */
   uint8_t active_ases = *number_of_already_active_group_ase;
   uint8_t max_required_ase_per_dev =
       ent.ase_cnt / ent.device_cnt + (ent.ase_cnt % ent.device_cnt);
@@ -1519,40 +1529,48 @@
     ase->configured_for_context_type = context_type;
     active_ases++;
 
-    if (ase->state == AseState::BTA_LE_AUDIO_ASE_STATE_CODEC_CONFIGURED)
-      ase->reconfigure = true;
+    /* In case of late connect, we could be here for STREAMING ase.
+     * in such case, it is needed to mark ase as known active ase which
+     * is important to validate scenario and is done already few lines above.
+     * Nothing more to do is needed here.
+     */
+    if (ase->state != AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING) {
+      if (ase->state == AseState::BTA_LE_AUDIO_ASE_STATE_CODEC_CONFIGURED)
+        ase->reconfigure = true;
 
-    ase->target_latency = ent.target_latency;
-    ase->codec_id = ent.codec.id;
-    /* TODO: find better way to not use LC3 explicitly */
-    ase->codec_config = std::get<LeAudioLc3Config>(ent.codec.config);
+      ase->target_latency = ent.target_latency;
+      ase->codec_id = ent.codec.id;
+      /* TODO: find better way to not use LC3 explicitly */
+      ase->codec_config = std::get<LeAudioLc3Config>(ent.codec.config);
 
-    /*Let's choose audio channel allocation if not set */
-    ase->codec_config.audio_channel_allocation =
-        PickAudioLocation(strategy, audio_locations, group_audio_locations);
+      /*Let's choose audio channel allocation if not set */
+      ase->codec_config.audio_channel_allocation =
+          PickAudioLocation(strategy, audio_locations, group_audio_locations);
 
-    /* Get default value if no requirement for specific frame blocks per sdu */
-    if (!ase->codec_config.codec_frames_blocks_per_sdu) {
-      ase->codec_config.codec_frames_blocks_per_sdu =
-          GetMaxCodecFramesPerSduFromPac(pac);
-    }
-    ase->max_sdu_size = codec_spec_caps::GetAudioChannelCounts(
-                            *ase->codec_config.audio_channel_allocation) *
-                        *ase->codec_config.octets_per_codec_frame *
-                        *ase->codec_config.codec_frames_blocks_per_sdu;
+      /* Get default value if no requirement for specific frame blocks per sdu
+       */
+      if (!ase->codec_config.codec_frames_blocks_per_sdu) {
+        ase->codec_config.codec_frames_blocks_per_sdu =
+            GetMaxCodecFramesPerSduFromPac(pac);
+      }
+      ase->max_sdu_size = codec_spec_caps::GetAudioChannelCounts(
+                              *ase->codec_config.audio_channel_allocation) *
+                          *ase->codec_config.octets_per_codec_frame *
+                          *ase->codec_config.codec_frames_blocks_per_sdu;
 
-    ase->retrans_nb = ent.qos.retransmission_number;
-    ase->max_transport_latency = ent.qos.max_transport_latency;
+      ase->retrans_nb = ent.qos.retransmission_number;
+      ase->max_transport_latency = ent.qos.max_transport_latency;
 
-    /* Filter multidirectional audio context for each ase direction */
-    auto directional_audio_context =
-        metadata_context_type & GetAvailableContexts(ase->direction);
-    if (directional_audio_context.any()) {
-      ase->metadata = GetMetadata(directional_audio_context, ccid_list);
-    } else {
-      ase->metadata =
-          GetMetadata(AudioContexts(LeAudioContextType::UNSPECIFIED),
-                      std::vector<uint8_t>());
+      /* Filter multidirectional audio context for each ase direction */
+      auto directional_audio_context =
+          metadata_context_type & GetAvailableContexts(ase->direction);
+      if (directional_audio_context.any()) {
+        ase->metadata = GetMetadata(directional_audio_context, ccid_list);
+      } else {
+        ase->metadata =
+            GetMetadata(AudioContexts(LeAudioContextType::UNSPECIFIED),
+                        std::vector<uint8_t>());
+      }
     }
 
     LOG_DEBUG(
@@ -1956,6 +1974,61 @@
 }
 
 LeAudioDeviceGroup::~LeAudioDeviceGroup(void) { this->Cleanup(); }
+
+void LeAudioDeviceGroup::PrintDebugState(void) {
+  auto* active_conf = GetActiveConfiguration();
+  std::stringstream debug_str;
+
+  debug_str << "\n Groupd id: " << group_id_
+            << ", state: " << bluetooth::common::ToString(GetState())
+            << ", target state: "
+            << bluetooth::common::ToString(GetTargetState())
+            << ", cig state: " << bluetooth::common::ToString(cig_state_)
+            << ", \n group available contexts: "
+            << bluetooth::common::ToString(GetAvailableContexts())
+            << ", \n configuration context type: "
+            << bluetooth::common::ToString(GetConfigurationContextType())
+            << ", \n active configuration name: "
+            << (active_conf ? active_conf->name : " not set");
+
+  if (cises_.size() > 0) {
+    LOG_INFO("\n Allocated CISes: %d", static_cast<int>(cises_.size()));
+    for (auto cis : cises_) {
+      LOG_INFO("\n cis id: %d, type: %d, conn_handle %d, addr: %s", cis.id,
+               cis.type, cis.conn_handle, cis.addr.ToString().c_str());
+    }
+  }
+
+  if (GetFirstActiveDevice() != nullptr) {
+    uint32_t sink_delay = 0;
+    uint32_t source_delay = 0;
+    GetPresentationDelay(&sink_delay, le_audio::types::kLeAudioDirectionSink);
+    GetPresentationDelay(&source_delay,
+                         le_audio::types::kLeAudioDirectionSource);
+    auto phy_mtos = GetPhyBitmask(le_audio::types::kLeAudioDirectionSink);
+    auto phy_stom = GetPhyBitmask(le_audio::types::kLeAudioDirectionSource);
+    auto max_transport_latency_mtos = GetMaxTransportLatencyMtos();
+    auto max_transport_latency_stom = GetMaxTransportLatencyStom();
+    auto sdu_mts = GetSduInterval(le_audio::types::kLeAudioDirectionSink);
+    auto sdu_stom = GetSduInterval(le_audio::types::kLeAudioDirectionSource);
+
+    debug_str << "\n resentation_delay for sink (speaker): " << +sink_delay
+              << " us, presentation_delay for source (microphone): "
+              << +source_delay << "us, \n MtoS transport latency:  "
+              << +max_transport_latency_mtos
+              << ", StoM transport latency: " << +max_transport_latency_stom
+              << ", \n MtoS Phy: " << loghex(phy_mtos)
+              << ", MtoS sdu: " << loghex(phy_stom)
+              << " \n MtoS sdu: " << +sdu_mts << ", StoM sdu: " << +sdu_stom;
+  }
+
+  LOG_INFO("%s", debug_str.str().c_str());
+
+  for (const auto& device_iter : leAudioDevices_) {
+    device_iter.lock()->PrintDebugState();
+  }
+}
+
 void LeAudioDeviceGroup::Dump(int fd, int active_group_id) {
   bool is_active = (group_id_ == active_group_id);
   std::stringstream stream;
@@ -2324,13 +2397,15 @@
   return iter == ases_.end();
 }
 
-bool LeAudioDevice::HaveAllAsesCisDisc(void) {
-  auto iter = std::find_if(ases_.begin(), ases_.end(), [](const auto& ase) {
-    return ase.active &&
-           (ase.data_path_state != AudioStreamDataPathState::CIS_ASSIGNED);
-  });
-
-  return iter == ases_.end();
+bool LeAudioDevice::HaveAnyCisConnected(void) {
+  /* Pending and Disconnecting is considered as connected in this function */
+  for (auto const ase : ases_) {
+    if (ase.data_path_state != AudioStreamDataPathState::CIS_ASSIGNED &&
+        ase.data_path_state != AudioStreamDataPathState::IDLE) {
+      return true;
+    }
+  }
+  return false;
 }
 
 bool LeAudioDevice::HasCisId(uint8_t id) {
@@ -2450,31 +2525,75 @@
   supp_contexts_.source = src_contexts;
 }
 
+void LeAudioDevice::PrintDebugState(void) {
+  std::stringstream debug_str;
+
+  debug_str << " address: " << address_ << ", "
+            << bluetooth::common::ToString(connection_state_)
+            << ", conn_id: " << +conn_id_ << ", mtu: " << +mtu_
+            << ", num_of_ase: " << static_cast<int>(ases_.size());
+
+  if (ases_.size() > 0) {
+    debug_str << "\n  == ASEs == ";
+    for (auto& ase : ases_) {
+      debug_str << "\n  id: " << +ase.id << ", active: " << ase.active
+                << ", dir: "
+                << (ase.direction == types::kLeAudioDirectionSink ? "sink"
+                                                                  : "source")
+                << ", cis_id: " << +ase.cis_id
+                << ", cis_handle: " << +ase.cis_conn_hdl << ", state: "
+                << bluetooth::common::ToString(ase.data_path_state)
+                << "\n ase max_latency: " << +ase.max_transport_latency
+                << ", rtn: " << +ase.retrans_nb
+                << ", max_sdu: " << +ase.max_sdu_size
+                << ", target latency: " << +ase.target_latency;
+    }
+  }
+
+  LOG_INFO("%s", debug_str.str().c_str());
+}
+
 void LeAudioDevice::Dump(int fd) {
   uint16_t acl_handle = BTM_GetHCIConnHandle(address_, BT_TRANSPORT_LE);
+  std::string location = "unknown location";
+
+  if (snk_audio_locations_.to_ulong() &
+      codec_spec_conf::kLeAudioLocationAnyLeft) {
+    std::string location_left = "left";
+    location.swap(location_left);
+  } else if (snk_audio_locations_.to_ulong() &
+             codec_spec_conf::kLeAudioLocationAnyRight) {
+    std::string location_right = "right";
+    location.swap(location_right);
+  }
 
   std::stringstream stream;
-  stream << std::boolalpha;
   stream << "\n\taddress: " << address_ << ": " << connection_state_ << ": "
          << (conn_id_ == GATT_INVALID_CONN_ID ? "" : std::to_string(conn_id_))
-         << ", acl_handle: " << std::to_string(acl_handle) << ",\t"
-         << (encrypted_ ? "Encrypted" : "Unecrypted")
+         << ", acl_handle: " << std::to_string(acl_handle) << ", " << location
+         << ",\t" << (encrypted_ ? "Encrypted" : "Unecrypted")
          << ",mtu: " << std::to_string(mtu_)
          << "\n\tnumber of ases_: " << static_cast<int>(ases_.size());
 
   if (ases_.size() > 0) {
-    stream << "\n\t  == ASEs == ";
+    stream << "\n\t== ASEs == \n\t";
+    stream
+        << "id  active dir     cis_id  cis_handle  sdu  latency rtn  state";
     for (auto& ase : ases_) {
-      stream << "\n\t  id: " << static_cast<int>(ase.id)
-             << ",\tactive: " << ase.active << ", dir: "
+      stream << std::setfill('\xA0') << "\n\t" << std::left << std::setw(4)
+             << static_cast<int>(ase.id) << std::left << std::setw(7)
+             << (ase.active ? "true" : "false") << std::left << std::setw(8)
              << (ase.direction == types::kLeAudioDirectionSink ? "sink"
                                                                : "source")
-             << ",\tcis_id: " << static_cast<int>(ase.cis_id)
-             << ",\tcis_handle: " << ase.cis_conn_hdl << ",\tstate: "
+             << std::left << std::setw(8) << static_cast<int>(ase.cis_id)
+             << std::left << std::setw(12) << ase.cis_conn_hdl << std::left
+             << std::setw(5) << ase.max_sdu_size << std::left << std::setw(8)
+             << ase.max_transport_latency << std::left << std::setw(5)
+             << static_cast<int>(ase.retrans_nb) << std::left << std::setw(12)
              << bluetooth::common::ToString(ase.data_path_state);
     }
   }
-  stream << "\n\t  ====";
+  stream << "\n\t====";
 
   dprintf(fd, "%s", stream.str().c_str());
 }
diff --git a/system/bta/le_audio/devices.h b/system/bta/le_audio/devices.h
index 11f31de..d9f4900 100644
--- a/system/bta/le_audio/devices.h
+++ b/system/bta/le_audio/devices.h
@@ -155,7 +155,7 @@
   bool IsReadyToCreateStream(void);
   bool IsReadyToSuspendStream(void);
   bool HaveAllActiveAsesCisEst(void);
-  bool HaveAllAsesCisDisc(void);
+  bool HaveAnyCisConnected(void);
   bool HasCisId(uint8_t id);
   uint8_t GetMatchingBidirectionCisId(const struct types::ase* base_ase);
   const struct types::acs_ac_record* GetCodecConfigurationSupportedPac(
@@ -179,7 +179,10 @@
                                             types::AudioContexts src_cont_val);
   void DeactivateAllAses(void);
   bool ActivateConfiguredAses(types::LeAudioContextType context_type);
+
+  void PrintDebugState(void);
   void Dump(int fd);
+
   void DisconnectAcl(void);
   std::vector<uint8_t> GetMetadata(types::AudioContexts context_type,
                                    const std::vector<uint8_t>& ccid_list);
@@ -283,7 +286,7 @@
   bool IsDeviceInTheGroup(LeAudioDevice* leAudioDevice);
   bool HaveAllActiveDevicesAsesTheSameState(types::AseState state);
   bool IsGroupStreamReady(void);
-  bool HaveAllActiveDevicesCisDisc(void);
+  bool HaveAllCisesDisconnected(void);
   uint8_t GetFirstFreeCisId(void);
   uint8_t GetFirstFreeCisId(types::CisType cis_type);
   void CigGenerateCisIds(types::LeAudioContextType context_type);
@@ -370,6 +373,8 @@
 
   bool IsInTransition(void);
   bool IsReleasingOrIdle(void);
+
+  void PrintDebugState(void);
   void Dump(int fd, int active_group_id);
 
  private:
diff --git a/system/bta/le_audio/le_audio_client_test.cc b/system/bta/le_audio/le_audio_client_test.cc
index 54fe8f7..69978b3 100644
--- a/system/bta/le_audio/le_audio_client_test.cc
+++ b/system/bta/le_audio/le_audio_client_test.cc
@@ -1463,6 +1463,7 @@
                               base::Unretained(LeAudioClient::Get()), address));
 
     SyncOnMainLoop();
+    Mock::VerifyAndClearExpectations(&mock_gatt_interface_);
   }
 
   void DisconnectLeAudio(const RawAddress& address, uint16_t conn_id) {
@@ -4085,4 +4086,61 @@
   LeAudioClient::Get()->SetInCall(false);
 }
 
+TEST_F(UnicastTest, HandleDatabaseOutOfSync) {
+  const RawAddress test_address0 = GetTestAddress(0);
+  int group_id = bluetooth::groups::kGroupUnknown;
+
+  SetSampleDatabaseEarbudsValid(
+      1, test_address0, codec_spec_conf::kLeAudioLocationStereo,
+      codec_spec_conf::kLeAudioLocationStereo, default_channel_cnt,
+      default_channel_cnt, 0x0004, false /*add_csis*/, true /*add_cas*/,
+      true /*add_pacs*/, default_ase_cnt /*add_ascs_cnt*/, 1 /*set_size*/,
+      0 /*rank*/);
+  EXPECT_CALL(mock_audio_hal_client_callbacks_,
+              OnConnectionState(ConnectionState::CONNECTED, test_address0))
+      .Times(1);
+  EXPECT_CALL(mock_audio_hal_client_callbacks_,
+              OnGroupNodeStatus(test_address0, _, GroupNodeStatus::ADDED))
+      .WillOnce(DoAll(SaveArg<1>(&group_id)));
+
+  ConnectLeAudio(test_address0);
+  ASSERT_NE(group_id, bluetooth::groups::kGroupUnknown);
+
+  SyncOnMainLoop();
+  Mock::VerifyAndClearExpectations(&mock_audio_hal_client_callbacks_);
+
+  EXPECT_CALL(mock_audio_hal_client_callbacks_,
+              OnConnectionState(ConnectionState::DISCONNECTED, test_address0))
+      .Times(1);
+  InjectDisconnectedEvent(1, GATT_CONN_TERMINATE_PEER_USER);
+  SyncOnMainLoop();
+  Mock::VerifyAndClearExpectations(&mock_audio_hal_client_callbacks_);
+
+  // default action for WriteDescriptor function call
+  ON_CALL(mock_gatt_queue_, WriteDescriptor(_, _, _, _, _, _))
+      .WillByDefault(Invoke([](uint16_t conn_id, uint16_t handle,
+                               std::vector<uint8_t> value,
+                               tGATT_WRITE_TYPE write_type, GATT_WRITE_OP_CB cb,
+                               void* cb_data) -> void {
+        if (cb)
+          do_in_main_thread(
+              FROM_HERE,
+              base::BindOnce(
+                  [](GATT_WRITE_OP_CB cb, uint16_t conn_id, uint16_t handle,
+                     uint16_t len, uint8_t* value, void* cb_data) {
+                    cb(conn_id, GATT_DATABASE_OUT_OF_SYNC, handle, len, value,
+                       cb_data);
+                  },
+                  cb, conn_id, handle, value.size(), value.data(), cb_data));
+      }));
+
+  ON_CALL(mock_gatt_interface_, ServiceSearchRequest(_, _))
+      .WillByDefault(Return());
+  EXPECT_CALL(mock_gatt_interface_, ServiceSearchRequest(_, _));
+
+  InjectConnectedEvent(test_address0, 1);
+  SyncOnMainLoop();
+  Mock::VerifyAndClearExpectations(&mock_gatt_interface_);
+}
+
 }  // namespace le_audio
diff --git a/system/bta/le_audio/le_audio_types.h b/system/bta/le_audio/le_audio_types.h
index b5935a5..94a722e 100644
--- a/system/bta/le_audio/le_audio_types.h
+++ b/system/bta/le_audio/le_audio_types.h
@@ -627,6 +627,13 @@
         data_path_state(AudioStreamDataPathState::IDLE),
         configured_for_context_type(LeAudioContextType::UNINITIALIZED),
         preferred_phy(0),
+        max_sdu_size(0),
+        retrans_nb(0),
+        max_transport_latency(0),
+        pres_delay_min(0),
+        pres_delay_max(0),
+        preferred_pres_delay_min(0),
+        preferred_pres_delay_max(0),
         state(AseState::BTA_LE_AUDIO_ASE_STATE_IDLE) {}
 
   struct hdl_pair hdls;
diff --git a/system/bta/le_audio/state_machine.cc b/system/bta/le_audio/state_machine.cc
index 09bba6f..9b91b43 100644
--- a/system/bta/le_audio/state_machine.cc
+++ b/system/bta/le_audio/state_machine.cc
@@ -227,8 +227,8 @@
         }
 
         while (leAudioDevice) {
-          PrepareAndSendUpdateMetadata(group, leAudioDevice,
-                                       metadata_context_type, ccid_list);
+          PrepareAndSendUpdateMetadata(leAudioDevice, metadata_context_type,
+                                       ccid_list);
           leAudioDevice = group->GetNextActiveDevice(leAudioDevice);
         }
         break;
@@ -638,7 +638,7 @@
     LOG_DEBUG(
         " device: %s, group connected: %d, all active ase disconnected:: %d",
         leAudioDevice->address_.ToString().c_str(),
-        group->IsAnyDeviceConnected(), group->HaveAllActiveDevicesCisDisc());
+        group->IsAnyDeviceConnected(), group->HaveAllCisesDisconnected());
 
     /* Update the current group audio context availability which could change
      * due to disconnected group member.
@@ -649,8 +649,7 @@
      * If there is active CIS, do nothing here. Just update the available
      * contexts table.
      */
-    if (group->IsAnyDeviceConnected() &&
-        !group->HaveAllActiveDevicesCisDisc()) {
+    if (group->IsAnyDeviceConnected() && !group->HaveAllCisesDisconnected()) {
       if (group->GetState() == AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING) {
         /* We keep streaming but want others to let know user that it might be
          * need to update offloader with new CIS configuration
@@ -666,6 +665,11 @@
      */
     group->SetState(AseState::BTA_LE_AUDIO_ASE_STATE_IDLE);
     group->SetTargetState(AseState::BTA_LE_AUDIO_ASE_STATE_IDLE);
+
+    /* Clear group pending status */
+    group->ClearPendingAvailableContextsChange();
+    group->ClearPendingConfiguration();
+
     if (alarm_is_scheduled(watchdog_)) alarm_cancel(watchdog_);
     ReleaseCisIds(group);
     state_machine_callbacks_->StatusReportCb(group->group_id_,
@@ -677,8 +681,6 @@
       LeAudioDeviceGroup* group, LeAudioDevice* leAudioDevice,
       const bluetooth::hci::iso_manager::cis_establish_cmpl_evt* event)
       override {
-    std::vector<uint8_t> value;
-
     auto ases_pair = leAudioDevice->GetAsesByCisConnHdl(event->cis_conn_hdl);
 
     if (event->status) {
@@ -693,7 +695,7 @@
        * or pending. If CIS is established, this will be handled in disconnected
        * complete event
        */
-      if (group->HaveAllActiveDevicesCisDisc()) {
+      if (group->HaveAllCisesDisconnected()) {
         RemoveCigForGroup(group);
       }
 
@@ -730,11 +732,17 @@
     }
 
     if (!leAudioDevice->HaveAllActiveAsesCisEst()) {
-      /* More cis established event has to come */
+      /* More cis established events has to come */
       return;
     }
 
-    std::vector<uint8_t> ids;
+    if (!leAudioDevice->IsReadyToCreateStream()) {
+      /* Device still remains in ready to create stream state. It means that
+       * more enabling status notifications has to come. This may only happen
+       * for reconnection scenario for bi-directional CIS.
+       */
+      return;
+    }
 
     /* All CISes created. Send start ready for source ASE before we can go
      * to streaming state.
@@ -745,21 +753,8 @@
                "id: %d, cis handle 0x%04x",
                leAudioDevice->address_.ToString().c_str(), event->cig_id,
                event->cis_conn_hdl);
-    do {
-      if (ase->direction == le_audio::types::kLeAudioDirectionSource)
-        ids.push_back(ase->id);
-    } while ((ase = leAudioDevice->GetNextActiveAse(ase)));
 
-    if (ids.size() > 0) {
-      le_audio::client_parser::ascs::PrepareAseCtpAudioReceiverStartReady(
-          ids, value);
-
-      BtaGattQueue::WriteCharacteristic(leAudioDevice->conn_id_,
-                                        leAudioDevice->ctp_hdls_.val_hdl, value,
-                                        GATT_WRITE_NO_RSP, NULL, NULL);
-
-      return;
-    }
+    PrepareAndSendReceiverStartReady(leAudioDevice, ase);
 
     /* Cis establishment may came after setting group state to streaming, e.g.
      * for autonomous scenario when ase is sink */
@@ -837,7 +832,7 @@
          * If there is other device connected and streaming, just leave it as it
          * is, otherwise stop the stream.
          */
-        if (!group->HaveAllActiveDevicesCisDisc()) {
+        if (!group->HaveAllCisesDisconnected()) {
           /* There is ASE streaming for some device. Continue streaming. */
           LOG_WARN(
               "Group member disconnected during streaming. Cis handle 0x%04x",
@@ -846,6 +841,7 @@
         }
 
         LOG_INFO("Lost all members from the group %d", group->group_id_);
+        group->cises_.clear();
         RemoveCigForGroup(group);
 
         group->SetState(AseState::BTA_LE_AUDIO_ASE_STATE_IDLE);
@@ -863,7 +859,7 @@
          */
         if ((group->GetState() ==
              AseState::BTA_LE_AUDIO_ASE_STATE_QOS_CONFIGURED) &&
-            group->HaveAllActiveDevicesCisDisc()) {
+            group->HaveAllCisesDisconnected()) {
           /* No more transition for group */
           alarm_cancel(watchdog_);
 
@@ -873,15 +869,61 @@
         }
         break;
       case AseState::BTA_LE_AUDIO_ASE_STATE_IDLE:
-      case AseState::BTA_LE_AUDIO_ASE_STATE_CODEC_CONFIGURED:
+      case AseState::BTA_LE_AUDIO_ASE_STATE_CODEC_CONFIGURED: {
         /* Those two are used when closing the stream and CIS disconnection is
          * expected */
-        if (group->HaveAllActiveDevicesCisDisc()) {
-          RemoveCigForGroup(group);
+        if (!group->HaveAllCisesDisconnected()) {
+          LOG_DEBUG(
+              "Still waiting for all CISes being disconnected for group:%d",
+              group->group_id_);
           return;
         }
 
-        break;
+        auto current_group_state = group->GetState();
+        LOG_INFO("group %d current state: %s, target state: %s",
+                 group->group_id_,
+                 bluetooth::common::ToString(current_group_state).c_str(),
+                 bluetooth::common::ToString(target_state).c_str());
+        /* It might happen that controller notified about CIS disconnection
+         * later, after ASE state already changed.
+         * In such an event, there is need to notify upper layer about state
+         * from here.
+         */
+        if (alarm_is_scheduled(watchdog_)) {
+          alarm_cancel(watchdog_);
+        }
+
+        if (current_group_state == AseState::BTA_LE_AUDIO_ASE_STATE_IDLE) {
+          LOG_INFO(
+              "Cises disconnected for group %d, we are good in Idle state.",
+              group->group_id_);
+          ReleaseCisIds(group);
+          state_machine_callbacks_->StatusReportCb(group->group_id_,
+                                                   GroupStreamStatus::IDLE);
+        } else if (current_group_state ==
+                   AseState::BTA_LE_AUDIO_ASE_STATE_CODEC_CONFIGURED) {
+          auto reconfig = group->IsPendingConfiguration();
+          LOG_INFO(
+              "Cises disconnected for group: %d, we are good in Configured "
+              "state, reconfig=%d.",
+              group->group_id_, reconfig);
+
+          if (reconfig) {
+            group->ClearPendingConfiguration();
+            state_machine_callbacks_->StatusReportCb(
+                group->group_id_, GroupStreamStatus::CONFIGURED_BY_USER);
+          } else {
+            /* This is Autonomous change if both, target and current state
+             * is CODEC_CONFIGURED
+             */
+            if (target_state == current_group_state) {
+              state_machine_callbacks_->StatusReportCb(
+                  group->group_id_, GroupStreamStatus::CONFIGURED_AUTONOMOUS);
+            }
+          }
+        }
+        RemoveCigForGroup(group);
+      } break;
       default:
         break;
     }
@@ -1275,6 +1317,15 @@
       }
     }
 
+    if ((sdu_interval_mtos == 0 && sdu_interval_stom == 0) ||
+        (max_trans_lat_mtos == le_audio::types::kMaxTransportLatencyMin &&
+         max_trans_lat_stom == le_audio::types::kMaxTransportLatencyMin) ||
+        (max_sdu_size_mtos == 0 && max_sdu_size_stom == 0)) {
+      LOG_ERROR(" Trying to create invalid group");
+      group->PrintDebugState();
+      return false;
+    }
+
     bluetooth::hci::iso_manager::cig_create_params param = {
         .sdu_itv_mtos = sdu_interval_mtos,
         .sdu_itv_stom = sdu_interval_stom,
@@ -1459,9 +1510,21 @@
           PrepareAndSendRelease(leAudioDeviceNext);
         } else {
           /* Last node is in releasing state*/
-          if (alarm_is_scheduled(watchdog_)) alarm_cancel(watchdog_);
-
           group->SetState(AseState::BTA_LE_AUDIO_ASE_STATE_IDLE);
+
+          group->PrintDebugState();
+          /* If all CISes are disconnected, notify upper layer about IDLE state,
+           * otherwise wait for */
+          if (!group->HaveAllCisesDisconnected()) {
+            LOG_WARN(
+                "Not all CISes removed before going to IDLE for group %d, "
+                "waiting...",
+                group->group_id_);
+            group->PrintDebugState();
+            return;
+          }
+
+          if (alarm_is_scheduled(watchdog_)) alarm_cancel(watchdog_);
           ReleaseCisIds(group);
           state_machine_callbacks_->StatusReportCb(group->group_id_,
                                                    GroupStreamStatus::IDLE);
@@ -1642,6 +1705,19 @@
                   AseState::BTA_LE_AUDIO_ASE_STATE_CODEC_CONFIGURED &&
               group->IsPendingConfiguration()) {
             LOG_INFO(" Configured state completed ");
+
+            /* If all CISes are disconnected, notify upper layer about IDLE
+             * state, otherwise wait for */
+            if (!group->HaveAllCisesDisconnected()) {
+              LOG_WARN(
+                  "Not all CISes removed before going to CONFIGURED for group "
+                  "%d, "
+                  "waiting...",
+                  group->group_id_);
+              group->PrintDebugState();
+              return;
+            }
+
             group->ClearPendingConfiguration();
             state_machine_callbacks_->StatusReportCb(
                 group->group_id_, GroupStreamStatus::CONFIGURED_BY_USER);
@@ -1780,7 +1856,6 @@
           PrepareAndSendRelease(leAudioDeviceNext);
         } else {
           /* Last node is in releasing state*/
-          if (alarm_is_scheduled(watchdog_)) alarm_cancel(watchdog_);
 
           group->SetState(AseState::BTA_LE_AUDIO_ASE_STATE_CODEC_CONFIGURED);
           /* Remote device has cache and keep staying in configured state after
@@ -1788,6 +1863,18 @@
            * remote device.
            */
           group->SetTargetState(group->GetState());
+
+          if (!group->HaveAllCisesDisconnected()) {
+            LOG_WARN(
+                "Not all CISes removed before going to IDLE for group %d, "
+                "waiting...",
+                group->group_id_);
+            group->PrintDebugState();
+            return;
+          }
+
+          if (alarm_is_scheduled(watchdog_)) alarm_cancel(watchdog_);
+
           state_machine_callbacks_->StatusReportCb(
               group->group_id_, GroupStreamStatus::CONFIGURED_AUTONOMOUS);
         }
@@ -1876,7 +1963,7 @@
 
         group->SetState(AseState::BTA_LE_AUDIO_ASE_STATE_QOS_CONFIGURED);
 
-        if (!group->HaveAllActiveDevicesCisDisc()) return;
+        if (!group->HaveAllCisesDisconnected()) return;
 
         if (group->GetTargetState() ==
             AseState::BTA_LE_AUDIO_ASE_STATE_QOS_CONFIGURED) {
@@ -1972,6 +2059,9 @@
                                LeAudioDevice* leAudioDevice) {
     std::vector<struct le_audio::client_parser::ascs::ctp_qos_conf> confs;
 
+    bool validate_transport_latency = false;
+    bool validate_max_sdu_size = false;
+
     for (struct ase* ase = leAudioDevice->GetFirstActiveAse(); ase != nullptr;
          ase = leAudioDevice->GetNextActiveAse(ase)) {
       LOG_DEBUG("device: %s, ase_id: %d, cis_id: %d, ase state: %s",
@@ -1988,14 +2078,16 @@
       conf.max_sdu = ase->max_sdu_size;
       conf.retrans_nb = ase->retrans_nb;
       if (!group->GetPresentationDelay(&conf.pres_delay, ase->direction)) {
-        LOG(ERROR) << __func__ << ", inconsistent presentation delay for group";
+        LOG_ERROR("inconsistent presentation delay for group");
+        group->PrintDebugState();
         StopStream(group);
         return;
       }
 
       conf.sdu_interval = group->GetSduInterval(ase->direction);
       if (!conf.sdu_interval) {
-        LOG(ERROR) << __func__ << ", unsupported SDU interval for group";
+        LOG_ERROR("unsupported SDU interval for group");
+        group->PrintDebugState();
         StopStream(group);
         return;
       }
@@ -2005,64 +2097,105 @@
       } else {
         conf.max_transport_latency = group->GetMaxTransportLatencyStom();
       }
+
+      if (conf.max_transport_latency >
+          le_audio::types::kMaxTransportLatencyMin) {
+        validate_transport_latency = true;
+      }
+
+      if (conf.max_sdu > 0) {
+        validate_max_sdu_size = true;
+      }
       confs.push_back(conf);
     }
 
-    LOG_ASSERT(confs.size() > 0)
-        << __func__ << " shouldn't be called without an active ASE";
+    if (confs.size() == 0 || !validate_transport_latency ||
+        !validate_max_sdu_size) {
+      LOG_ERROR("Invalid configuration or latency or sdu size");
+      group->PrintDebugState();
+      StopStream(group);
+      return;
+    }
 
     std::vector<uint8_t> value;
     le_audio::client_parser::ascs::PrepareAseCtpConfigQos(confs, value);
-
     BtaGattQueue::WriteCharacteristic(leAudioDevice->conn_id_,
                                       leAudioDevice->ctp_hdls_.val_hdl, value,
                                       GATT_WRITE_NO_RSP, NULL, NULL);
   }
 
-  void PrepareAndSendUpdateMetadata(LeAudioDeviceGroup* group,
-                                    LeAudioDevice* leAudioDevice,
+  void PrepareAndSendUpdateMetadata(LeAudioDevice* leAudioDevice,
                                     le_audio::types::AudioContexts context_type,
                                     const std::vector<uint8_t>& ccid_list) {
     std::vector<struct le_audio::client_parser::ascs::ctp_update_metadata>
         confs;
 
-    for (; leAudioDevice;
-         leAudioDevice = group->GetNextActiveDevice(leAudioDevice)) {
-      if (!leAudioDevice->IsMetadataChanged(context_type, ccid_list)) continue;
+    if (!leAudioDevice->IsMetadataChanged(context_type, ccid_list)) return;
 
-      /* Request server to update ASEs with new metadata */
-      for (struct ase* ase = leAudioDevice->GetFirstActiveAse(); ase != nullptr;
-           ase = leAudioDevice->GetNextActiveAse(ase)) {
-        LOG_DEBUG("device: %s, ase_id: %d, cis_id: %d, ase state: %s",
-                  leAudioDevice->address_.ToString().c_str(), ase->id,
-                  ase->cis_id, ToString(ase->state).c_str());
+    /* Request server to update ASEs with new metadata */
+    for (struct ase* ase = leAudioDevice->GetFirstActiveAse(); ase != nullptr;
+         ase = leAudioDevice->GetNextActiveAse(ase)) {
+      LOG_DEBUG("device: %s, ase_id: %d, cis_id: %d, ase state: %s",
+                leAudioDevice->address_.ToString().c_str(), ase->id,
+                ase->cis_id, ToString(ase->state).c_str());
 
-        /* Filter multidirectional audio context for each ase direction */
-        auto directional_audio_context =
-            context_type & leAudioDevice->GetAvailableContexts(ase->direction);
-        if (directional_audio_context.any()) {
-          ase->metadata =
-              leAudioDevice->GetMetadata(directional_audio_context, ccid_list);
-        } else {
-          ase->metadata = leAudioDevice->GetMetadata(
-              AudioContexts(LeAudioContextType::UNSPECIFIED),
-              std::vector<uint8_t>());
-        }
-
-        struct le_audio::client_parser::ascs::ctp_update_metadata conf;
-
-        conf.ase_id = ase->id;
-        conf.metadata = ase->metadata;
-
-        confs.push_back(conf);
+      if (ase->state != AseState::BTA_LE_AUDIO_ASE_STATE_ENABLING &&
+          ase->state != AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING) {
+        /* This might happen when update metadata happens on late connect */
+        LOG_DEBUG(
+            "Metadata for ase_id %d cannot be updated due to invalid ase state "
+            "- see log above",
+            ase->id);
+        continue;
       }
 
+      /* Filter multidirectional audio context for each ase direction */
+      auto directional_audio_context =
+          context_type & leAudioDevice->GetAvailableContexts(ase->direction);
+      if (directional_audio_context.any()) {
+        ase->metadata =
+            leAudioDevice->GetMetadata(directional_audio_context, ccid_list);
+      } else {
+        ase->metadata = leAudioDevice->GetMetadata(
+            AudioContexts(LeAudioContextType::UNSPECIFIED),
+            std::vector<uint8_t>());
+      }
+
+      struct le_audio::client_parser::ascs::ctp_update_metadata conf;
+
+      conf.ase_id = ase->id;
+      conf.metadata = ase->metadata;
+
+      confs.push_back(conf);
+    }
+
+    if (confs.size() != 0) {
       std::vector<uint8_t> value;
       le_audio::client_parser::ascs::PrepareAseCtpUpdateMetadata(confs, value);
 
       BtaGattQueue::WriteCharacteristic(leAudioDevice->conn_id_,
                                         leAudioDevice->ctp_hdls_.val_hdl, value,
                                         GATT_WRITE_NO_RSP, NULL, NULL);
+    }
+  }
+
+  void PrepareAndSendReceiverStartReady(LeAudioDevice* leAudioDevice,
+                                        struct ase* ase) {
+    std::vector<uint8_t> ids;
+    std::vector<uint8_t> value;
+
+    do {
+      if (ase->direction == le_audio::types::kLeAudioDirectionSource)
+        ids.push_back(ase->id);
+    } while ((ase = leAudioDevice->GetNextActiveAse(ase)));
+
+    if (ids.size() > 0) {
+      le_audio::client_parser::ascs::PrepareAseCtpAudioReceiverStartReady(
+          ids, value);
+
+      BtaGattQueue::WriteCharacteristic(leAudioDevice->conn_id_,
+                                        leAudioDevice->ctp_hdls_.val_hdl, value,
+                                        GATT_WRITE_NO_RSP, NULL, NULL);
 
       return;
     }
@@ -2076,17 +2209,40 @@
       return;
     }
 
-    if (group->GetState() == AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING) {
-      /* We are here because of the reconnection of the single device. */
-      ase->state = AseState::BTA_LE_AUDIO_ASE_STATE_ENABLING;
-      CisCreateForDevice(leAudioDevice);
-      return;
-    }
-
     switch (ase->state) {
       case AseState::BTA_LE_AUDIO_ASE_STATE_QOS_CONFIGURED:
         ase->state = AseState::BTA_LE_AUDIO_ASE_STATE_ENABLING;
 
+        if (group->GetState() == AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING) {
+          if (ase->data_path_state < AudioStreamDataPathState::CIS_PENDING) {
+            /* We are here because of the reconnection of the single device. */
+            CisCreateForDevice(leAudioDevice);
+          }
+
+          if (!leAudioDevice->HaveAllActiveAsesCisEst()) {
+            /* More cis established events has to come */
+            return;
+          }
+
+          if (!leAudioDevice->IsReadyToCreateStream()) {
+            /* Device still remains in ready to create stream state. It means
+             * that more enabling status notifications has to come.
+             */
+            return;
+          }
+
+          /* All CISes created. Send start ready for source ASE before we can go
+           * to streaming state.
+           */
+          struct ase* ase = leAudioDevice->GetFirstActiveAse();
+          ASSERT_LOG(ase != nullptr,
+                     "shouldn't be called without an active ASE, device %s",
+                     leAudioDevice->address_.ToString().c_str());
+          PrepareAndSendReceiverStartReady(leAudioDevice, ase);
+
+          return;
+        }
+
         if (leAudioDevice->IsReadyToCreateStream())
           ProcessGroupEnable(group, leAudioDevice);
 
diff --git a/system/bta/le_audio/state_machine_test.cc b/system/bta/le_audio/state_machine_test.cc
index 2d0fc44..dc5cfe9 100644
--- a/system/bta/le_audio/state_machine_test.cc
+++ b/system/bta/le_audio/state_machine_test.cc
@@ -449,6 +449,11 @@
   }
 
   void TearDown() override {
+    /* Clear the alarm on tear down in case test case ends when the
+     * alarm is scheduled
+     */
+    alarm_cancel(nullptr);
+
     iso_manager_->Stop();
     mock_iso_manager_ = nullptr;
     codec_manager_->Stop();
@@ -865,7 +870,7 @@
             codec_configured_state_params.framing =
                 ascs::kAseParamFramingUnframedSupported;
             codec_configured_state_params.preferred_retrans_nb = 0x04;
-            codec_configured_state_params.max_transport_latency = 0x0005;
+            codec_configured_state_params.max_transport_latency = 0x0010;
             codec_configured_state_params.pres_delay_min = 0xABABAB;
             codec_configured_state_params.pres_delay_max = 0xCDCDCD;
             codec_configured_state_params.preferred_pres_delay_min =
@@ -1238,6 +1243,9 @@
   // Check if group has transitioned to a proper state
   ASSERT_EQ(group->GetState(),
             types::AseState::BTA_LE_AUDIO_ASE_STATE_CODEC_CONFIGURED);
+
+  /* Cancel is called when group goes to streaming. */
+  ASSERT_EQ(0, mock_function_count_map["alarm_cancel"]);
 }
 
 TEST_F(StateMachineTest, testConfigureCodecMulti) {
@@ -1280,6 +1288,9 @@
   // Check if group has transitioned to a proper state
   ASSERT_EQ(group->GetState(),
             types::AseState::BTA_LE_AUDIO_ASE_STATE_CODEC_CONFIGURED);
+
+  /* Cancel is called when group goes to streaming. */
+  ASSERT_EQ(0, mock_function_count_map["alarm_cancel"]);
 }
 
 TEST_F(StateMachineTest, testConfigureQosSingle) {
@@ -1322,6 +1333,8 @@
   // Check if group has transitioned to a proper state
   ASSERT_EQ(group->GetState(),
             types::AseState::BTA_LE_AUDIO_ASE_STATE_QOS_CONFIGURED);
+
+  ASSERT_EQ(0, mock_function_count_map["alarm_cancel"]);
 }
 
 TEST_F(StateMachineTest, testConfigureQosSingleRecoverCig) {
@@ -1368,6 +1381,7 @@
   // Check if group has transitioned to a proper state
   ASSERT_EQ(group->GetState(),
             types::AseState::BTA_LE_AUDIO_ASE_STATE_QOS_CONFIGURED);
+  ASSERT_EQ(0, mock_function_count_map["alarm_cancel"]);
 }
 
 TEST_F(StateMachineTest, testConfigureQosMultiple) {
@@ -1413,6 +1427,7 @@
   // Check if group has transitioned to a proper state
   ASSERT_EQ(group->GetState(),
             types::AseState::BTA_LE_AUDIO_ASE_STATE_QOS_CONFIGURED);
+  ASSERT_EQ(0, mock_function_count_map["alarm_cancel"]);
 }
 
 TEST_F(StateMachineTest, testStreamSingle) {
@@ -1463,6 +1478,7 @@
   // Check if group has transitioned to a proper state
   ASSERT_EQ(group->GetState(),
             types::AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING);
+  ASSERT_EQ(1, mock_function_count_map["alarm_cancel"]);
 }
 
 TEST_F(StateMachineTest, testStreamSkipEnablingSink) {
@@ -1511,6 +1527,8 @@
   // Check if group has transitioned to a proper state
   ASSERT_EQ(group->GetState(),
             types::AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING);
+
+  ASSERT_EQ(1, mock_function_count_map["alarm_cancel"]);
 }
 
 TEST_F(StateMachineTest, testStreamSkipEnablingSinkSource) {
@@ -1562,6 +1580,7 @@
   // Check if group has transitioned to a proper state
   ASSERT_EQ(group->GetState(),
             types::AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING);
+  ASSERT_EQ(1, mock_function_count_map["alarm_cancel"]);
 }
 
 TEST_F(StateMachineTest, testStreamMultipleConversational) {
@@ -1615,6 +1634,7 @@
   // Check if group has transitioned to a proper state
   ASSERT_EQ(group->GetState(),
             types::AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING);
+  ASSERT_EQ(1, mock_function_count_map["alarm_cancel"]);
 }
 
 TEST_F(StateMachineTest, testStreamMultiple) {
@@ -1667,6 +1687,7 @@
   // Check if group has transitioned to a proper state
   ASSERT_EQ(group->GetState(),
             types::AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING);
+  ASSERT_EQ(1, mock_function_count_map["alarm_cancel"]);
 }
 
 TEST_F(StateMachineTest, testUpdateMetadataMultiple) {
@@ -1722,6 +1743,9 @@
   ASSERT_EQ(group->GetState(),
             types::AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING);
 
+  ASSERT_EQ(1, mock_function_count_map["alarm_cancel"]);
+  mock_function_count_map["alarm_cancel"] = 0;
+
   // Make sure all devices get the metadata update
   leAudioDevice = group->GetFirstDevice();
   expected_devices_written = 0;
@@ -1741,6 +1765,9 @@
   ASSERT_TRUE(LeAudioGroupStateMachine::Get()->StartStream(
       group, static_cast<LeAudioContextType>(context_type),
       metadata_context_type));
+
+  /* This is just update metadata - watchdog is not used */
+  ASSERT_EQ(0, mock_function_count_map["alarm_cancel"]);
 }
 
 TEST_F(StateMachineTest, testDisableSingle) {
@@ -1781,6 +1808,11 @@
 
   InjectInitialIdleNotification(group);
 
+  EXPECT_CALL(
+      mock_callbacks_,
+      StatusReportCb(leaudio_group_id,
+                     bluetooth::le_audio::GroupStreamStatus::STREAMING));
+
   // Start the configuration and stream Media content
   LeAudioGroupStateMachine::Get()->StartStream(
       group, static_cast<LeAudioContextType>(context_type),
@@ -1790,6 +1822,10 @@
   ASSERT_EQ(group->GetState(),
             types::AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING);
 
+  testing::Mock::VerifyAndClearExpectations(&mock_callbacks_);
+  ASSERT_EQ(1, mock_function_count_map["alarm_cancel"]);
+  mock_function_count_map["alarm_cancel"] = 0;
+
   // Validate GroupStreamStatus
   EXPECT_CALL(
       mock_callbacks_,
@@ -1806,6 +1842,9 @@
   // Check if group has transition to a proper state
   ASSERT_EQ(group->GetState(),
             types::AseState::BTA_LE_AUDIO_ASE_STATE_QOS_CONFIGURED);
+
+  testing::Mock::VerifyAndClearExpectations(&mock_callbacks_);
+  ASSERT_EQ(1, mock_function_count_map["alarm_cancel"]);
 }
 
 TEST_F(StateMachineTest, testDisableMultiple) {
@@ -1857,6 +1896,8 @@
   // Check if group has transitioned to a proper state
   ASSERT_EQ(group->GetState(),
             types::AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING);
+  ASSERT_EQ(1, mock_function_count_map["alarm_cancel"]);
+  mock_function_count_map["alarm_cancel"] = 0;
 
   // Validate GroupStreamStatus
   EXPECT_CALL(
@@ -1874,6 +1915,8 @@
   // Check if group has transitioned to a proper state
   ASSERT_EQ(group->GetState(),
             types::AseState::BTA_LE_AUDIO_ASE_STATE_QOS_CONFIGURED);
+  testing::Mock::VerifyAndClearExpectations(&mock_callbacks_);
+  ASSERT_EQ(1, mock_function_count_map["alarm_cancel"]);
 }
 
 TEST_F(StateMachineTest, testDisableBidirectional) {
@@ -1958,6 +2001,19 @@
   ASSERT_EQ(group->GetState(),
             types::AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING);
 
+  ASSERT_EQ(1, mock_function_count_map["alarm_cancel"]);
+  mock_function_count_map["alarm_cancel"] = 0;
+
+  // Validate GroupStreamStatus
+  EXPECT_CALL(
+      mock_callbacks_,
+      StatusReportCb(leaudio_group_id,
+                     bluetooth::le_audio::GroupStreamStatus::SUSPENDING));
+  EXPECT_CALL(
+      mock_callbacks_,
+      StatusReportCb(leaudio_group_id,
+                     bluetooth::le_audio::GroupStreamStatus::SUSPENDED));
+
   // Suspend the stream
   LeAudioGroupStateMachine::Get()->SuspendStream(group);
 
@@ -1966,6 +2022,9 @@
             types::AseState::BTA_LE_AUDIO_ASE_STATE_QOS_CONFIGURED);
   ASSERT_EQ(removed_bidirectional, true);
   ASSERT_EQ(removed_unidirectional, true);
+
+  testing::Mock::VerifyAndClearExpectations(&mock_callbacks_);
+  ASSERT_EQ(1, mock_function_count_map["alarm_cancel"]);
 }
 
 TEST_F(StateMachineTest, testReleaseSingle) {
@@ -2012,7 +2071,8 @@
   // Check if group has transitioned to a proper state
   ASSERT_EQ(group->GetState(),
             types::AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING);
-
+  ASSERT_EQ(1, mock_function_count_map["alarm_cancel"]);
+  mock_function_count_map["alarm_cancel"] = 0;
   // Validate GroupStreamStatus
   EXPECT_CALL(
       mock_callbacks_,
@@ -2027,6 +2087,7 @@
 
   // Check if group has transitioned to a proper state
   ASSERT_EQ(group->GetState(), types::AseState::BTA_LE_AUDIO_ASE_STATE_IDLE);
+  ASSERT_EQ(1, mock_function_count_map["alarm_cancel"]);
 }
 
 TEST_F(StateMachineTest, testReleaseCachingSingle) {
@@ -2090,12 +2151,17 @@
   ASSERT_EQ(group->GetState(),
             types::AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING);
 
+  ASSERT_EQ(1, mock_function_count_map["alarm_cancel"]);
+  mock_function_count_map["alarm_cancel"] = 0;
+
   // Stop the stream
   LeAudioGroupStateMachine::Get()->StopStream(group);
 
   // Check if group has transitioned to a proper state
   ASSERT_EQ(group->GetState(),
             types::AseState::BTA_LE_AUDIO_ASE_STATE_CODEC_CONFIGURED);
+
+  ASSERT_EQ(1, mock_function_count_map["alarm_cancel"]);
 }
 
 TEST_F(StateMachineTest,
@@ -2167,6 +2233,9 @@
   ASSERT_EQ(group->GetState(),
             types::AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING);
 
+  ASSERT_EQ(1, mock_function_count_map["alarm_cancel"]);
+  mock_function_count_map["alarm_cancel"] = 0;
+
   // Stop the stream
   LeAudioGroupStateMachine::Get()->StopStream(group);
 
@@ -2174,6 +2243,9 @@
   ASSERT_EQ(group->GetState(),
             types::AseState::BTA_LE_AUDIO_ASE_STATE_CODEC_CONFIGURED);
 
+  ASSERT_EQ(1, mock_function_count_map["alarm_cancel"]);
+  mock_function_count_map["alarm_cancel"] = 0;
+
   // Start the configuration and stream Media content
   LeAudioGroupStateMachine::Get()->StartStream(
       group, static_cast<LeAudioContextType>(context_type),
@@ -2182,6 +2254,9 @@
   // Check if group has transitioned to a proper state
   ASSERT_EQ(group->GetState(),
             types::AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING);
+
+  ASSERT_EQ(1, mock_function_count_map["alarm_cancel"]);
+  mock_function_count_map["alarm_cancel"] = 0;
 }
 
 TEST_F(StateMachineTest,
@@ -2267,6 +2342,9 @@
   ASSERT_EQ(group->GetState(),
             types::AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING);
 
+  ASSERT_EQ(1, mock_function_count_map["alarm_cancel"]);
+  mock_function_count_map["alarm_cancel"] = 0;
+
   // Stop the stream
   LeAudioGroupStateMachine::Get()->StopStream(group);
 
@@ -2274,6 +2352,9 @@
   ASSERT_EQ(group->GetState(),
             types::AseState::BTA_LE_AUDIO_ASE_STATE_CODEC_CONFIGURED);
 
+  ASSERT_EQ(1, mock_function_count_map["alarm_cancel"]);
+  mock_function_count_map["alarm_cancel"] = 0;
+
   // Start the configuration and stream Media content
   context_type = kContextTypeMedia;
   LeAudioGroupStateMachine::Get()->StartStream(
@@ -2283,6 +2364,7 @@
   // Check if group has transitioned to a proper state
   ASSERT_EQ(group->GetState(),
             types::AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING);
+  ASSERT_EQ(1, mock_function_count_map["alarm_cancel"]);
 }
 
 TEST_F(StateMachineTest, testReleaseMultiple) {
@@ -2332,6 +2414,9 @@
   ASSERT_EQ(group->GetState(),
             types::AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING);
 
+  ASSERT_EQ(1, mock_function_count_map["alarm_cancel"]);
+  mock_function_count_map["alarm_cancel"] = 0;
+
   // Validate GroupStreamStatus
   EXPECT_CALL(
       mock_callbacks_,
@@ -2346,6 +2431,7 @@
 
   // Check if group has transitioned to a proper state
   ASSERT_EQ(group->GetState(), types::AseState::BTA_LE_AUDIO_ASE_STATE_IDLE);
+  ASSERT_EQ(1, mock_function_count_map["alarm_cancel"]);
 }
 
 TEST_F(StateMachineTest, testReleaseBidirectional) {
@@ -2393,11 +2479,16 @@
   ASSERT_EQ(group->GetState(),
             types::AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING);
 
+  ASSERT_EQ(1, mock_function_count_map["alarm_cancel"]);
+  mock_function_count_map["alarm_cancel"] = 0;
+
   // Stop the stream
   LeAudioGroupStateMachine::Get()->StopStream(group);
 
   // Check if group has transitioned to a proper state
   ASSERT_EQ(group->GetState(), types::AseState::BTA_LE_AUDIO_ASE_STATE_IDLE);
+  ASSERT_EQ(1, mock_function_count_map["alarm_cancel"]);
+  mock_function_count_map["alarm_cancel"] = 0;
 }
 
 TEST_F(StateMachineTest, testDisableAndReleaseBidirectional) {
@@ -2558,6 +2649,9 @@
   /* Single disconnect as it is bidirectional Cis*/
   EXPECT_CALL(*mock_iso_manager_, DisconnectCis(_, _)).Times(2);
 
+  ASSERT_EQ(1, mock_function_count_map["alarm_cancel"]);
+  mock_function_count_map["alarm_cancel"] = 0;
+
   for (auto* device = group->GetFirstDevice(); device != nullptr;
        device = group->GetNextDevice(device)) {
     for (auto& ase : device->ases_) {
@@ -2581,6 +2675,8 @@
       ASSERT_EQ(ase.state, types::AseState::BTA_LE_AUDIO_ASE_STATE_IDLE);
     }
   }
+
+  ASSERT_EQ(0, mock_function_count_map["alarm_cancel"]);
 }
 
 TEST_F(StateMachineTest, testAseAutonomousRelease2Devices) {
@@ -2885,15 +2981,15 @@
                                   uint8_t reason) {
   bluetooth::hci::iso_manager::cis_disconnected_evt event;
 
-  auto* ase = leAudioDevice->GetFirstActiveAse();
-  while (ase) {
-    event.reason = reason;
-    event.cig_id = group->group_id_;
-    event.cis_conn_hdl = ase->cis_conn_hdl;
-    LeAudioGroupStateMachine::Get()->ProcessHciNotifCisDisconnected(
-        group, leAudioDevice, &event);
-
-    ase = leAudioDevice->GetNextActiveAse(ase);
+  for (auto const ase : leAudioDevice->ases_) {
+    if (ase.data_path_state != types::AudioStreamDataPathState::CIS_ASSIGNED &&
+        ase.data_path_state != types::AudioStreamDataPathState::IDLE) {
+      event.reason = reason;
+      event.cig_id = group->group_id_;
+      event.cis_conn_hdl = ase.cis_conn_hdl;
+      LeAudioGroupStateMachine::Get()->ProcessHciNotifCisDisconnected(
+          group, leAudioDevice, &event);
+    }
   }
 }
 
@@ -2917,6 +3013,7 @@
 
   auto* leAudioDevice = group->GetFirstDevice();
   LeAudioDevice* lastDevice;
+  LeAudioDevice* fistDevice = leAudioDevice;
 
   auto expected_devices_written = 0;
   while (leAudioDevice) {
@@ -2996,6 +3093,123 @@
   auto ccids = ltv.Find(le_audio::types::kLeAudioMetadataTypeCcidList);
   ASSERT_TRUE(ccids.has_value());
   ASSERT_NE(std::find(ccids->begin(), ccids->end(), media_ccid), ccids->end());
+
+  /* Verify that ASE of first device are still good*/
+  auto ase = fistDevice->GetFirstActiveAse();
+  ASSERT_NE(ase->max_transport_latency, 0);
+  ASSERT_NE(ase->retrans_nb, 0);
+}
+
+TEST_F(StateMachineTest, testAttachDeviceToTheConversationalStream) {
+  const auto context_type = kContextTypeConversational;
+  const auto leaudio_group_id = 6;
+  const auto num_devices = 2;
+
+  ContentControlIdKeeper::GetInstance()->SetCcid(call_context, call_ccid);
+
+  // Prepare multiple fake connected devices in a group
+  auto* group =
+      PrepareSingleTestDeviceGroup(leaudio_group_id, context_type, num_devices);
+  ASSERT_EQ(group->Size(), num_devices);
+
+  PrepareConfigureCodecHandler(group);
+  PrepareConfigureQosHandler(group);
+  PrepareEnableHandler(group);
+  PrepareReceiverStartReady(group);
+  PrepareDisableHandler(group);
+  PrepareReleaseHandler(group);
+
+  auto* leAudioDevice = group->GetFirstDevice();
+  LeAudioDevice* lastDevice;
+  LeAudioDevice* fistDevice = leAudioDevice;
+
+  auto expected_devices_written = 0;
+  while (leAudioDevice) {
+    /* Three Writes:
+     * 1: Codec Config
+     * 2: Codec QoS
+     * 3: Enabling
+     */
+    lastDevice = leAudioDevice;
+    EXPECT_CALL(gatt_queue,
+                WriteCharacteristic(leAudioDevice->conn_id_,
+                                    leAudioDevice->ctp_hdls_.val_hdl, _,
+                                    GATT_WRITE_NO_RSP, _, _))
+        .Times(AtLeast(3));
+    expected_devices_written++;
+    leAudioDevice = group->GetNextDevice(leAudioDevice);
+  }
+  ASSERT_EQ(expected_devices_written, num_devices);
+
+  EXPECT_CALL(*mock_iso_manager_, CreateCig(_, _)).Times(1);
+  EXPECT_CALL(*mock_iso_manager_, EstablishCis(_)).Times(1);
+  EXPECT_CALL(*mock_iso_manager_, SetupIsoDataPath(_, _)).Times(4);
+
+  InjectInitialIdleNotification(group);
+
+  // Start the configuration and stream Conversational content
+  LeAudioGroupStateMachine::Get()->StartStream(
+      group, static_cast<LeAudioContextType>(context_type),
+      types::AudioContexts(context_type));
+
+  // Check if group has transitioned to a proper state
+  ASSERT_EQ(group->GetState(),
+            types::AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING);
+  testing::Mock::VerifyAndClearExpectations(&mock_iso_manager_);
+
+  // Inject CIS and ACL disconnection of first device
+  InjectCisDisconnected(group, lastDevice, HCI_ERR_CONNECTION_TOUT);
+  InjectAclDisconnected(group, lastDevice);
+
+  // Check if group keeps streaming
+  ASSERT_EQ(group->GetState(),
+            types::AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING);
+
+  lastDevice->conn_id_ = 3;
+  group->UpdateAudioContextTypeAvailability();
+
+  // Make sure ASE with disconnected CIS are not left in STREAMING
+  ASSERT_EQ(lastDevice->GetFirstAseWithState(
+                ::le_audio::types::kLeAudioDirectionSink,
+                types::AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING),
+            nullptr);
+  ASSERT_EQ(lastDevice->GetFirstAseWithState(
+                ::le_audio::types::kLeAudioDirectionSource,
+                types::AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING),
+            nullptr);
+
+  EXPECT_CALL(gatt_queue, WriteCharacteristic(lastDevice->conn_id_,
+                                              lastDevice->ctp_hdls_.val_hdl, _,
+                                              GATT_WRITE_NO_RSP, _, _))
+      .Times(AtLeast(3));
+
+  EXPECT_CALL(*mock_iso_manager_, EstablishCis(_)).Times(1);
+  EXPECT_CALL(*mock_iso_manager_, SetupIsoDataPath(_, _)).Times(2);
+  LeAudioGroupStateMachine::Get()->AttachToStream(group, lastDevice);
+
+  // Check if group keeps streaming
+  ASSERT_EQ(group->GetState(),
+            types::AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING);
+
+  // Verify that the joining device receives the right CCID list
+  auto lastMeta = lastDevice->GetFirstActiveAse()->metadata;
+  bool parsedOk = false;
+  auto ltv = le_audio::types::LeAudioLtvMap::Parse(lastMeta.data(),
+                                                   lastMeta.size(), parsedOk);
+  ASSERT_TRUE(parsedOk);
+
+  auto ccids = ltv.Find(le_audio::types::kLeAudioMetadataTypeCcidList);
+  ASSERT_TRUE(ccids.has_value());
+  ASSERT_NE(std::find(ccids->begin(), ccids->end(), call_ccid), ccids->end());
+
+  /* Verify that ASE of first device are still good*/
+  auto ase = fistDevice->GetFirstActiveAse();
+  ASSERT_NE(ase->max_transport_latency, 0);
+  ASSERT_NE(ase->retrans_nb, 0);
+
+  // Make sure ASEs with reconnected CIS are in STREAMING state
+  ASSERT_TRUE(lastDevice->HaveAllActiveAsesSameState(
+      types::AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING));
 }
 
 TEST_F(StateMachineTest, StartStreamAfterConfigure) {
@@ -3114,6 +3328,9 @@
 
   testing::Mock::VerifyAndClearExpectations(&mock_callbacks_);
 
+  ASSERT_EQ(1, mock_function_count_map["alarm_cancel"]);
+  mock_function_count_map["alarm_cancel"] = 0;
+
   // Validate GroupStreamStatus
   EXPECT_CALL(
       mock_callbacks_,
@@ -3130,6 +3347,9 @@
 
   testing::Mock::VerifyAndClearExpectations(&mock_callbacks_);
 
+  ASSERT_EQ(1, mock_function_count_map["alarm_cancel"]);
+  mock_function_count_map["alarm_cancel"] = 0;
+
   // Restart stream
   EXPECT_CALL(
       mock_callbacks_,
@@ -3141,6 +3361,7 @@
       group, context_type, types::AudioContexts(context_type));
 
   testing::Mock::VerifyAndClearExpectations(&mock_callbacks_);
+  ASSERT_EQ(1, mock_function_count_map["alarm_cancel"]);
 }
 
 TEST_F(StateMachineTest, BoundedHeadphonesConversationalToMediaChannelCount_2) {
@@ -3298,6 +3519,9 @@
 
   testing::Mock::VerifyAndClearExpectations(&mock_callbacks_);
 
+  ASSERT_EQ(1, mock_function_count_map["alarm_cancel"]);
+  mock_function_count_map["alarm_cancel"] = 0;
+
   // Validate GroupStreamStatus
   EXPECT_CALL(
       mock_callbacks_,
@@ -3313,6 +3537,8 @@
   LeAudioGroupStateMachine::Get()->StopStream(group);
 
   testing::Mock::VerifyAndClearExpectations(&mock_callbacks_);
+  ASSERT_EQ(1, mock_function_count_map["alarm_cancel"]);
+  mock_function_count_map["alarm_cancel"] = 0;
 
   // Restart stream
   EXPECT_CALL(
@@ -3325,6 +3551,401 @@
       group, new_context_type, types::AudioContexts(new_context_type));
 
   testing::Mock::VerifyAndClearExpectations(&mock_callbacks_);
+  ASSERT_EQ(1, mock_function_count_map["alarm_cancel"]);
 }
+
+TEST_F(StateMachineTest, lateCisDisconnectedEvent_ConfiguredByUser) {
+  const auto context_type = kContextTypeMedia;
+  const auto leaudio_group_id = 6;
+  const auto num_devices = 1;
+
+  ContentControlIdKeeper::GetInstance()->SetCcid(media_context, media_ccid);
+
+  // Prepare multiple fake connected devices in a group
+  auto* group =
+      PrepareSingleTestDeviceGroup(leaudio_group_id, context_type, num_devices);
+  ASSERT_EQ(group->Size(), num_devices);
+
+  PrepareConfigureCodecHandler(group, 0, true);
+  PrepareConfigureQosHandler(group);
+  PrepareEnableHandler(group);
+  PrepareDisableHandler(group);
+  PrepareReleaseHandler(group);
+
+  auto* leAudioDevice = group->GetFirstDevice();
+  auto expected_devices_written = 0;
+
+  /* Three Writes:
+   * 1: Codec Config
+   * 2: Codec QoS
+   * 3: Enabling
+   */
+  EXPECT_CALL(gatt_queue, WriteCharacteristic(leAudioDevice->conn_id_,
+                                              leAudioDevice->ctp_hdls_.val_hdl,
+                                              _, GATT_WRITE_NO_RSP, _, _))
+      .Times(AtLeast(3));
+  expected_devices_written++;
+
+  ASSERT_EQ(expected_devices_written, num_devices);
+
+  EXPECT_CALL(*mock_iso_manager_, CreateCig(_, _)).Times(1);
+  EXPECT_CALL(*mock_iso_manager_, EstablishCis(_)).Times(1);
+  EXPECT_CALL(*mock_iso_manager_, SetupIsoDataPath(_, _)).Times(2);
+
+  InjectInitialIdleNotification(group);
+
+  // Start the configuration and stream Media content
+  LeAudioGroupStateMachine::Get()->StartStream(
+      group, static_cast<LeAudioContextType>(context_type),
+      types::AudioContexts(context_type));
+
+  // Check if group has transitioned to a proper state
+  ASSERT_EQ(group->GetState(),
+            types::AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING);
+  testing::Mock::VerifyAndClearExpectations(&mock_iso_manager_);
+
+  ASSERT_EQ(1, mock_function_count_map["alarm_cancel"]);
+  mock_function_count_map["alarm_cancel"] = 0;
+
+  /* Prepare DisconnectCis mock to not symulate CisDisconnection */
+  ON_CALL(*mock_iso_manager_, DisconnectCis).WillByDefault(Return());
+
+  /* Do reconfiguration */
+  group->SetPendingConfiguration();
+
+  // Validate GroupStreamStatus
+  EXPECT_CALL(
+      mock_callbacks_,
+      StatusReportCb(leaudio_group_id,
+                     bluetooth::le_audio::GroupStreamStatus::RELEASING));
+
+  EXPECT_CALL(mock_callbacks_,
+              StatusReportCb(
+                  leaudio_group_id,
+                  bluetooth::le_audio::GroupStreamStatus::CONFIGURED_BY_USER))
+      .Times(0);
+  LeAudioGroupStateMachine::Get()->StopStream(group);
+
+  testing::Mock::VerifyAndClearExpectations(&mock_callbacks_);
+
+  ASSERT_EQ(0, mock_function_count_map["alarm_cancel"]);
+
+  EXPECT_CALL(mock_callbacks_,
+              StatusReportCb(
+                  leaudio_group_id,
+                  bluetooth::le_audio::GroupStreamStatus::CONFIGURED_BY_USER));
+
+  // Inject CIS and ACL disconnection of first device
+  InjectCisDisconnected(group, leAudioDevice, HCI_ERR_CONN_CAUSE_LOCAL_HOST);
+  testing::Mock::VerifyAndClearExpectations(&mock_callbacks_);
+  ASSERT_EQ(1, mock_function_count_map["alarm_cancel"]);
+}
+
+TEST_F(StateMachineTest, lateCisDisconnectedEvent_AutonomousConfigured) {
+  const auto context_type = kContextTypeMedia;
+  const auto leaudio_group_id = 6;
+  const auto num_devices = 1;
+
+  ContentControlIdKeeper::GetInstance()->SetCcid(media_context, media_ccid);
+
+  // Prepare multiple fake connected devices in a group
+  auto* group =
+      PrepareSingleTestDeviceGroup(leaudio_group_id, context_type, num_devices);
+  ASSERT_EQ(group->Size(), num_devices);
+
+  PrepareConfigureCodecHandler(group, 0, true);
+  PrepareConfigureQosHandler(group);
+  PrepareEnableHandler(group);
+  PrepareDisableHandler(group);
+  PrepareReleaseHandler(group);
+
+  auto* leAudioDevice = group->GetFirstDevice();
+  auto expected_devices_written = 0;
+
+  /* Three Writes:
+   * 1: Codec Config
+   * 2: Codec QoS
+   * 3: Enabling
+   */
+  EXPECT_CALL(gatt_queue, WriteCharacteristic(leAudioDevice->conn_id_,
+                                              leAudioDevice->ctp_hdls_.val_hdl,
+                                              _, GATT_WRITE_NO_RSP, _, _))
+      .Times(AtLeast(3));
+  expected_devices_written++;
+
+  ASSERT_EQ(expected_devices_written, num_devices);
+
+  EXPECT_CALL(*mock_iso_manager_, CreateCig(_, _)).Times(1);
+  EXPECT_CALL(*mock_iso_manager_, EstablishCis(_)).Times(1);
+  EXPECT_CALL(*mock_iso_manager_, SetupIsoDataPath(_, _)).Times(2);
+
+  InjectInitialIdleNotification(group);
+
+  // Start the configuration and stream Media content
+  LeAudioGroupStateMachine::Get()->StartStream(
+      group, static_cast<LeAudioContextType>(context_type),
+      types::AudioContexts(context_type));
+
+  // Check if group has transitioned to a proper state
+  ASSERT_EQ(group->GetState(),
+            types::AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING);
+  testing::Mock::VerifyAndClearExpectations(&mock_iso_manager_);
+
+  ASSERT_EQ(1, mock_function_count_map["alarm_cancel"]);
+  mock_function_count_map["alarm_cancel"] = 0;
+
+  /* Prepare DisconnectCis mock to not symulate CisDisconnection */
+  ON_CALL(*mock_iso_manager_, DisconnectCis).WillByDefault(Return());
+
+  // Validate GroupStreamStatus
+  EXPECT_CALL(
+      mock_callbacks_,
+      StatusReportCb(leaudio_group_id,
+                     bluetooth::le_audio::GroupStreamStatus::RELEASING));
+
+  EXPECT_CALL(
+      mock_callbacks_,
+      StatusReportCb(
+          leaudio_group_id,
+          bluetooth::le_audio::GroupStreamStatus::CONFIGURED_AUTONOMOUS))
+      .Times(0);
+
+  // Stop the stream
+  LeAudioGroupStateMachine::Get()->StopStream(group);
+
+  // Check if group has transitioned to a proper state
+  ASSERT_EQ(group->GetState(),
+            types::AseState::BTA_LE_AUDIO_ASE_STATE_CODEC_CONFIGURED);
+
+  testing::Mock::VerifyAndClearExpectations(&mock_callbacks_);
+
+  ASSERT_EQ(0, mock_function_count_map["alarm_cancel"]);
+
+  EXPECT_CALL(
+      mock_callbacks_,
+      StatusReportCb(
+          leaudio_group_id,
+          bluetooth::le_audio::GroupStreamStatus::CONFIGURED_AUTONOMOUS));
+
+  // Inject CIS and ACL disconnection of first device
+  InjectCisDisconnected(group, leAudioDevice, HCI_ERR_CONN_CAUSE_LOCAL_HOST);
+  testing::Mock::VerifyAndClearExpectations(&mock_callbacks_);
+  ASSERT_EQ(1, mock_function_count_map["alarm_cancel"]);
+}
+
+TEST_F(StateMachineTest, lateCisDisconnectedEvent_Idle) {
+  const auto context_type = kContextTypeMedia;
+  const auto leaudio_group_id = 6;
+  const auto num_devices = 1;
+
+  ContentControlIdKeeper::GetInstance()->SetCcid(media_context, media_ccid);
+
+  // Prepare multiple fake connected devices in a group
+  auto* group =
+      PrepareSingleTestDeviceGroup(leaudio_group_id, context_type, num_devices);
+  ASSERT_EQ(group->Size(), num_devices);
+
+  PrepareConfigureCodecHandler(group);
+  PrepareConfigureQosHandler(group);
+  PrepareEnableHandler(group);
+  PrepareDisableHandler(group);
+  PrepareReleaseHandler(group);
+
+  auto* leAudioDevice = group->GetFirstDevice();
+  auto expected_devices_written = 0;
+
+  /* Three Writes:
+   * 1: Codec Config
+   * 2: Codec QoS
+   * 3: Enabling
+   */
+  EXPECT_CALL(gatt_queue, WriteCharacteristic(leAudioDevice->conn_id_,
+                                              leAudioDevice->ctp_hdls_.val_hdl,
+                                              _, GATT_WRITE_NO_RSP, _, _))
+      .Times(AtLeast(3));
+  expected_devices_written++;
+
+  ASSERT_EQ(expected_devices_written, num_devices);
+
+  EXPECT_CALL(*mock_iso_manager_, CreateCig(_, _)).Times(1);
+  EXPECT_CALL(*mock_iso_manager_, EstablishCis(_)).Times(1);
+  EXPECT_CALL(*mock_iso_manager_, SetupIsoDataPath(_, _)).Times(2);
+
+  InjectInitialIdleNotification(group);
+
+  // Start the configuration and stream Media content
+  LeAudioGroupStateMachine::Get()->StartStream(
+      group, static_cast<LeAudioContextType>(context_type),
+      types::AudioContexts(context_type));
+
+  // Check if group has transitioned to a proper state
+  ASSERT_EQ(group->GetState(),
+            types::AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING);
+  testing::Mock::VerifyAndClearExpectations(&mock_iso_manager_);
+
+  ASSERT_EQ(1, mock_function_count_map["alarm_cancel"]);
+  mock_function_count_map["alarm_cancel"] = 0;
+  /* Prepare DisconnectCis mock to not symulate CisDisconnection */
+  ON_CALL(*mock_iso_manager_, DisconnectCis).WillByDefault(Return());
+
+  // Validate GroupStreamStatus
+  EXPECT_CALL(
+      mock_callbacks_,
+      StatusReportCb(leaudio_group_id,
+                     bluetooth::le_audio::GroupStreamStatus::RELEASING));
+
+  EXPECT_CALL(mock_callbacks_,
+              StatusReportCb(leaudio_group_id,
+                             bluetooth::le_audio::GroupStreamStatus::IDLE))
+      .Times(0);
+
+  // Stop the stream
+  LeAudioGroupStateMachine::Get()->StopStream(group);
+
+  // Check if group has transitioned to a proper state
+  ASSERT_EQ(group->GetState(), types::AseState::BTA_LE_AUDIO_ASE_STATE_IDLE);
+  ASSERT_EQ(0, mock_function_count_map["alarm_cancel"]);
+
+  testing::Mock::VerifyAndClearExpectations(&mock_callbacks_);
+
+  EXPECT_CALL(mock_callbacks_,
+              StatusReportCb(leaudio_group_id,
+                             bluetooth::le_audio::GroupStreamStatus::IDLE));
+
+  // Inject CIS and ACL disconnection of first device
+  InjectCisDisconnected(group, leAudioDevice, HCI_ERR_CONN_CAUSE_LOCAL_HOST);
+  testing::Mock::VerifyAndClearExpectations(&mock_callbacks_);
+  ASSERT_EQ(1, mock_function_count_map["alarm_cancel"]);
+}
+
+TEST_F(StateMachineTest, StreamReconfigureAfterCisLostTwoDevices) {
+  auto context_type = kContextTypeConversational;
+  const auto leaudio_group_id = 4;
+  const auto num_devices = 2;
+
+  // Prepare multiple fake connected devices in a group
+  auto* group = PrepareSingleTestDeviceGroup(
+      leaudio_group_id, context_type, num_devices,
+      kContextTypeConversational | kContextTypeMedia);
+  ASSERT_EQ(group->Size(), num_devices);
+
+  PrepareConfigureCodecHandler(group);
+  PrepareConfigureQosHandler(group);
+  PrepareEnableHandler(group);
+  PrepareReceiverStartReady(group);
+
+  /* Prepare DisconnectCis mock to not symulate CisDisconnection */
+  ON_CALL(*mock_iso_manager_, DisconnectCis).WillByDefault(Return());
+
+  EXPECT_CALL(*mock_iso_manager_, CreateCig(_, _)).Times(2);
+  EXPECT_CALL(*mock_iso_manager_, EstablishCis(_)).Times(2);
+  EXPECT_CALL(*mock_iso_manager_, SetupIsoDataPath(_, _)).Times(6);
+  EXPECT_CALL(*mock_iso_manager_, RemoveIsoDataPath(_, _)).Times(0);
+  EXPECT_CALL(*mock_iso_manager_, DisconnectCis(_, _)).Times(0);
+  EXPECT_CALL(*mock_iso_manager_, RemoveCig(_, _)).Times(1);
+
+  InjectInitialIdleNotification(group);
+
+  auto* leAudioDevice = group->GetFirstDevice();
+  auto expected_devices_written = 0;
+  while (leAudioDevice) {
+    EXPECT_CALL(gatt_queue,
+                WriteCharacteristic(leAudioDevice->conn_id_,
+                                    leAudioDevice->ctp_hdls_.val_hdl, _,
+                                    GATT_WRITE_NO_RSP, _, _))
+        .Times(3);
+    expected_devices_written++;
+    leAudioDevice = group->GetNextDevice(leAudioDevice);
+  }
+  ASSERT_EQ(expected_devices_written, num_devices);
+
+  // Validate GroupStreamStatus
+  EXPECT_CALL(
+      mock_callbacks_,
+      StatusReportCb(leaudio_group_id,
+                     bluetooth::le_audio::GroupStreamStatus::STREAMING));
+
+  // Start the configuration and stream Media content
+  context_type = kContextTypeMedia;
+  ASSERT_TRUE(LeAudioGroupStateMachine::Get()->StartStream(
+      group, static_cast<LeAudioContextType>(context_type),
+      types::AudioContexts(context_type)));
+
+  // Check if group has transitioned to a proper state
+  ASSERT_EQ(group->GetState(),
+            types::AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING);
+  ASSERT_EQ(1, mock_function_count_map["alarm_cancel"]);
+  testing::Mock::VerifyAndClearExpectations(&mock_iso_manager_);
+  testing::Mock::VerifyAndClearExpectations(&gatt_queue);
+  testing::Mock::VerifyAndClearExpectations(&mock_callbacks_);
+
+  // Device disconnects due to timeout of CIS
+  leAudioDevice = group->GetFirstDevice();
+  while (leAudioDevice) {
+    InjectCisDisconnected(group, leAudioDevice, HCI_ERR_CONN_CAUSE_LOCAL_HOST);
+    // Disconnect device
+    LeAudioGroupStateMachine::Get()->ProcessHciNotifAclDisconnected(
+        group, leAudioDevice);
+
+    leAudioDevice = group->GetNextDevice(leAudioDevice);
+  }
+
+  LOG(INFO) << "GK A1";
+  group->ReloadAudioLocations();
+  group->ReloadAudioDirections();
+  group->UpdateAudioContextTypeAvailability();
+
+  // Start conversational scenario
+  leAudioDevice = group->GetFirstDevice();
+  int device_cnt = num_devices;
+  while (leAudioDevice) {
+    LOG(INFO) << "GK A11";
+    leAudioDevice->conn_id_ = device_cnt--;
+    leAudioDevice->SetConnectionState(DeviceConnectState::CONNECTED);
+    leAudioDevice = group->GetNextDevice(leAudioDevice);
+  }
+
+  LOG(INFO) << "GK A2";
+  InjectInitialIdleNotification(group);
+
+  group->ReloadAudioLocations();
+  group->ReloadAudioDirections();
+  group->UpdateAudioContextTypeAvailability(kContextTypeConversational |
+                                            kContextTypeMedia);
+
+  leAudioDevice = group->GetFirstDevice();
+  expected_devices_written = 0;
+  while (leAudioDevice) {
+    EXPECT_CALL(gatt_queue,
+                WriteCharacteristic(leAudioDevice->conn_id_,
+                                    leAudioDevice->ctp_hdls_.val_hdl, _,
+                                    GATT_WRITE_NO_RSP, _, _))
+        .Times(4);
+    expected_devices_written++;
+    leAudioDevice = group->GetNextDevice(leAudioDevice);
+  }
+  ASSERT_EQ(expected_devices_written, num_devices);
+
+  // Validate GroupStreamStatus
+  EXPECT_CALL(
+      mock_callbacks_,
+      StatusReportCb(leaudio_group_id,
+                     bluetooth::le_audio::GroupStreamStatus::STREAMING));
+
+  // Start the configuration and stream Conversational content
+  context_type = kContextTypeConversational;
+  ASSERT_TRUE(LeAudioGroupStateMachine::Get()->StartStream(
+      group, static_cast<LeAudioContextType>(context_type),
+      types::AudioContexts(context_type)));
+
+  // Check if group has transitioned to a proper state
+  ASSERT_EQ(group->GetState(),
+            types::AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING);
+  ASSERT_EQ(2, mock_function_count_map["alarm_cancel"]);
+  testing::Mock::VerifyAndClearExpectations(&mock_iso_manager_);
+  testing::Mock::VerifyAndClearExpectations(&gatt_queue);
+  testing::Mock::VerifyAndClearExpectations(&mock_callbacks_);
+}
+
 }  // namespace internal
 }  // namespace le_audio
diff --git a/system/bta/test/bta_hf_client_add_record_test.cc b/system/bta/test/bta_hf_client_add_record_test.cc
index 5e50cc0..63b1b03 100644
--- a/system/bta/test/bta_hf_client_add_record_test.cc
+++ b/system/bta/test/bta_hf_client_add_record_test.cc
@@ -63,11 +63,11 @@
 };
 
 TEST_F(BtaHfClientAddRecordTest, test_hf_client_add_record) {
-  tBTA_HF_CLIENT_FEAT features = BTIF_HF_CLIENT_FEATURES;
+  tBTA_HF_CLIENT_FEAT features = get_default_hf_client_features();
   uint32_t sdp_handle = 0;
   uint8_t scn = 0;
 
   bta_hf_client_add_record("Handsfree", scn, features, sdp_handle);
-  ASSERT_EQ(gVersion, BTA_HFP_VERSION);
+  ASSERT_EQ(gVersion, get_default_hfp_version());
 }
 
diff --git a/system/bta/test/common/btif_storage_mock.cc b/system/bta/test/common/btif_storage_mock.cc
index f5d8a6f..9440394 100644
--- a/system/bta/test/common/btif_storage_mock.cc
+++ b/system/bta/test/common/btif_storage_mock.cc
@@ -110,4 +110,9 @@
                                                 uint8_t active_preset) {
   LOG_ASSERT(btif_storage_interface) << "Mock storage module not set!";
   btif_storage_interface->SetLeaudioHasActivePreset(address, active_preset);
+}
+
+void btif_storage_remove_leaudio_has(const RawAddress& address) {
+  LOG_ASSERT(btif_storage_interface) << "Mock storage module not set!";
+  btif_storage_interface->RemoveLeaudioHas(address);
 }
\ No newline at end of file
diff --git a/system/bta/test/common/btif_storage_mock.h b/system/bta/test/common/btif_storage_mock.h
index d7e0013..77fb91d 100644
--- a/system/bta/test/common/btif_storage_mock.h
+++ b/system/bta/test/common/btif_storage_mock.h
@@ -50,6 +50,7 @@
   virtual bool GetLeaudioHasPresets(const RawAddress& address,
                                     std::vector<uint8_t>& presets_bin,
                                     uint8_t& active_preset) = 0;
+  virtual void RemoveLeaudioHas(const RawAddress& address) = 0;
 
   virtual ~BtifStorageInterface() = default;
 };
@@ -88,6 +89,8 @@
               (const RawAddress& address, uint8_t features), (override));
   MOCK_METHOD((void), SetLeaudioHasActivePreset,
               (const RawAddress& address, uint8_t active_preset), (override));
+  MOCK_METHOD((void), RemoveLeaudioHas, (const RawAddress& address),
+              (override));
 };
 
 /**
diff --git a/system/bta/vc/device.cc b/system/bta/vc/device.cc
index 4eb15f5..33f509b 100644
--- a/system/bta/vc/device.cc
+++ b/system/bta/vc/device.cc
@@ -29,26 +29,27 @@
 
 using namespace bluetooth::vc::internal;
 
+void VolumeControlDevice::DeregisterNotifications(tGATT_IF gatt_if) {
+  if (volume_state_handle != 0)
+    BTA_GATTC_DeregisterForNotifications(gatt_if, address, volume_state_handle);
+
+  if (volume_flags_handle != 0)
+    BTA_GATTC_DeregisterForNotifications(gatt_if, address, volume_flags_handle);
+
+  for (const VolumeOffset& of : audio_offsets.volume_offsets) {
+    BTA_GATTC_DeregisterForNotifications(gatt_if, address,
+                                         of.audio_descr_handle);
+    BTA_GATTC_DeregisterForNotifications(gatt_if, address,
+                                         of.audio_location_handle);
+    BTA_GATTC_DeregisterForNotifications(gatt_if, address, of.state_handle);
+  }
+}
+
 void VolumeControlDevice::Disconnect(tGATT_IF gatt_if) {
   LOG(INFO) << __func__ << ": " << this->ToString();
 
   if (IsConnected()) {
-    if (volume_state_handle != 0)
-      BTA_GATTC_DeregisterForNotifications(gatt_if, address,
-                                           volume_state_handle);
-
-    if (volume_flags_handle != 0)
-      BTA_GATTC_DeregisterForNotifications(gatt_if, address,
-                                           volume_flags_handle);
-
-    for (const VolumeOffset& of : audio_offsets.volume_offsets) {
-      BTA_GATTC_DeregisterForNotifications(gatt_if, address,
-                                           of.audio_descr_handle);
-      BTA_GATTC_DeregisterForNotifications(gatt_if, address,
-                                           of.audio_location_handle);
-      BTA_GATTC_DeregisterForNotifications(gatt_if, address, of.state_handle);
-    }
-
+    DeregisterNotifications(gatt_if);
     BtaGattQueue::Clean(connection_id);
     BTA_GATTC_Close(connection_id);
     connection_id = GATT_INVALID_CONN_ID;
diff --git a/system/bta/vc/devices.h b/system/bta/vc/devices.h
index d5126f5..321a530 100644
--- a/system/bta/vc/devices.h
+++ b/system/bta/vc/devices.h
@@ -109,6 +109,8 @@
 
   void Disconnect(tGATT_IF gatt_if);
 
+  void DeregisterNotifications(tGATT_IF gatt_if);
+
   bool UpdateHandles(void);
 
   void ResetHandles(void);
diff --git a/system/bta/vc/vc.cc b/system/bta/vc/vc.cc
index fde14a9..196816e 100644
--- a/system/bta/vc/vc.cc
+++ b/system/bta/vc/vc.cc
@@ -102,6 +102,21 @@
       volume_control_devices_.Add(address, true);
     } else {
       device->connecting_actively = true;
+
+      if (device->IsConnected()) {
+        LOG(WARNING) << __func__ << ": address=" << address
+                     << ", connection_id=" << device->connection_id
+                     << " already connected.";
+
+        if (device->IsReady()) {
+          callbacks_->OnConnectionState(ConnectionState::CONNECTED,
+                                        device->address);
+        } else {
+          OnGattConnected(GATT_SUCCESS, device->connection_id, gatt_if_,
+                          device->address, BT_TRANSPORT_LE, GATT_MAX_MTU_SIZE);
+        }
+        return;
+      }
     }
 
     BTA_GATTC_Open(gatt_if_, address, BTM_BLE_DIRECT_CONNECTION, false);
@@ -182,6 +197,29 @@
     }
   }
 
+  void ClearDeviceInformationAndStartSearch(VolumeControlDevice* device) {
+    if (!device) {
+      LOG_ERROR("Device is null");
+      return;
+    }
+
+    LOG_INFO(": address=%s", device->address.ToString().c_str());
+    if (device->service_changed_rcvd) {
+      LOG_INFO("Device already is waiting for new services");
+      return;
+    }
+
+    std::vector<RawAddress> devices = {device->address};
+    device->DeregisterNotifications(gatt_if_);
+
+    RemovePendingVolumeControlOperations(devices,
+                                         bluetooth::groups::kGroupUnknown);
+    device->first_connection = true;
+    device->service_changed_rcvd = true;
+    BtaGattQueue::Clean(device->connection_id);
+    BTA_GATTC_ServiceSearchRequest(device->connection_id, &kVolumeControlUuid);
+  }
+
   void OnServiceChangeEvent(const RawAddress& address) {
     VolumeControlDevice* device =
         volume_control_devices_.FindByAddress(address);
@@ -189,10 +227,8 @@
       LOG(ERROR) << __func__ << "Skipping unknown device " << address;
       return;
     }
-    LOG(INFO) << __func__ << ": address=" << address;
-    device->first_connection = true;
-    device->service_changed_rcvd = true;
-    BtaGattQueue::Clean(device->connection_id);
+
+    ClearDeviceInformationAndStartSearch(device);
   }
 
   void OnServiceDiscDoneEvent(const RawAddress& address) {
@@ -249,7 +285,12 @@
     }
 
     if (status != GATT_SUCCESS) {
-      LOG(INFO) << __func__ << ": status=" << static_cast<int>(status);
+      LOG_INFO(": status=0x%02x", static_cast<int>(status));
+      if (status == GATT_DATABASE_OUT_OF_SYNC) {
+        LOG_INFO("Database out of sync for %s",
+                 device->address.ToString().c_str());
+        ClearDeviceInformationAndStartSearch(device);
+      }
       return;
     }
 
@@ -547,10 +588,15 @@
     }
 
     if (status != GATT_SUCCESS) {
-      LOG(ERROR) << __func__
-                 << "Failed to register for notification: " << loghex(handle)
-                 << " status: " << status;
-      device_cleanup_helper(device, true);
+      if (status == GATT_DATABASE_OUT_OF_SYNC) {
+        LOG_INFO("Database out of sync for %s, conn_id: 0x%04x",
+                 device->address.ToString().c_str(), connection_id);
+        ClearDeviceInformationAndStartSearch(device);
+      } else {
+        LOG_ERROR("Failed to register for notification: 0x%04x, status 0x%02x",
+                  handle, status);
+        device_cleanup_helper(device, true);
+      }
       return;
     }
 
@@ -597,13 +643,22 @@
       return;
     }
 
+    if (!device->IsConnected()) {
+      LOG(ERROR) << __func__
+                 << " Skipping disconnect of the already disconnected device, "
+                    "connection_id="
+                 << loghex(connection_id);
+      return;
+    }
+
     // If we get here, it means, device has not been exlicitly disconnected.
     bool device_ready = device->IsReady();
 
     device_cleanup_helper(device, device->connecting_actively);
 
     if (device_ready) {
-      volume_control_devices_.Add(remote_bda, true);
+      device->first_connection = true;
+      device->connecting_actively = true;
 
       /* Add device into BG connection to accept remote initiated connection */
       BTA_GATTC_Open(gatt_if_, remote_bda, BTM_BLE_BKG_CONNECT_ALLOW_LIST,
@@ -683,6 +738,12 @@
 
     /* In case of error, remove device from the tracking operation list */
     RemoveDeviceFromOperationList(device->address, PTR_TO_INT(data));
+
+    if (status == GATT_DATABASE_OUT_OF_SYNC) {
+      LOG_INFO("Database out of sync for %s",
+               device->address.ToString().c_str());
+      ClearDeviceInformationAndStartSearch(device);
+    }
   }
 
   static void operation_callback(void* data) {
@@ -1031,7 +1092,6 @@
     if (notify)
       callbacks_->OnConnectionState(ConnectionState::DISCONNECTED,
                                     device->address);
-    volume_control_devices_.Remove(device->address);
   }
 
   void devices_control_point_helper(std::vector<RawAddress>& devices,
diff --git a/system/bta/vc/vc_test.cc b/system/bta/vc/vc_test.cc
index 159eb80..ca1bb02 100644
--- a/system/bta/vc/vc_test.cc
+++ b/system/bta/vc/vc_test.cc
@@ -547,6 +547,55 @@
   TestAppUnregister();
 }
 
+TEST_F(VolumeControlTest, test_reconnect_after_interrupted_discovery) {
+  const RawAddress test_address = GetTestAddress(0);
+
+  // Initial connection - no callback calls yet as we want to disconnect in the
+  // middle
+  SetSampleDatabaseVOCS(1);
+  TestAppRegister();
+  TestConnect(test_address);
+  EXPECT_CALL(*callbacks,
+              OnConnectionState(ConnectionState::CONNECTED, test_address))
+      .Times(0);
+  EXPECT_CALL(*callbacks, OnDeviceAvailable(test_address, 2)).Times(0);
+  GetConnectedEvent(test_address, 1);
+  Mock::VerifyAndClearExpectations(callbacks.get());
+
+  // Remote disconnects in the middle of the service discovery
+  EXPECT_CALL(*callbacks,
+              OnConnectionState(ConnectionState::DISCONNECTED, test_address));
+  GetDisconnectedEvent(test_address, 1);
+  Mock::VerifyAndClearExpectations(callbacks.get());
+
+  // This time let the service discovery pass
+  ON_CALL(gatt_interface, ServiceSearchRequest(_, _))
+      .WillByDefault(Invoke(
+          [&](uint16_t conn_id, const bluetooth::Uuid* p_srvc_uuid) -> void {
+            if (*p_srvc_uuid == kVolumeControlUuid)
+              GetSearchCompleteEvent(conn_id);
+          }));
+
+  // Remote is being connected by another GATT client
+  EXPECT_CALL(*callbacks,
+              OnConnectionState(ConnectionState::CONNECTED, test_address));
+  EXPECT_CALL(*callbacks, OnDeviceAvailable(test_address, 2));
+  GetConnectedEvent(test_address, 1);
+  Mock::VerifyAndClearExpectations(callbacks.get());
+
+  // Request connect when the remote was already connected by another service
+  EXPECT_CALL(*callbacks, OnDeviceAvailable(test_address, 2)).Times(0);
+  EXPECT_CALL(*callbacks,
+              OnConnectionState(ConnectionState::CONNECTED, test_address));
+  VolumeControl::Get()->Connect(test_address);
+  // The GetConnectedEvent(test_address, 1); should not be triggered here, since
+  // GATT implementation will not send this event for the already connected
+  // device
+  Mock::VerifyAndClearExpectations(callbacks.get());
+
+  TestAppUnregister();
+}
+
 TEST_F(VolumeControlTest, test_add_from_storage) {
   TestAppRegister();
   TestAddFromStorage(GetTestAddress(0), true);
@@ -754,6 +803,46 @@
   TestAppUnregister();
 }
 
+TEST_F(VolumeControlTest, test_read_vcs_database_out_of_sync) {
+  const RawAddress test_address = GetTestAddress(0);
+  EXPECT_CALL(*callbacks, OnVolumeStateChanged(test_address, _, _, false));
+  std::vector<uint16_t> handles({0x0021});
+  uint16_t conn_id = 1;
+
+  SetSampleDatabase(conn_id);
+  TestAppRegister();
+  TestConnect(test_address);
+  GetConnectedEvent(test_address, conn_id);
+
+  EXPECT_CALL(gatt_queue, ReadCharacteristic(conn_id, _, _, _))
+      .WillRepeatedly(DoDefault());
+  for (auto const& handle : handles) {
+    EXPECT_CALL(gatt_queue, ReadCharacteristic(conn_id, handle, _, _))
+        .WillOnce(DoDefault());
+  }
+  GetSearchCompleteEvent(conn_id);
+
+  /* Simulate database change on the remote side. */
+  ON_CALL(gatt_queue, WriteCharacteristic(_, _, _, _, _, _))
+      .WillByDefault(
+          Invoke([this](uint16_t conn_id, uint16_t handle,
+                        std::vector<uint8_t> value, tGATT_WRITE_TYPE write_type,
+                        GATT_WRITE_OP_CB cb, void* cb_data) {
+            auto* svc = gatt::FindService(services_map[conn_id], handle);
+            if (svc == nullptr) return;
+
+            tGATT_STATUS status = GATT_DATABASE_OUT_OF_SYNC;
+            if (cb)
+              cb(conn_id, status, handle, value.size(), value.data(), cb_data);
+          }));
+
+  ON_CALL(gatt_interface, ServiceSearchRequest(_, _)).WillByDefault(Return());
+  EXPECT_CALL(gatt_interface, ServiceSearchRequest(_, _));
+  VolumeControl::Get()->SetVolume(test_address, 15);
+  Mock::VerifyAndClearExpectations(&gatt_interface);
+  TestAppUnregister();
+}
+
 class VolumeControlCallbackTest : public VolumeControlTest {
  protected:
   const RawAddress test_address = GetTestAddress(0);
diff --git a/system/btif/Android.bp b/system/btif/Android.bp
index 6299237..ad0de3b 100644
--- a/system/btif/Android.bp
+++ b/system/btif/Android.bp
@@ -176,6 +176,7 @@
         "lib-bt-packets-base",
         "lib-bt-packets-avrcp",
         "libbt-audio-hal-interface",
+        "libcom.android.sysprop.bluetooth",
         "libaudio-a2dp-hw-utils",
     ],
     cflags: [
@@ -384,11 +385,131 @@
     ],
     static_libs: [
         "libbluetooth-types",
+        "libcom.android.sysprop.bluetooth",
         "libosi",
     ],
     cflags: ["-DBUILDCFG"],
 }
 
+cc_test {
+    name: "net_test_btif_hh",
+    host_supported: true,
+    defaults: [
+        "fluoride_defaults",
+        "mts_defaults",
+    ],
+    test_suites: ["device-tests"],
+    include_dirs: [
+        "frameworks/av/media/libaaudio/include",
+        "packages/modules/Bluetooth/system",
+        "packages/modules/Bluetooth/system/bta/dm",
+        "packages/modules/Bluetooth/system/bta/include",
+        "packages/modules/Bluetooth/system/bta/sys",
+        "packages/modules/Bluetooth/system/btif/avrcp",
+        "packages/modules/Bluetooth/system/btif/co",
+        "packages/modules/Bluetooth/system/btif/include",
+        "packages/modules/Bluetooth/system/device/include",
+        "packages/modules/Bluetooth/system/embdrv/sbc/decoder/include",
+        "packages/modules/Bluetooth/system/embdrv/sbc/encoder/include",
+        "packages/modules/Bluetooth/system/gd",
+        "packages/modules/Bluetooth/system/include",
+        "packages/modules/Bluetooth/system/internal_include",
+        "packages/modules/Bluetooth/system/stack/a2dp",
+        "packages/modules/Bluetooth/system/stack/avdt",
+        "packages/modules/Bluetooth/system/stack/btm",
+        "packages/modules/Bluetooth/system/stack/include",
+        "packages/modules/Bluetooth/system/stack/l2cap",
+        "packages/modules/Bluetooth/system/udrv/include",
+        "packages/modules/Bluetooth/system/utils/include",
+        "packages/modules/Bluetooth/system/vnd/include",
+        "system/libfmq/include",
+        "system/libhwbinder/include",
+        ],
+      srcs: [
+          ":LibBluetoothSources",
+          ":TestCommonMainHandler",
+          ":TestCommonMockFunctions",
+          ":TestMockAndroidHardware",
+          ":BtaDmSources",
+          ":TestMockBtaAg",
+          ":TestMockBtaAr",
+          ":TestMockBtaAv",
+          ":TestMockBtaCsis",
+          ":TestMockBtaGatt",
+          ":TestMockBtaGroups",
+          ":TestMockBtaHas",
+          ":TestMockBtaHd",
+          ":TestMockBtaHearingAid",
+          ":TestMockBtaHf",
+          ":TestMockBtaHh",
+          ":TestMockBtaJv",
+          ":TestMockBtaLeAudio",
+          ":TestMockBtaLeAudioHalVerifier",
+          ":TestMockBtaPan",
+          ":TestMockBtaSdp",
+          ":TestMockBtaSys",
+          ":TestMockBtaVc",
+          ":TestMockBtu",
+          ":TestMockBtcore",
+          ":TestMockCommon",
+          ":TestMockFrameworks",
+          ":TestMockHci",
+          ":TestMockMainShim",
+          ":TestMockOsi",
+          ":TestMockStack",
+          ":TestMockSystemLibfmq",
+          ":TestMockUdrv",
+          ":TestMockUtils",
+          "test/btif_hh_test.cc",
+      ],
+      generated_headers: [
+        "BluetoothGeneratedDumpsysDataSchema_h",
+        "BluetoothGeneratedPackets_h",
+      ],
+      header_libs: ["libbluetooth_headers"],
+      shared_libs: [
+          "android.hardware.bluetooth.audio@2.0",
+          "android.hardware.bluetooth.audio@2.1",
+          "libcrypto",
+          "libcutils",
+          "libhidlbase",
+          "liblog",
+          "libtinyxml2",
+      ],
+      whole_static_libs: [
+          "libbtif",
+      ],
+      static_libs: [
+          "android.hardware.bluetooth.a2dp@1.0",
+          "avrcp-target-service",
+          "libaudio-a2dp-hw-utils",
+          "libbluetooth-types",
+          "libbt-audio-hal-interface",
+          "libbt-stack",
+          "libbtdevice",
+          "lib-bt-packets",
+          "lib-bt-packets-avrcp",
+          "lib-bt-packets-base",
+          "libcom.android.sysprop.bluetooth",
+          "libc++fs",
+          "libflatbuffers-cpp",
+          "libgmock",
+      ],
+      cflags: ["-DBUILDCFG"],
+      target: {
+          android: {
+              shared_libs: [
+                  "libbinder_ndk",
+                  "android.hardware.bluetooth.audio-V2-ndk",
+              ],
+          },
+      },
+      sanitize: {
+        address: true,
+        cfi: true,
+        misc_undefined: ["bounds"],
+    },
+}
 // Cycle stack test
 cc_test {
     name: "net_test_btif_stack",
@@ -487,6 +608,7 @@
           "lib-bt-packets",
           "lib-bt-packets-avrcp",
           "lib-bt-packets-base",
+          "libcom.android.sysprop.bluetooth",
           "libc++fs",
           "libflatbuffers-cpp",
           "libgmock",
diff --git a/system/btif/co/bta_av_co.cc b/system/btif/co/bta_av_co.cc
index 34406f1..e828f0a 100644
--- a/system/btif/co/bta_av_co.cc
+++ b/system/btif/co/bta_av_co.cc
@@ -33,6 +33,7 @@
 #include "btif/include/btif_av.h"
 #include "include/hardware/bt_av.h"
 #include "osi/include/osi.h"  // UNUSED_ATTR
+#include "osi/include/allocator.h"
 #include "stack/include/a2dp_codec_api.h"
 #include "stack/include/a2dp_error_codes.h"
 #include "stack/include/avdt_api.h"
@@ -1372,6 +1373,12 @@
   p_buf = btif_a2dp_source_audio_readbuf();
   if (p_buf == nullptr) return nullptr;
 
+  if (p_buf->offset < 4) {
+    osi_free(p_buf);
+    APPL_TRACE_ERROR("No space for timestamp in packet, dropped");
+    return nullptr;
+  }
+
   /*
    * Retrieve the timestamp information from the media packet,
    * and set up the packet header.
@@ -1385,6 +1392,8 @@
       !A2DP_BuildCodecHeader(p_codec_info, p_buf, p_buf->layer_specific)) {
     APPL_TRACE_ERROR("%s: unsupported codec type (%d)", __func__,
                      A2DP_GetCodecType(p_codec_info));
+    osi_free(p_buf);
+    return nullptr;
   }
 
   if (ContentProtectEnabled() && (active_peer_ != nullptr) &&
diff --git a/system/btif/include/btif_a2dp_source.h b/system/btif/include/btif_a2dp_source.h
index f0b4b24..df20319 100644
--- a/system/btif/include/btif_a2dp_source.h
+++ b/system/btif/include/btif_a2dp_source.h
@@ -63,7 +63,7 @@
 
 // Shutdown the A2DP Source module.
 // This function should be called by the BTIF state machine to stop streaming.
-void btif_a2dp_source_shutdown(void);
+void btif_a2dp_source_shutdown(std::promise<void>);
 
 // Cleanup the A2DP Source module.
 // This function should be called by the BTIF state machine during graceful
diff --git a/system/btif/include/btif_bqr.h b/system/btif/include/btif_bqr.h
index 3905a8a..b0778ab 100644
--- a/system/btif/include/btif_bqr.h
+++ b/system/btif/include/btif_bqr.h
@@ -99,6 +99,8 @@
 // Total length of all parameters of the link Quality related event except
 // Vendor Specific Parameters.
 static constexpr uint8_t kLinkQualityParamTotalLen = 48;
+// 7.8.116 LE Read ISO Link Quality command
+static constexpr uint8_t kISOLinkQualityParamTotalLen = 24;
 // Total length of all parameters of the ROOT_INFLAMMATION event except Vendor
 // Specific Parameters.
 static constexpr uint8_t kRootInflammationParamTotalLen = 3;
diff --git a/system/btif/include/btif_sock.h b/system/btif/include/btif_sock.h
index cb0378e..494782e 100644
--- a/system/btif/include/btif_sock.h
+++ b/system/btif/include/btif_sock.h
@@ -18,11 +18,35 @@
 
 #pragma once
 
-#include "btif_uid.h"
-
 #include <hardware/bt_sock.h>
 
+#include "btif_uid.h"
+#include "types/raw_address.h"
+
+enum {
+  SOCKET_CONNECTION_STATE_UNKNOWN,
+  // Socket acts as a server waiting for connection
+  SOCKET_CONNECTION_STATE_LISTENING,
+  // Socket acts as a client trying to connect
+  SOCKET_CONNECTION_STATE_CONNECTING,
+  // Socket is connected
+  SOCKET_CONNECTION_STATE_CONNECTED,
+  // Socket tries to disconnect from remote
+  SOCKET_CONNECTION_STATE_DISCONNECTING,
+  // This socket is closed
+  SOCKET_CONNECTION_STATE_DISCONNECTED,
+};
+
+enum {
+  SOCKET_ROLE_UNKNOWN,
+  SOCKET_ROLE_LISTEN,
+  SOCKET_ROLE_CONNECTION,
+};
+
 const btsock_interface_t* btif_sock_get_interface(void);
 
 bt_status_t btif_sock_init(uid_set_t* uid_set);
 void btif_sock_cleanup(void);
+
+void btif_sock_connection_logger(int state, int role, const RawAddress& addr);
+void btif_sock_dump(int fd);
diff --git a/system/btif/src/bluetooth.cc b/system/btif/src/bluetooth.cc
index 9a16712..9e089f7 100644
--- a/system/btif/src/bluetooth.cc
+++ b/system/btif/src/bluetooth.cc
@@ -58,6 +58,7 @@
 #include "bta/include/bta_le_audio_broadcaster_api.h"
 #include "bta/include/bta_vc_api.h"
 #include "btif/avrcp/avrcp_service.h"
+#include "btif/include/btif_sock.h"
 #include "btif/include/stack_manager.h"
 #include "btif_a2dp.h"
 #include "btif_activity_attribution.h"
@@ -434,6 +435,7 @@
   btif_debug_av_dump(fd);
   bta_debug_av_dump(fd);
   stack_debug_avdtp_api_dump(fd);
+  btif_sock_dump(fd);
   bluetooth::avrcp::AvrcpService::DebugDump(fd);
   btif_debug_config_dump(fd);
   BTA_HfClientDumpStatistics(fd);
diff --git a/system/btif/src/btif_a2dp_source.cc b/system/btif/src/btif_a2dp_source.cc
index f13abac..4ca2c8c 100644
--- a/system/btif/src/btif_a2dp_source.cc
+++ b/system/btif/src/btif_a2dp_source.cc
@@ -30,6 +30,7 @@
 #include <string.h>
 
 #include <algorithm>
+#include <future>
 
 #include "audio_a2dp_hw/include/audio_a2dp_hw.h"
 #include "audio_hal_interface/a2dp_encoding.h"
@@ -242,7 +243,7 @@
     const RawAddress& peer_address, std::promise<void> start_session_promise);
 static void btif_a2dp_source_end_session_delayed(
     const RawAddress& peer_address);
-static void btif_a2dp_source_shutdown_delayed(void);
+static void btif_a2dp_source_shutdown_delayed(std::promise<void>);
 static void btif_a2dp_source_cleanup_delayed(void);
 static void btif_a2dp_source_audio_tx_start_event(void);
 static void btif_a2dp_source_audio_tx_stop_event(void);
@@ -483,7 +484,7 @@
   }
 }
 
-void btif_a2dp_source_shutdown(void) {
+void btif_a2dp_source_shutdown(std::promise<void> shutdown_complete_promise) {
   LOG_INFO("%s: state=%s", __func__, btif_a2dp_source_cb.StateStr().c_str());
 
   if ((btif_a2dp_source_cb.State() == BtifA2dpSource::kStateOff) ||
@@ -495,10 +496,12 @@
   btif_a2dp_source_cb.SetState(BtifA2dpSource::kStateShuttingDown);
 
   btif_a2dp_source_thread.DoInThread(
-      FROM_HERE, base::Bind(&btif_a2dp_source_shutdown_delayed));
+      FROM_HERE, base::BindOnce(&btif_a2dp_source_shutdown_delayed,
+                                std::move(shutdown_complete_promise)));
 }
 
-static void btif_a2dp_source_shutdown_delayed(void) {
+static void btif_a2dp_source_shutdown_delayed(
+    std::promise<void> shutdown_complete_promise) {
   LOG_INFO("%s: state=%s", __func__, btif_a2dp_source_cb.StateStr().c_str());
 
   // Stop the timer
@@ -514,13 +517,16 @@
   btif_a2dp_source_cb.tx_audio_queue = nullptr;
 
   btif_a2dp_source_cb.SetState(BtifA2dpSource::kStateOff);
+
+  shutdown_complete_promise.set_value();
 }
 
 void btif_a2dp_source_cleanup(void) {
   LOG_INFO("%s: state=%s", __func__, btif_a2dp_source_cb.StateStr().c_str());
 
   // Make sure the source is shutdown
-  btif_a2dp_source_shutdown();
+  std::promise<void> shutdown_complete_promise;
+  btif_a2dp_source_shutdown(std::move(shutdown_complete_promise));
 
   btif_a2dp_source_thread.DoInThread(
       FROM_HERE, base::Bind(&btif_a2dp_source_cleanup_delayed));
diff --git a/system/btif/src/btif_av.cc b/system/btif/src/btif_av.cc
index 4e132c6..b05ed16 100644
--- a/system/btif/src/btif_av.cc
+++ b/system/btif/src/btif_av.cc
@@ -26,6 +26,7 @@
 #include <frameworks/proto_logging/stats/enums/bluetooth/a2dp/enums.pb.h>
 #include <frameworks/proto_logging/stats/enums/bluetooth/enums.pb.h>
 
+#include <chrono>
 #include <cstdint>
 #include <future>
 #include <memory>
@@ -52,6 +53,7 @@
 #include "main/shim/dumpsys.h"
 #include "osi/include/allocator.h"
 #include "osi/include/properties.h"
+#include "stack/include/avrc_api.h"
 #include "stack/include/bt_hdr.h"
 #include "stack/include/btm_api.h"
 #include "stack/include/btu.h"  // do_in_main_thread
@@ -500,7 +502,15 @@
                      << ": unable to set active peer to empty in BtaAvCo";
       }
       btif_a2dp_source_end_session(active_peer_);
-      btif_a2dp_source_shutdown();
+      std::promise<void> shutdown_complete_promise;
+      std::future<void> shutdown_complete_future =
+          shutdown_complete_promise.get_future();
+      btif_a2dp_source_shutdown(std::move(shutdown_complete_promise));
+      using namespace std::chrono_literals;
+      if (shutdown_complete_future.wait_for(1s) ==
+          std::future_status::timeout) {
+        LOG_ERROR("Timed out waiting for A2DP source shutdown to complete.");
+      }
       active_peer_ = peer_address;
       peer_ready_promise.set_value();
       return true;
@@ -3332,9 +3342,10 @@
       features |= BTA_AV_FEAT_DELAY_RPT;
     }
 
-#if (AVRC_ADV_CTRL_INCLUDED == TRUE)
-    features |= BTA_AV_FEAT_RCCT | BTA_AV_FEAT_ADV_CTRL | BTA_AV_FEAT_BROWSE;
-#endif
+    if (avrcp_absolute_volume_is_enabled()) {
+      features |= BTA_AV_FEAT_RCCT | BTA_AV_FEAT_ADV_CTRL | BTA_AV_FEAT_BROWSE;
+    }
+
     BTA_AvEnable(features, bta_av_source_callback);
     btif_av_source.RegisterAllBtaHandles();
     return BT_STATUS_SUCCESS;
diff --git a/system/btif/src/btif_bqr.cc b/system/btif/src/btif_bqr.cc
index a728d80..03f1a25 100644
--- a/system/btif/src/btif_bqr.cc
+++ b/system/btif/src/btif_bqr.cc
@@ -77,13 +77,27 @@
   STREAM_TO_UINT32(bqr_link_quality_event_.buffer_underflow_bytes, p_param_buf);
 
   if (vendor_cap_supported_version >= kBqrIsoVersion) {
-    STREAM_TO_UINT32(bqr_link_quality_event_.tx_total_packets, p_param_buf);
-    STREAM_TO_UINT32(bqr_link_quality_event_.tx_unacked_packets, p_param_buf);
-    STREAM_TO_UINT32(bqr_link_quality_event_.tx_flushed_packets, p_param_buf);
-    STREAM_TO_UINT32(bqr_link_quality_event_.tx_last_subevent_packets,
-                     p_param_buf);
-    STREAM_TO_UINT32(bqr_link_quality_event_.crc_error_packets, p_param_buf);
-    STREAM_TO_UINT32(bqr_link_quality_event_.rx_duplicate_packets, p_param_buf);
+    if (length < kLinkQualityParamTotalLen + kISOLinkQualityParamTotalLen) {
+      LOG(WARNING) << __func__
+                   << ": Parameter total length: " << std::to_string(length)
+                   << " is abnormal. "
+                   << "vendor_cap_supported_version: "
+                   << vendor_cap_supported_version << " "
+                   << " (>= "
+                   << "kBqrIsoVersion=" << kBqrIsoVersion << "), "
+                   << "It should not be shorter than: "
+                   << std::to_string(kLinkQualityParamTotalLen +
+                                     kISOLinkQualityParamTotalLen);
+    } else {
+      STREAM_TO_UINT32(bqr_link_quality_event_.tx_total_packets, p_param_buf);
+      STREAM_TO_UINT32(bqr_link_quality_event_.tx_unacked_packets, p_param_buf);
+      STREAM_TO_UINT32(bqr_link_quality_event_.tx_flushed_packets, p_param_buf);
+      STREAM_TO_UINT32(bqr_link_quality_event_.tx_last_subevent_packets,
+                       p_param_buf);
+      STREAM_TO_UINT32(bqr_link_quality_event_.crc_error_packets, p_param_buf);
+      STREAM_TO_UINT32(bqr_link_quality_event_.rx_duplicate_packets,
+                       p_param_buf);
+    }
   }
 
   const auto now = system_clock::to_time_t(system_clock::now());
diff --git a/system/btif/src/btif_config.cc b/system/btif/src/btif_config.cc
index 4b99365..a777ba8 100644
--- a/system/btif/src/btif_config.cc
+++ b/system/btif/src/btif_config.cc
@@ -112,7 +112,7 @@
     metrics_salt.fill(0);
   }
   if (!AddressObfuscator::IsSaltValid(metrics_salt)) {
-    LOG(INFO) << __func__ << ": Metrics salt is not invalid, creating new one";
+    LOG(INFO) << __func__ << ": Metrics salt is invalid, creating new one";
     if (RAND_bytes(metrics_salt.data(), metrics_salt.size()) != 1) {
       LOG(FATAL) << __func__ << "Failed to generate salt for metrics";
     }
diff --git a/system/btif/src/btif_dm.cc b/system/btif/src/btif_dm.cc
index 16981d6..9b0569d 100644
--- a/system/btif/src/btif_dm.cc
+++ b/system/btif/src/btif_dm.cc
@@ -1381,6 +1381,17 @@
         status = btif_storage_set_remote_addr_type(&bdaddr, addr_type);
         ASSERTC(status == BT_STATUS_SUCCESS,
                 "failed to save remote addr type (inquiry)", status);
+
+        bool restrict_report = osi_property_get_bool(
+            "bluetooth.restrict_discovered_device.enabled", false);
+        if (restrict_report &&
+            p_search_data->inq_res.device_type == BT_DEVICE_TYPE_BLE &&
+            !(p_search_data->inq_res.ble_evt_type & BTM_BLE_CONNECTABLE_MASK)) {
+          LOG_INFO("%s: Ble device is not connectable",
+                   bdaddr.ToString().c_str());
+          break;
+        }
+
         /* Callback to notify upper layer of device */
         invoke_device_found_cb(num_properties, properties);
       }
@@ -1626,6 +1637,14 @@
           }
           pairing_cb.gatt_over_le =
               btif_dm_pairing_cb_t::ServiceDiscoveryState::FINISHED;
+
+          if (pairing_cb.sdp_over_classic !=
+              btif_dm_pairing_cb_t::ServiceDiscoveryState::SCHEDULED) {
+            // Both SDP and bonding are either done, or not scheduled,
+            // we are safe to clear the service discovery part of CB.
+            LOG_INFO("clearing pairing_cb");
+            pairing_cb = {};
+          }
         }
       } else {
         LOG_INFO("New GATT over SDP UUIDs for %s:", PRIVATE_ADDRESS(bd_addr));
@@ -1694,18 +1713,6 @@
       /* Send the event to the BTIF */
       invoke_remote_device_properties_cb(BT_STATUS_SUCCESS, bd_addr,
                                          num_properties, prop);
-
-      if ((bd_addr == pairing_cb.bd_addr ||
-           bd_addr == pairing_cb.static_bdaddr) &&
-          pairing_cb.sdp_over_classic !=
-              btif_dm_pairing_cb_t::ServiceDiscoveryState::SCHEDULED &&
-          pairing_cb.gatt_over_le !=
-              btif_dm_pairing_cb_t::ServiceDiscoveryState::SCHEDULED) {
-        // Both SDP and bonding are either done, or not scheduled, we are safe
-        // to clear the service discovery part of CB.
-        LOG_INFO("clearing pairing_cb");
-        pairing_cb = {};
-      }
     } break;
 
     default: { ASSERTC(0, "unhandled search services event", event); } break;
@@ -2792,7 +2799,11 @@
         stop_oob_advertiser();
       }
       waiting_on_oob_advertiser_start = true;
-      SMP_CrLocScOobData();
+      if (!SMP_CrLocScOobData()) {
+        waiting_on_oob_advertiser_start = false;
+        invoke_oob_data_request_cb(transport, false, Octet16{}, Octet16{},
+                                 RawAddress{}, 0x00);
+      }
     } else {
       invoke_oob_data_request_cb(transport, false, Octet16{}, Octet16{},
                                  RawAddress{}, 0x00);
diff --git a/system/btif/src/btif_gatt.cc b/system/btif/src/btif_gatt.cc
index d8076b0..2d0944f 100644
--- a/system/btif/src/btif_gatt.cc
+++ b/system/btif/src/btif_gatt.cc
@@ -55,6 +55,7 @@
  ******************************************************************************/
 static bt_status_t btif_gatt_init(const btgatt_callbacks_t* callbacks) {
   bt_gatt_callbacks = callbacks;
+  BTA_GATTS_InitBonded();
   return BT_STATUS_SUCCESS;
 }
 
diff --git a/system/btif/src/btif_gatt_util.cc b/system/btif/src/btif_gatt_util.cc
index 290c431..55788b0 100644
--- a/system/btif/src/btif_gatt_util.cc
+++ b/system/btif/src/btif_gatt_util.cc
@@ -36,6 +36,7 @@
 #include "btif_gatt.h"
 #include "btif_storage.h"
 #include "btif_util.h"
+#include "gd/os/system_properties.h"
 #include "osi/include/allocator.h"
 #include "osi/include/osi.h"
 #include "stack/btm/btm_sec.h"
@@ -75,6 +76,12 @@
 
 void btif_gatt_check_encrypted_link(RawAddress bd_addr,
                                     tBT_TRANSPORT transport_link) {
+  static const bool check_encrypted = bluetooth::os::GetSystemPropertyBool(
+      "bluetooth.gatt.check_encrypted_link.enabled", true);
+  if (!check_encrypted) {
+    LOG_DEBUG("Check skipped due to system config");
+    return;
+  }
   tBTM_LE_PENC_KEYS key;
   if ((btif_storage_get_ble_bonding_key(
            bd_addr, BTM_LE_KEY_PENC, (uint8_t*)&key,
diff --git a/system/btif/src/btif_hd.cc b/system/btif/src/btif_hd.cc
index 0d5b365..c41713a 100644
--- a/system/btif/src/btif_hd.cc
+++ b/system/btif/src/btif_hd.cc
@@ -32,10 +32,12 @@
 #include "bt_target.h"  // Must be first to define build configuration
 
 #include "bta/include/bta_hd_api.h"
+#include "bta/sys/bta_sys.h"
 #include "btif/include/btif_common.h"
 #include "btif/include/btif_hd.h"
 #include "btif/include/btif_storage.h"
 #include "btif/include/btif_util.h"
+#include "gd/common/init_flags.h"
 #include "include/hardware/bt_hd.h"
 #include "osi/include/allocator.h"
 #include "osi/include/compat.h"
@@ -162,6 +164,7 @@
       BTIF_TRACE_DEBUG("%s: status=%d", __func__, p_data->status);
       btif_hd_cb.status = BTIF_HD_DISABLED;
       if (btif_hd_cb.service_dereg_active) {
+        bta_sys_deregister(BTA_ID_HD);
         BTIF_TRACE_WARNING("registering hid host now");
         btif_hh_service_registration(TRUE);
         btif_hd_cb.service_dereg_active = FALSE;
@@ -181,6 +184,7 @@
         addr = NULL;
       }
 
+      LOG_INFO("Registering HID device app");
       btif_hd_cb.app_registered = TRUE;
       HAL_CBACK(bt_hd_callbacks, application_state_cb, addr,
                 BTHD_APP_STATE_REGISTERED);
@@ -192,7 +196,10 @@
                 BTHD_APP_STATE_NOT_REGISTERED);
       if (btif_hd_cb.service_dereg_active) {
         BTIF_TRACE_WARNING("disabling hid device service now");
-        btif_hd_free_buf();
+        if (!bluetooth::common::init_flags::
+                delay_hidh_cleanup_until_hidh_ready_start_is_enabled()) {
+          btif_hd_free_buf();
+        }
         BTA_HdDisable();
       }
       break;
diff --git a/system/btif/src/btif_hearing_aid.cc b/system/btif/src/btif_hearing_aid.cc
index 3b51ede..7820f42 100644
--- a/system/btif/src/btif_hearing_aid.cc
+++ b/system/btif/src/btif_hearing_aid.cc
@@ -85,30 +85,26 @@
 
   void Connect(const RawAddress& address) override {
     DVLOG(2) << __func__ << " address: " << address;
-    do_in_main_thread(FROM_HERE, Bind(&HearingAid::Connect,
-                                      Unretained(HearingAid::Get()), address));
+    do_in_main_thread(FROM_HERE, Bind(&HearingAid::Connect, address));
   }
 
   void Disconnect(const RawAddress& address) override {
     DVLOG(2) << __func__ << " address: " << address;
-    do_in_main_thread(FROM_HERE, Bind(&HearingAid::Disconnect,
-                                      Unretained(HearingAid::Get()), address));
+    do_in_main_thread(FROM_HERE, Bind(&HearingAid::Disconnect, address));
     do_in_jni_thread(FROM_HERE, Bind(&btif_storage_set_hearing_aid_acceptlist,
                                      address, false));
   }
 
   void AddToAcceptlist(const RawAddress& address) override {
     VLOG(2) << __func__ << " address: " << address;
-    do_in_main_thread(FROM_HERE, Bind(&HearingAid::AddToAcceptlist,
-                                      Unretained(HearingAid::Get()), address));
+    do_in_main_thread(FROM_HERE, Bind(&HearingAid::AddToAcceptlist, address));
     do_in_jni_thread(FROM_HERE, Bind(&btif_storage_set_hearing_aid_acceptlist,
                                      address, true));
   }
 
   void SetVolume(int8_t volume) override {
     DVLOG(2) << __func__ << " volume: " << +volume;
-    do_in_main_thread(FROM_HERE, Bind(&HearingAid::SetVolume,
-                                      Unretained(HearingAid::Get()), volume));
+    do_in_main_thread(FROM_HERE, Bind(&HearingAid::SetVolume, volume));
   }
 
   void RemoveDevice(const RawAddress& address) override {
@@ -116,9 +112,7 @@
 
     // RemoveDevice can be called on devices that don't have HA enabled
     if (HearingAid::IsHearingAidRunning()) {
-      do_in_main_thread(FROM_HERE,
-                        Bind(&HearingAid::Disconnect,
-                             Unretained(HearingAid::Get()), address));
+      do_in_main_thread(FROM_HERE, Bind(&HearingAid::Disconnect, address));
     }
 
     do_in_jni_thread(FROM_HERE,
diff --git a/system/btif/src/btif_hf.cc b/system/btif/src/btif_hf.cc
index 19f32e6..4d5aa1e 100644
--- a/system/btif/src/btif_hf.cc
+++ b/system/btif/src/btif_hf.cc
@@ -27,6 +27,10 @@
 
 #define LOG_TAG "bt_btif_hf"
 
+#ifdef OS_ANDROID
+#include <hfp.sysprop.h>
+#endif
+
 #include <cstdint>
 #include <string>
 
@@ -64,25 +68,14 @@
 #define BTIF_HFAG_SERVICE_NAME ("Handsfree Gateway")
 #endif
 
-#ifndef BTIF_HF_SERVICES
-#define BTIF_HF_SERVICES (BTA_HSP_SERVICE_MASK | BTA_HFP_SERVICE_MASK)
-#endif
-
 #ifndef BTIF_HF_SERVICE_NAMES
 #define BTIF_HF_SERVICE_NAMES \
   { BTIF_HSAG_SERVICE_NAME, BTIF_HFAG_SERVICE_NAME }
 #endif
 
-#ifndef BTIF_HF_FEATURES
-#define BTIF_HF_FEATURES                                          \
-  (BTA_AG_FEAT_3WAY | BTA_AG_FEAT_ECNR | BTA_AG_FEAT_REJECT |     \
-   BTA_AG_FEAT_ECS | BTA_AG_FEAT_EXTERR | BTA_AG_FEAT_VREC |      \
-   BTA_AG_FEAT_CODEC | BTA_AG_FEAT_HF_IND | BTA_AG_FEAT_ESCO_S4 | \
-   BTA_AG_FEAT_UNAT)
-#endif
-
+static uint32_t get_hf_features();
 /* HF features supported at runtime */
-static uint32_t btif_hf_features = BTIF_HF_FEATURES;
+static uint32_t btif_hf_features = get_hf_features();
 
 #define BTIF_HF_INVALID_IDX (-1)
 
@@ -145,6 +138,36 @@
   return !active_bda.IsEmpty() && active_bda == bd_addr;
 }
 
+static tBTA_SERVICE_MASK get_BTIF_HF_SERVICES() {
+#ifdef OS_ANDROID
+  static const tBTA_SERVICE_MASK hf_services =
+      android::sysprop::bluetooth::Hfp::hf_services().value_or(
+          BTA_HSP_SERVICE_MASK | BTA_HFP_SERVICE_MASK);
+  return hf_services;
+#else
+  return BTA_HSP_SERVICE_MASK | BTA_HFP_SERVICE_MASK;
+#endif
+}
+
+/* HF features supported at runtime */
+static uint32_t get_hf_features() {
+#define DEFAULT_BTIF_HF_FEATURES                                  \
+  (BTA_AG_FEAT_3WAY | BTA_AG_FEAT_ECNR | BTA_AG_FEAT_REJECT |     \
+   BTA_AG_FEAT_ECS | BTA_AG_FEAT_EXTERR | BTA_AG_FEAT_VREC |      \
+   BTA_AG_FEAT_CODEC | BTA_AG_FEAT_HF_IND | BTA_AG_FEAT_ESCO_S4 | \
+   BTA_AG_FEAT_UNAT)
+#ifdef OS_ANDROID
+  static const uint32_t hf_features =
+      android::sysprop::bluetooth::Hfp::hf_features().value_or(
+          DEFAULT_BTIF_HF_FEATURES);
+  return hf_features;
+#elif TARGET_FLOSS
+  return BTA_AG_FEAT_ECS | BTA_AG_FEAT_CODEC;
+#else
+  return DEFAULT_BTIF_HF_FEATURES;
+#endif
+}
+
 /*******************************************************************************
  *
  * Function         is_connected
@@ -773,11 +796,11 @@
 // Invoke the enable service API to the core to set the appropriate service_id
 // Internally, the HSP_SERVICE_ID shall also be enabled if HFP is enabled
 // (phone) otherwise only HSP is enabled (tablet)
-#if (defined(BTIF_HF_SERVICES) && (BTIF_HF_SERVICES & BTA_HFP_SERVICE_MASK))
-  btif_enable_service(BTA_HFP_SERVICE_ID);
-#else
-  btif_enable_service(BTA_HSP_SERVICE_ID);
-#endif
+  if (get_BTIF_HF_SERVICES() & BTA_HFP_SERVICE_MASK) {
+    btif_enable_service(BTA_HFP_SERVICE_ID);
+  } else {
+    btif_enable_service(BTA_HSP_SERVICE_ID);
+  }
 
   return BT_STATUS_SUCCESS;
 }
@@ -1401,15 +1424,16 @@
   btif_queue_cleanup(UUID_SERVCLASS_AG_HANDSFREE);
 
   tBTA_SERVICE_MASK mask = btif_get_enabled_services_mask();
-#if (defined(BTIF_HF_SERVICES) && (BTIF_HF_SERVICES & BTA_HFP_SERVICE_MASK))
-  if ((mask & (1 << BTA_HFP_SERVICE_ID)) != 0) {
-    btif_disable_service(BTA_HFP_SERVICE_ID);
+  if (get_BTIF_HF_SERVICES() & BTA_HFP_SERVICE_MASK) {
+    if ((mask & (1 << BTA_HFP_SERVICE_ID)) != 0) {
+      btif_disable_service(BTA_HFP_SERVICE_ID);
+    }
+  } else {
+    if ((mask & (1 << BTA_HSP_SERVICE_ID)) != 0) {
+      btif_disable_service(BTA_HSP_SERVICE_ID);
+    }
   }
-#else
-  if ((mask & (1 << BTA_HSP_SERVICE_ID)) != 0) {
-    btif_disable_service(BTA_HSP_SERVICE_ID);
-  }
-#endif
+
   do_in_jni_thread(FROM_HERE, base::Bind([]() { bt_hf_callbacks = nullptr; }));
 }
 
@@ -1466,7 +1490,8 @@
     /* Enable and register with BTA-AG */
     BTA_AgEnable(bte_hf_evt);
     for (uint8_t app_id = 0; app_id < btif_max_hf_clients; app_id++) {
-      BTA_AgRegister(BTIF_HF_SERVICES, btif_hf_features, service_names, app_id);
+      BTA_AgRegister(get_BTIF_HF_SERVICES(), btif_hf_features, service_names,
+                     app_id);
     }
   } else {
     /* De-register AG */
diff --git a/system/btif/src/btif_hf_client.cc b/system/btif/src/btif_hf_client.cc
index 0826368..ed94e0d 100644
--- a/system/btif/src/btif_hf_client.cc
+++ b/system/btif/src/btif_hf_client.cc
@@ -68,13 +68,6 @@
 #define BTIF_HF_CLIENT_SERVICE_NAME ("Handsfree")
 #endif
 
-#ifndef BTIF_HF_CLIENT_FEATURES
-#define BTIF_HF_CLIENT_FEATURES                                                \
-  (BTA_HF_CLIENT_FEAT_ECNR | BTA_HF_CLIENT_FEAT_3WAY |                         \
-   BTA_HF_CLIENT_FEAT_CLI | BTA_HF_CLIENT_FEAT_VREC | BTA_HF_CLIENT_FEAT_VOL | \
-   BTA_HF_CLIENT_FEAT_ECS | BTA_HF_CLIENT_FEAT_ECC | BTA_HF_CLIENT_FEAT_CODEC)
-#endif
-
 /*******************************************************************************
  *  Local type definitions
  ******************************************************************************/
@@ -313,9 +306,7 @@
    * The handle is valid until we have called BTA_HfClientClose or the LL
    * has notified us of channel close due to remote closing, error etc.
    */
-  BTA_HfClientOpen(cb->peer_bda, &cb->handle);
-
-  return BT_STATUS_SUCCESS;
+  return BTA_HfClientOpen(cb->peer_bda, &cb->handle);
 }
 
 static bt_status_t connect(RawAddress* bd_addr) {
@@ -360,7 +351,7 @@
 
   CHECK_BTHF_CLIENT_SLC_CONNECTED(cb);
 
-  if ((BTIF_HF_CLIENT_FEATURES & BTA_HF_CLIENT_FEAT_CODEC) &&
+  if ((get_default_hf_client_features() & BTA_HF_CLIENT_FEAT_CODEC) &&
       (cb->peer_feat & BTA_HF_CLIENT_PEER_CODEC)) {
     BTA_HfClientSendAT(cb->handle, BTA_HF_CLIENT_AT_CMD_BCC, 0, 0, NULL);
   } else {
@@ -745,6 +736,27 @@
   return BT_STATUS_SUCCESS;
 }
 
+/*******************************************************************************
+ *
+ * Function         send_hfp_audio_policy
+ *
+ * Description      Send requested audio policies to remote device.
+ *
+ * Returns          bt_status_t
+ *
+ ******************************************************************************/
+static bt_status_t send_android_at(const RawAddress* bd_addr, const char* arg) {
+  btif_hf_client_cb_t* cb = btif_hf_client_get_cb_by_bda(*bd_addr);
+  if (cb == NULL || !is_connected(cb)) return BT_STATUS_FAIL;
+
+  CHECK_BTHF_CLIENT_SLC_CONNECTED(cb);
+
+  BTIF_TRACE_EVENT("%s: val1 %s", __func__, arg);
+  BTA_HfClientSendAT(cb->handle, BTA_HF_CLIENT_AT_CMD_ANDROID, 0, 0, arg);
+
+  return BT_STATUS_SUCCESS;
+}
+
 static const bthf_client_interface_t bthfClientInterface = {
     .size = sizeof(bthf_client_interface_t),
     .init = init,
@@ -765,6 +777,7 @@
     .request_last_voice_tag_number = request_last_voice_tag_number,
     .cleanup = cleanup,
     .send_at_cmd = send_at_cmd,
+    .send_android_at = send_android_at,
 };
 
 static void process_ind_evt(tBTA_HF_CLIENT_IND* ind) {
@@ -852,6 +865,7 @@
         cb->state = BTHF_CLIENT_CONNECTION_STATE_CONNECTED;
         cb->peer_feat = 0;
         cb->chld_feat = 0;
+        cb->handle = p_data->open.handle;
       } else if (cb->state == BTHF_CLIENT_CONNECTION_STATE_CONNECTING) {
         cb->state = BTHF_CLIENT_CONNECTION_STATE_DISCONNECTED;
       } else {
@@ -897,6 +911,22 @@
       cb->peer_bda = RawAddress::kAny;
       cb->peer_feat = 0;
       cb->chld_feat = 0;
+      cb->handle = 0;
+
+      /* Clean up any btif_hf_client_cb for the same disconnected bd_addr.
+       * when there is an Incoming hf_client connection is in progress and
+       * at the same time, outgoing hf_client connection is initiated then
+       * due to race condition two btif_hf_client_cb is created. This creates
+       * problem for successive connections
+       */
+      while ((cb = btif_hf_client_get_cb_by_bda(p_data->bd_addr)) != NULL) {
+        cb->state = BTHF_CLIENT_CONNECTION_STATE_DISCONNECTED;
+        cb->peer_bda = RawAddress::kAny;
+        cb->peer_feat = 0;
+        cb->chld_feat = 0;
+        cb->handle = 0;
+      }
+
       btif_queue_advance();
       break;
 
@@ -1048,8 +1078,8 @@
 bt_status_t btif_hf_client_execute_service(bool b_enable) {
   BTIF_TRACE_EVENT("%s: enable: %d", __func__, b_enable);
 
-  tBTA_HF_CLIENT_FEAT features = BTIF_HF_CLIENT_FEATURES;
-  uint16_t hfp_version = BTA_HFP_VERSION;
+  tBTA_HF_CLIENT_FEAT features = get_default_hf_client_features();
+  uint16_t hfp_version = get_default_hfp_version();
   if (hfp_version >= HFP_VERSION_1_7) {
     features |= BTA_HF_CLIENT_FEAT_ESCO_S4;
   }
diff --git a/system/btif/src/btif_hh.cc b/system/btif/src/btif_hh.cc
index 3280a95..38986b9 100644
--- a/system/btif/src/btif_hh.cc
+++ b/system/btif/src/btif_hh.cc
@@ -538,6 +538,15 @@
        (btif_hh_cb.status == BTIF_HH_DEV_CONNECTING)) {
           btif_hh_cb.status = (BTIF_HH_STATUS)BTIF_HH_DEV_DISCONNECTED;
           btif_hh_cb.pending_conn_address = RawAddress::kEmpty;
+
+      /* need to notify up-layer device is disconnected to avoid
+       * state out of sync with up-layer */
+      do_in_jni_thread(base::Bind(
+            [](RawAddress bd_addrcb) {
+              HAL_CBACK(bt_hh_callbacks, connection_state_cb, &bd_addrcb,
+                        BTHH_CONN_STATE_DISCONNECTED);
+            },
+           *bd_addr));
     }
     return BT_STATUS_FAIL;
   }
diff --git a/system/btif/src/btif_pan.cc b/system/btif/src/btif_pan.cc
index df71187..99be952 100644
--- a/system/btif/src/btif_pan.cc
+++ b/system/btif/src/btif_pan.cc
@@ -34,6 +34,9 @@
 #include <linux/if_ether.h>
 #include <linux/if_tun.h>
 #include <net/if.h>
+#ifdef OS_ANDROID
+#include <pan.sysprop.h>
+#endif
 #include <poll.h>
 #include <sys/ioctl.h>
 #include <unistd.h>
@@ -93,6 +96,16 @@
 
 const btpan_interface_t* btif_pan_get_interface() { return &pan_if; }
 
+static bool pan_nap_is_enabled() {
+#ifdef OS_ANDROID
+  // replace build time config PAN_NAP_DISABLED with runtime
+  static const bool nap_is_enabled =
+      android::sysprop::bluetooth::Pan::nap().value_or(true);
+  return nap_is_enabled;
+#else
+  return true;
+#endif
+}
 /*******************************************************************************
  **
  ** Function        btif_pan_init
@@ -118,9 +131,9 @@
     btpan_cb.enabled = 1;
 
     int role = BTPAN_ROLE_NONE;
-#if PAN_NAP_DISABLED == FALSE
-    role |= BTPAN_ROLE_PANNAP;
-#endif
+    if (pan_nap_is_enabled()) {
+      role |= BTPAN_ROLE_PANNAP;
+    }
 #if PANU_DISABLED == FALSE
     role |= BTPAN_ROLE_PANU;
 #endif
diff --git a/system/btif/src/btif_rc.cc b/system/btif/src/btif_rc.cc
index d66c427..9666668 100644
--- a/system/btif/src/btif_rc.cc
+++ b/system/btif/src/btif_rc.cc
@@ -599,22 +599,23 @@
     rc_features = (btrc_remote_features_t)(rc_features | BTRC_FEAT_BROWSE);
   }
 
-#if (AVRC_ADV_CTRL_INCLUDED == TRUE)
+  if (p_dev->rc_features & BTA_AV_FEAT_METADATA) {
+    rc_features = (btrc_remote_features_t)(rc_features | BTRC_FEAT_METADATA);
+  }
+
+  if (!avrcp_absolute_volume_is_enabled()) {
+    return;
+  }
+
   if ((p_dev->rc_features & BTA_AV_FEAT_ADV_CTRL) &&
       (p_dev->rc_features & BTA_AV_FEAT_RCTG)) {
     rc_features =
         (btrc_remote_features_t)(rc_features | BTRC_FEAT_ABSOLUTE_VOLUME);
   }
-#endif
-
-  if (p_dev->rc_features & BTA_AV_FEAT_METADATA) {
-    rc_features = (btrc_remote_features_t)(rc_features | BTRC_FEAT_METADATA);
-  }
 
   BTIF_TRACE_DEBUG("%s: rc_features: 0x%x", __func__, rc_features);
   HAL_CBACK(bt_rc_callbacks, remote_features_cb, p_dev->rc_addr, rc_features);
 
-#if (AVRC_ADV_CTRL_INCLUDED == TRUE)
   BTIF_TRACE_DEBUG(
       "%s: Checking for feature flags in btif_rc_handler with label: %d",
       __func__, p_dev->rc_vol_label);
@@ -640,7 +641,6 @@
       register_volumechange(p_dev->rc_vol_label, p_dev);
     }
   }
-#endif
 }
 
 /***************************************************************************
@@ -1959,6 +1959,11 @@
                    dump_rc_notification_event_id(event_id));
   std::unique_lock<std::mutex> lock(btif_rc_cb.lock);
 
+  if (event_id > MAX_RC_NOTIFICATIONS) {
+    BTIF_TRACE_ERROR("Invalid event id");
+    return BT_STATUS_PARM_INVALID;
+  }
+
   memset(&(avrc_rsp.reg_notif), 0, sizeof(tAVRC_REG_NOTIF_RSP));
 
   avrc_rsp.reg_notif.event_id = event_id;
@@ -3650,29 +3655,31 @@
      * for standard attributes.
      */
     p_app_settings->num_ext_attrs = 0;
-    for (xx = 0; xx < p_app_settings->ext_attr_index; xx++) {
+    for (xx = 0;
+         xx < p_app_settings->ext_attr_index && xx < AVRC_MAX_APP_ATTR_SIZE;
+         xx++) {
       osi_free_and_reset((void**)&p_app_settings->ext_attrs[xx].p_str);
     }
     p_app_settings->ext_attr_index = 0;
 
-    if (p_dev) {
-      for (xx = 0; xx < p_app_settings->num_attrs; xx++) {
-        attrs[xx] = p_app_settings->attrs[xx].attr_id;
-      }
-
-      do_in_jni_thread(
-          FROM_HERE,
-          base::Bind(bt_rc_ctrl_callbacks->playerapplicationsetting_cb,
-                     p_dev->rc_addr, p_app_settings->num_attrs,
-                     p_app_settings->attrs, 0, nullptr));
-      get_player_app_setting_cmd(xx, attrs, p_dev);
+    for (xx = 0; xx < p_app_settings->num_attrs && xx < AVRC_MAX_APP_ATTR_SIZE;
+         xx++) {
+      attrs[xx] = p_app_settings->attrs[xx].attr_id;
     }
+
+    do_in_jni_thread(
+        FROM_HERE, base::Bind(bt_rc_ctrl_callbacks->playerapplicationsetting_cb,
+                              p_dev->rc_addr, p_app_settings->num_attrs,
+                              p_app_settings->attrs, 0, nullptr));
+    get_player_app_setting_cmd(xx, attrs, p_dev);
+
     return;
   }
 
   for (xx = 0; xx < p_rsp->num_attr; xx++) {
     uint8_t x;
-    for (x = 0; x < p_app_settings->num_ext_attrs; x++) {
+    for (x = 0; x < p_app_settings->num_ext_attrs && x < AVRC_MAX_APP_ATTR_SIZE;
+         x++) {
       if (p_app_settings->ext_attrs[x].attr_id == p_rsp->p_attrs[xx].attr_id) {
         p_app_settings->ext_attrs[x].charset_id = p_rsp->p_attrs[xx].charset_id;
         p_app_settings->ext_attrs[x].str_len = p_rsp->p_attrs[xx].str_len;
@@ -3682,7 +3689,9 @@
     }
   }
 
-  for (xx = 0; xx < p_app_settings->ext_attrs[0].num_val; xx++) {
+  for (xx = 0;
+       xx < p_app_settings->ext_attrs[0].num_val && xx < BTRC_MAX_APP_ATTR_SIZE;
+       xx++) {
     vals[xx] = p_app_settings->ext_attrs[0].ext_attr_val[xx].val;
   }
   get_player_app_setting_value_text_cmd(vals, xx, p_dev);
@@ -3726,11 +3735,13 @@
      * for standard attributes.
      */
     p_app_settings->num_ext_attrs = 0;
-    for (xx = 0; xx < p_app_settings->ext_attr_index; xx++) {
+    for (xx = 0;
+         xx < p_app_settings->ext_attr_index && xx < AVRC_MAX_APP_ATTR_SIZE;
+         xx++) {
       int x;
       btrc_player_app_ext_attr_t* p_ext_attr = &p_app_settings->ext_attrs[xx];
 
-      for (x = 0; x < p_ext_attr->num_val; x++)
+      for (x = 0; x < p_ext_attr->num_val && x < BTRC_MAX_APP_ATTR_SIZE; x++)
         osi_free_and_reset((void**)&p_ext_attr->ext_attr_val[x].p_str);
       p_ext_attr->num_val = 0;
       osi_free_and_reset((void**)&p_app_settings->ext_attrs[xx].p_str);
@@ -3749,11 +3760,17 @@
     return;
   }
 
+  if (p_app_settings->ext_val_index >= AVRC_MAX_APP_ATTR_SIZE) {
+    BTIF_TRACE_ERROR("ext_val_index is 0x%02x, overflow!",
+                     p_app_settings->ext_val_index);
+    return;
+  }
+
   for (xx = 0; xx < p_rsp->num_attr; xx++) {
     uint8_t x;
     btrc_player_app_ext_attr_t* p_ext_attr;
     p_ext_attr = &p_app_settings->ext_attrs[p_app_settings->ext_val_index];
-    for (x = 0; x < p_rsp->num_attr; x++) {
+    for (x = 0; x < p_rsp->num_attr && x < BTRC_MAX_APP_ATTR_SIZE; x++) {
       if (p_ext_attr->ext_attr_val[x].val == p_rsp->p_attrs[xx].attr_id) {
         p_ext_attr->ext_attr_val[x].charset_id = p_rsp->p_attrs[xx].charset_id;
         p_ext_attr->ext_attr_val[x].str_len = p_rsp->p_attrs[xx].str_len;
@@ -3806,10 +3823,12 @@
  **************************************************************************/
 static void cleanup_app_attr_val_txt_response(
     btif_rc_player_app_settings_t* p_app_settings) {
-  for (uint8_t xx = 0; xx < p_app_settings->ext_attr_index; xx++) {
+  for (uint8_t xx = 0;
+       xx < p_app_settings->ext_attr_index && xx < AVRC_MAX_APP_ATTR_SIZE;
+       xx++) {
     int x;
     btrc_player_app_ext_attr_t* p_ext_attr = &p_app_settings->ext_attrs[xx];
-    for (x = 0; x < p_ext_attr->num_val; x++) {
+    for (x = 0; x < p_ext_attr->num_val && x < BTRC_MAX_APP_ATTR_SIZE; x++) {
       osi_free_and_reset((void**)&p_ext_attr->ext_attr_val[x].p_str);
     }
     p_ext_attr->num_val = 0;
diff --git a/system/btif/src/btif_sdp_server.cc b/system/btif/src/btif_sdp_server.cc
index 5bfd3e8..64dab16 100644
--- a/system/btif/src/btif_sdp_server.cc
+++ b/system/btif/src/btif_sdp_server.cc
@@ -287,6 +287,10 @@
 bt_status_t remove_sdp_record(int record_id) {
   int handle;
 
+  if (record_id >= MAX_SDP_SLOTS) {
+    return BT_STATUS_PARM_INVALID;
+  }
+
   bluetooth_sdp_record* record;
   bluetooth_sdp_types sdp_type = SDP_TYPE_RAW;
   {
@@ -349,9 +353,9 @@
   BTIF_TRACE_DEBUG("Sdp Server %s", __func__);
   const sdp_slot_t* sdp_slot = start_create_sdp(id);
   tBTA_SERVICE_ID service_id = -1;
+  bluetooth_sdp_record* record;
   /* In the case we are shutting down, sdp_slot is NULL */
-  if (sdp_slot != NULL) {
-    bluetooth_sdp_record* record = sdp_slot->record_data;
+  if (sdp_slot != nullptr && (record = sdp_slot->record_data) != nullptr) {
     int handle = -1;
     switch (record->hdr.type) {
       case SDP_TYPE_MAP_MAS:
diff --git a/system/btif/src/btif_sock.cc b/system/btif/src/btif_sock.cc
index b945f4b..bcbce01 100644
--- a/system/btif/src/btif_sock.cc
+++ b/system/btif/src/btif_sock.cc
@@ -18,10 +18,13 @@
 
 #define LOG_TAG "bt_btif_sock"
 
+#include "btif/include/btif_sock.h"
+
 #include <base/logging.h>
 #include <frameworks/proto_logging/stats/enums/bluetooth/enums.pb.h>
 #include <hardware/bluetooth.h>
 #include <hardware/bt_sock.h>
+#include <time.h>
 
 #include <atomic>
 
@@ -60,6 +63,22 @@
 static std::atomic_int thread_handle{-1};
 static thread_t* thread;
 
+#define SOCK_LOGGER_SIZE_MAX 16
+
+struct SockConnectionEvent {
+  bool used;
+  RawAddress addr;
+  int state;
+  int role;
+  struct timespec timestamp;
+
+  void dump(const int fd);
+};
+
+static std::atomic<uint8_t> logger_index;
+
+static SockConnectionEvent connection_logger[SOCK_LOGGER_SIZE_MAX];
+
 const btsock_interface_t* btif_sock_get_interface(void) {
   static btsock_interface_t interface = {
       sizeof(interface), btsock_listen, /* listen */
@@ -131,6 +150,88 @@
   thread = NULL;
 }
 
+void btif_sock_connection_logger(int state, int role, const RawAddress& addr) {
+  LOG_INFO("address=%s, role=%d, state=%d", addr.ToString().c_str(), state,
+           role);
+
+  uint8_t index = logger_index++ % SOCK_LOGGER_SIZE_MAX;
+
+  connection_logger[index] = {
+      .used = true,
+      .addr = addr,
+      .state = state,
+      .role = role,
+  };
+  clock_gettime(CLOCK_REALTIME, &connection_logger[index].timestamp);
+}
+
+void btif_sock_dump(int fd) {
+  dprintf(fd, "\nSocket Events: \n");
+  dprintf(fd, "  Time        \tAddress          \tState             \tRole\n");
+
+  const uint8_t head = logger_index.load() % SOCK_LOGGER_SIZE_MAX;
+
+  uint8_t index = head;
+  do {
+    connection_logger[index].dump(fd);
+
+    index++;
+    index %= SOCK_LOGGER_SIZE_MAX;
+  } while (index != head);
+  dprintf(fd, "\n");
+}
+
+void SockConnectionEvent::dump(const int fd) {
+  if (!used) {
+    return;
+  }
+
+  char eventtime[20];
+  char temptime[20];
+  struct tm* tstamp = localtime(&timestamp.tv_sec);
+  strftime(temptime, sizeof(temptime), "%H:%M:%S", tstamp);
+  snprintf(eventtime, sizeof(eventtime), "%s.%03ld", temptime,
+           timestamp.tv_nsec / 1000000);
+
+  const char* str_state;
+  switch (state) {
+    case SOCKET_CONNECTION_STATE_LISTENING:
+      str_state = "STATE_LISTENING";
+      break;
+    case SOCKET_CONNECTION_STATE_CONNECTING:
+      str_state = "STATE_CONNECTING";
+      break;
+    case SOCKET_CONNECTION_STATE_CONNECTED:
+      str_state = "STATE_CONNECTED";
+      break;
+    case SOCKET_CONNECTION_STATE_DISCONNECTING:
+      str_state = "STATE_DISCONNECTING";
+      break;
+    case SOCKET_CONNECTION_STATE_DISCONNECTED:
+      str_state = "STATE_DISCONNECTED";
+      break;
+    default:
+      str_state = "STATE_UNKNOWN";
+      break;
+  }
+
+  const char* str_role;
+  switch (role) {
+    case SOCKET_ROLE_LISTEN:
+      str_role = "ROLE_LISTEN";
+      break;
+    case SOCKET_ROLE_CONNECTION:
+      str_role = "ROLE_CONNECTION";
+      break;
+    default:
+      str_role = "ROLE_UNKNOWN";
+      break;
+  }
+
+  dprintf(fd, "  %s\t%s\t%s   \t%s\n", eventtime,
+          addr.ToString().c_str(), str_state, str_role);
+}
+
 static bt_status_t btsock_listen(btsock_type_t type, const char* service_name,
                                  const Uuid* service_uuid, int channel,
                                  int* sock_fd, int flags, int app_uid) {
@@ -142,6 +243,8 @@
   bt_status_t status = BT_STATUS_FAIL;
   int original_channel = channel;
 
+  btif_sock_connection_logger(SOCKET_CONNECTION_STATE_LISTENING,
+                              SOCKET_ROLE_LISTEN, RawAddress::kEmpty);
   log_socket_connection_state(RawAddress::kEmpty, 0, type,
                               android::bluetooth::SocketConnectionstateEnum::
                                   SOCKET_CONNECTION_STATE_LISTENING,
@@ -183,6 +286,8 @@
       break;
   }
   if (status != BT_STATUS_SUCCESS) {
+    btif_sock_connection_logger(SOCKET_CONNECTION_STATE_DISCONNECTED,
+                                SOCKET_ROLE_LISTEN, RawAddress::kEmpty);
     log_socket_connection_state(RawAddress::kEmpty, 0, type,
                                 android::bluetooth::SocketConnectionstateEnum::
                                     SOCKET_CONNECTION_STATE_DISCONNECTED,
@@ -198,9 +303,13 @@
   CHECK(bd_addr != NULL);
   CHECK(sock_fd != NULL);
 
+  LOG_INFO("%s", __func__);
+
   *sock_fd = INVALID_FD;
   bt_status_t status = BT_STATUS_FAIL;
 
+  btif_sock_connection_logger(SOCKET_CONNECTION_STATE_CONNECTING,
+                              SOCKET_ROLE_CONNECTION, *bd_addr);
   log_socket_connection_state(*bd_addr, 0, type,
                               android::bluetooth::SocketConnectionstateEnum::
                                   SOCKET_CONNECTION_STATE_CONNECTING,
@@ -245,6 +354,8 @@
       break;
   }
   if (status != BT_STATUS_SUCCESS) {
+    btif_sock_connection_logger(SOCKET_CONNECTION_STATE_DISCONNECTED,
+                                SOCKET_ROLE_CONNECTION, *bd_addr);
     log_socket_connection_state(*bd_addr, 0, type,
                                 android::bluetooth::SocketConnectionstateEnum::
                                     SOCKET_CONNECTION_STATE_DISCONNECTED,
diff --git a/system/btif/src/btif_sock_l2cap.cc b/system/btif/src/btif_sock_l2cap.cc
index 9d5a5bd..8f42316 100644
--- a/system/btif/src/btif_sock_l2cap.cc
+++ b/system/btif/src/btif_sock_l2cap.cc
@@ -15,6 +15,7 @@
  * limitations under the License.
  */
 
+#include <base/logging.h>
 #include <sys/ioctl.h>
 #include <sys/socket.h>
 #include <sys/types.h>
@@ -24,6 +25,7 @@
 
 #include "bta/include/bta_jv_api.h"
 #include "btif/include/btif_metrics_logging.h"
+#include "btif/include/btif_sock.h"
 #include "btif/include/btif_sock_thread.h"
 #include "btif/include/btif_sock_util.h"
 #include "btif/include/btif_uid.h"
@@ -37,8 +39,6 @@
 #include "stack/include/bt_types.h"
 #include "types/raw_address.h"
 
-#include <base/logging.h>
-
 struct packet {
   struct packet *next, *prev;
   uint32_t len;
@@ -206,6 +206,10 @@
   if (!t) /* prever double-frees */
     return;
 
+  btif_sock_connection_logger(
+      SOCKET_CONNECTION_STATE_DISCONNECTED,
+      sock->server ? SOCKET_ROLE_LISTEN : SOCKET_ROLE_CONNECTION, sock->addr);
+
   // Whenever a socket is freed, the connection must be dropped
   log_socket_connection_state(
       sock->addr, sock->id, sock->is_le_coc ? BTSOCK_L2CAP_LE : BTSOCK_L2CAP,
@@ -389,6 +393,10 @@
 
   sock->handle = p_start->handle;
 
+  btif_sock_connection_logger(
+      SOCKET_CONNECTION_STATE_LISTENING,
+      sock->server ? SOCKET_ROLE_LISTEN : SOCKET_ROLE_CONNECTION, sock->addr);
+
   log_socket_connection_state(
       sock->addr, sock->id, sock->is_le_coc ? BTSOCK_L2CAP_LE : BTSOCK_L2CAP,
       android::bluetooth::SocketConnectionstateEnum::
@@ -452,6 +460,11 @@
   accept_rs->id = sock->id;
   sock->id = new_listen_id;
 
+  btif_sock_connection_logger(
+      SOCKET_CONNECTION_STATE_CONNECTED,
+      accept_rs->server ? SOCKET_ROLE_LISTEN : SOCKET_ROLE_CONNECTION,
+      accept_rs->addr);
+
   log_socket_connection_state(
       accept_rs->addr, accept_rs->id,
       accept_rs->is_le_coc ? BTSOCK_L2CAP_LE : BTSOCK_L2CAP,
@@ -492,6 +505,10 @@
     return;
   }
 
+  btif_sock_connection_logger(
+      SOCKET_CONNECTION_STATE_CONNECTED,
+      sock->server ? SOCKET_ROLE_LISTEN : SOCKET_ROLE_CONNECTION, sock->addr);
+
   log_socket_connection_state(
       sock->addr, sock->id, sock->is_le_coc ? BTSOCK_L2CAP_LE : BTSOCK_L2CAP,
       android::bluetooth::SOCKET_CONNECTION_STATE_CONNECTED, 0, 0,
@@ -544,6 +561,10 @@
     return;
   }
 
+  btif_sock_connection_logger(
+      SOCKET_CONNECTION_STATE_DISCONNECTING,
+      sock->server ? SOCKET_ROLE_LISTEN : SOCKET_ROLE_CONNECTION, sock->addr);
+
   log_socket_connection_state(
       sock->addr, sock->id, sock->is_le_coc ? BTSOCK_L2CAP_LE : BTSOCK_L2CAP,
       android::bluetooth::SOCKET_CONNECTION_STATE_DISCONNECTING, 0, 0,
diff --git a/system/btif/src/btif_sock_rfc.cc b/system/btif/src/btif_sock_rfc.cc
index 7452f86..0a6a26e 100644
--- a/system/btif/src/btif_sock_rfc.cc
+++ b/system/btif/src/btif_sock_rfc.cc
@@ -31,6 +31,7 @@
 #include "btif/include/btif_metrics_logging.h"
 /* The JV interface can have only one user, hence we need to call a few
  * L2CAP functions from this file. */
+#include "btif/include/btif_sock.h"
 #include "btif/include/btif_sock_l2cap.h"
 #include "btif/include/btif_sock_sdp.h"
 #include "btif/include/btif_sock_thread.h"
@@ -404,6 +405,10 @@
   if (slot->fd != INVALID_FD) {
     shutdown(slot->fd, SHUT_RDWR);
     close(slot->fd);
+    btif_sock_connection_logger(
+        SOCKET_CONNECTION_STATE_DISCONNECTED,
+        slot->f.server ? SOCKET_ROLE_LISTEN : SOCKET_ROLE_CONNECTION,
+        slot->addr);
     log_socket_connection_state(
         slot->addr, slot->id, BTSOCK_RFCOMM,
         android::bluetooth::SOCKET_CONNECTION_STATE_DISCONNECTED,
@@ -485,6 +490,10 @@
 
   if (p_start->status == BTA_JV_SUCCESS) {
     slot->rfc_handle = p_start->handle;
+    btif_sock_connection_logger(
+        SOCKET_CONNECTION_STATE_LISTENING,
+        slot->f.server ? SOCKET_ROLE_LISTEN : SOCKET_ROLE_CONNECTION,
+        slot->addr);
     log_socket_connection_state(
         slot->addr, slot->id, BTSOCK_RFCOMM,
         android::bluetooth::SocketConnectionstateEnum::
@@ -508,6 +517,10 @@
       srv_rs, &p_open->rem_bda, p_open->handle, p_open->new_listen_handle);
   if (!accept_rs) return 0;
 
+  btif_sock_connection_logger(
+      SOCKET_CONNECTION_STATE_CONNECTED,
+      accept_rs->f.server ? SOCKET_ROLE_LISTEN : SOCKET_ROLE_CONNECTION,
+      accept_rs->addr);
   log_socket_connection_state(
       accept_rs->addr, accept_rs->id, BTSOCK_RFCOMM,
       android::bluetooth::SOCKET_CONNECTION_STATE_CONNECTED, 0, 0,
@@ -540,6 +553,9 @@
   slot->rfc_port_handle = BTA_JvRfcommGetPortHdl(p_open->handle);
   slot->addr = p_open->rem_bda;
 
+  btif_sock_connection_logger(
+      SOCKET_CONNECTION_STATE_CONNECTED,
+      slot->f.server ? SOCKET_ROLE_LISTEN : SOCKET_ROLE_CONNECTION, slot->addr);
   log_socket_connection_state(
       slot->addr, slot->id, BTSOCK_RFCOMM,
       android::bluetooth::SOCKET_CONNECTION_STATE_CONNECTED, 0, 0,
diff --git a/system/btif/test/btif_hf_client_service_test.cc b/system/btif/test/btif_hf_client_service_test.cc
index db48af1..5a8863b 100644
--- a/system/btif/test/btif_hf_client_service_test.cc
+++ b/system/btif/test/btif_hf_client_service_test.cc
@@ -2,11 +2,42 @@
 #include <gtest/gtest.h>
 #include "bta_hfp_api.h"
 
+#ifdef OS_ANDROID
+#include <hfp.sysprop.h>
+#endif
+
 #undef LOG_TAG
 #include "btif/src/btif_hf_client.cc"
 
 static tBTA_HF_CLIENT_FEAT gFeatures;
 
+#define DEFAULT_BTA_HFP_VERSION HFP_VERSION_1_7
+int get_default_hfp_version() {
+#ifdef OS_ANDROID
+  static const int version =
+      android::sysprop::bluetooth::Hfp::version().value_or(
+          DEFAULT_BTA_HFP_VERSION);
+  return version;
+#else
+  return DEFAULT_BTA_HFP_VERSION;
+#endif
+}
+
+int get_default_hf_client_features() {
+#define DEFAULT_BTIF_HF_CLIENT_FEATURES                                        \
+  (BTA_HF_CLIENT_FEAT_ECNR | BTA_HF_CLIENT_FEAT_3WAY |                         \
+   BTA_HF_CLIENT_FEAT_CLI | BTA_HF_CLIENT_FEAT_VREC | BTA_HF_CLIENT_FEAT_VOL | \
+   BTA_HF_CLIENT_FEAT_ECS | BTA_HF_CLIENT_FEAT_ECC | BTA_HF_CLIENT_FEAT_CODEC)
+
+#ifdef OS_ANDROID
+  static const int features =
+      android::sysprop::bluetooth::Hfp::hf_client_features().value_or(
+          DEFAULT_BTIF_HF_CLIENT_FEATURES);
+  return features;
+#else
+  return DEFAULT_BTIF_HF_CLIENT_FEATURES;
+#endif
+}
 
 uint8_t btif_trace_level = BT_TRACE_LEVEL_WARNING;
 void LogMsg(uint32_t trace_set_mask, const char* fmt_str, ...) {}
@@ -29,9 +60,7 @@
 
 class BtifHfClientTest : public ::testing::Test {
  protected:
-  void SetUp() override {
-    gFeatures = BTIF_HF_CLIENT_FEATURES;
-  }
+  void SetUp() override { gFeatures = get_default_hf_client_features(); }
 
   void TearDown() override {}
 };
@@ -41,5 +70,5 @@
 
   btif_hf_client_execute_service(enable);
   ASSERT_EQ((gFeatures & BTA_HF_CLIENT_FEAT_ESCO_S4) > 0,
-            BTA_HFP_VERSION >= HFP_VERSION_1_7);
+            get_default_hfp_version() >= HFP_VERSION_1_7);
 }
diff --git a/system/btif/test/btif_hh_test.cc b/system/btif/test/btif_hh_test.cc
new file mode 100644
index 0000000..9c1fc9d
--- /dev/null
+++ b/system/btif/test/btif_hh_test.cc
@@ -0,0 +1,284 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "btif/include/btif_hh.h"
+
+#include <gtest/gtest.h>
+
+#include <algorithm>
+#include <array>
+#include <future>
+#include <vector>
+
+#include "bta/hh/bta_hh_int.h"
+#include "bta/include/bta_ag_api.h"
+#include "bta/include/bta_hh_api.h"
+#include "btcore/include/module.h"
+#include "btif/include/btif_api.h"
+#include "btif/include/stack_manager.h"
+#include "include/hardware/bt_hh.h"
+#include "test/common/mock_functions.h"
+#include "test/mock/mock_osi_allocator.h"
+
+using namespace std::chrono_literals;
+
+void set_hal_cbacks(bt_callbacks_t* callbacks);
+
+uint8_t appl_trace_level = BT_TRACE_LEVEL_DEBUG;
+uint8_t btif_trace_level = BT_TRACE_LEVEL_DEBUG;
+uint8_t btu_trace_level = BT_TRACE_LEVEL_DEBUG;
+
+module_t bt_utils_module;
+module_t gd_controller_module;
+module_t gd_idle_module;
+module_t gd_shim_module;
+module_t osi_module;
+
+const tBTA_AG_RES_DATA tBTA_AG_RES_DATA::kEmpty = {};
+
+extern void bte_hh_evt(tBTA_HH_EVT event, tBTA_HH* p_data);
+extern const bthh_interface_t* btif_hh_get_interface();
+
+namespace test {
+namespace mock {
+extern bool bluetooth_shim_is_gd_stack_started_up;
+}
+}  // namespace test
+
+#if __GLIBC__
+size_t strlcpy(char* dst, const char* src, size_t siz) {
+  char* d = dst;
+  const char* s = src;
+  size_t n = siz;
+
+  /* Copy as many bytes as will fit */
+  if (n != 0) {
+    while (--n != 0) {
+      if ((*d++ = *s++) == '\0') break;
+    }
+  }
+
+  /* Not enough room in dst, add NUL and traverse rest of src */
+  if (n == 0) {
+    if (siz != 0) *d = '\0'; /* NUL-terminate dst */
+    while (*s++)
+      ;
+  }
+
+  return (s - src - 1); /* count does not include NUL */
+}
+
+pid_t gettid(void) throw() { return syscall(SYS_gettid); }
+#endif
+
+namespace {
+std::array<uint8_t, 32> data32 = {
+    0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b,
+    0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16,
+    0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20,
+};
+
+const RawAddress kDeviceAddress({0x11, 0x22, 0x33, 0x44, 0x55, 0x66});
+const uint16_t kHhHandle = 123;
+
+// Callback parameters grouped into a structure
+struct get_report_cb_t {
+  RawAddress raw_address;
+  bthh_status_t status;
+  std::vector<uint8_t> data;
+} get_report_cb_;
+
+// Globals allow usage within function pointers
+std::promise<bt_cb_thread_evt> g_thread_evt_promise;
+std::promise<bt_status_t> g_status_promise;
+std::promise<get_report_cb_t> g_bthh_callbacks_get_report_promise;
+
+}  // namespace
+
+bt_callbacks_t bt_callbacks = {
+    .size = sizeof(bt_callbacks_t),
+    .adapter_state_changed_cb = nullptr,  // adapter_state_changed_callback
+    .adapter_properties_cb = nullptr,     // adapter_properties_callback
+    .remote_device_properties_cb =
+        nullptr,                            // remote_device_properties_callback
+    .device_found_cb = nullptr,             // device_found_callback
+    .discovery_state_changed_cb = nullptr,  // discovery_state_changed_callback
+    .pin_request_cb = nullptr,              // pin_request_callback
+    .ssp_request_cb = nullptr,              // ssp_request_callback
+    .bond_state_changed_cb = nullptr,       // bond_state_changed_callback
+    .address_consolidate_cb = nullptr,      // address_consolidate_callback
+    .le_address_associate_cb = nullptr,     // le_address_associate_callback
+    .acl_state_changed_cb = nullptr,        // acl_state_changed_callback
+    .thread_evt_cb = nullptr,               // callback_thread_event
+    .dut_mode_recv_cb = nullptr,            // dut_mode_recv_callback
+    .le_test_mode_cb = nullptr,             // le_test_mode_callback
+    .energy_info_cb = nullptr,              // energy_info_callback
+    .link_quality_report_cb = nullptr,      // link_quality_report_callback
+    .generate_local_oob_data_cb = nullptr,  // generate_local_oob_data_callback
+    .switch_buffer_size_cb = nullptr,       // switch_buffer_size_callback
+    .switch_codec_cb = nullptr,             // switch_codec_callback
+};
+
+bthh_callbacks_t bthh_callbacks = {
+    .size = sizeof(bthh_callbacks_t),
+    .connection_state_cb = nullptr,  // bthh_connection_state_callback
+    .hid_info_cb = nullptr,          // bthh_hid_info_callback
+    .protocol_mode_cb = nullptr,     // bthh_protocol_mode_callback
+    .idle_time_cb = nullptr,         // bthh_idle_time_callback
+    .get_report_cb = nullptr,        // bthh_get_report_callback
+    .virtual_unplug_cb = nullptr,    // bthh_virtual_unplug_callback
+    .handshake_cb = nullptr,         // bthh_handshake_callback
+};
+
+class BtifHhWithMockTest : public ::testing::Test {
+ protected:
+  void SetUp() override {
+    reset_mock_function_count_map();
+    test::mock::osi_allocator::osi_malloc.body = [](size_t size) {
+      return malloc(size);
+    };
+    test::mock::osi_allocator::osi_calloc.body = [](size_t size) {
+      return calloc(1UL, size);
+    };
+    test::mock::osi_allocator::osi_free.body = [](void* ptr) { free(ptr); };
+    test::mock::osi_allocator::osi_free_and_reset.body = [](void** ptr) {
+      free(*ptr);
+      *ptr = nullptr;
+    };
+  }
+
+  void TearDown() override {
+    test::mock::osi_allocator::osi_malloc = {};
+    test::mock::osi_allocator::osi_calloc = {};
+    test::mock::osi_allocator::osi_free = {};
+    test::mock::osi_allocator::osi_free_and_reset = {};
+  }
+};
+
+class BtifHhWithHalCallbacksTest : public BtifHhWithMockTest {
+ protected:
+  void SetUp() override {
+    bluetooth::common::InitFlags::SetAllForTesting();
+    BtifHhWithMockTest::SetUp();
+    g_thread_evt_promise = std::promise<bt_cb_thread_evt>();
+    auto future = g_thread_evt_promise.get_future();
+    bt_callbacks.thread_evt_cb = [](bt_cb_thread_evt evt) {
+      g_thread_evt_promise.set_value(evt);
+    };
+    set_hal_cbacks(&bt_callbacks);
+    // Start the jni callback thread
+    ASSERT_EQ(BT_STATUS_SUCCESS, btif_init_bluetooth());
+    ASSERT_EQ(std::future_status::ready, future.wait_for(2s));
+    ASSERT_EQ(ASSOCIATE_JVM, future.get());
+
+    bt_callbacks.thread_evt_cb = [](bt_cb_thread_evt evt) {};
+  }
+
+  void TearDown() override {
+    g_thread_evt_promise = std::promise<bt_cb_thread_evt>();
+    auto future = g_thread_evt_promise.get_future();
+    bt_callbacks.thread_evt_cb = [](bt_cb_thread_evt evt) {
+      g_thread_evt_promise.set_value(evt);
+    };
+    // Shutdown the jni callback thread
+    ASSERT_EQ(BT_STATUS_SUCCESS, btif_cleanup_bluetooth());
+    ASSERT_EQ(std::future_status::ready, future.wait_for(2s));
+    ASSERT_EQ(DISASSOCIATE_JVM, future.get());
+
+    bt_callbacks.thread_evt_cb = [](bt_cb_thread_evt evt) {};
+    BtifHhWithMockTest::TearDown();
+  }
+};
+
+class BtifHhAdapterReady : public BtifHhWithHalCallbacksTest {
+ protected:
+  void SetUp() override {
+    BtifHhWithHalCallbacksTest::SetUp();
+    test::mock::bluetooth_shim_is_gd_stack_started_up = true;
+    ASSERT_EQ(BT_STATUS_SUCCESS,
+              btif_hh_get_interface()->init(&bthh_callbacks));
+  }
+
+  void TearDown() override {
+    test::mock::bluetooth_shim_is_gd_stack_started_up = false;
+    BtifHhWithHalCallbacksTest::TearDown();
+  }
+};
+
+class BtifHhWithDevice : public BtifHhAdapterReady {
+ protected:
+  void SetUp() override {
+    BtifHhAdapterReady::SetUp();
+
+    // Short circuit a connected device
+    btif_hh_cb.devices[0].bd_addr = kDeviceAddress;
+    btif_hh_cb.devices[0].dev_status = BTHH_CONN_STATE_CONNECTED;
+    btif_hh_cb.devices[0].dev_handle = kHhHandle;
+  }
+
+  void TearDown() override { BtifHhAdapterReady::TearDown(); }
+};
+
+TEST_F(BtifHhAdapterReady, lifecycle) {}
+
+TEST_F(BtifHhWithDevice, BTA_HH_GET_RPT_EVT) {
+  tBTA_HH data = {
+      .hs_data =
+          {
+              .status = BTA_HH_OK,
+              .handle = kHhHandle,
+              .rsp_data =
+                  {
+                      .p_rpt_data = static_cast<BT_HDR*>(
+                          osi_calloc(data32.size() + sizeof(BT_HDR))),
+                  },
+          },
+  };
+
+  // Fill out the deep copy data
+  data.hs_data.rsp_data.p_rpt_data->len = static_cast<uint16_t>(data32.size());
+  std::copy(data32.begin(), data32.begin() + data32.size(),
+            reinterpret_cast<uint8_t*>((data.hs_data.rsp_data.p_rpt_data + 1)));
+
+  g_bthh_callbacks_get_report_promise = std::promise<get_report_cb_t>();
+  auto future = g_bthh_callbacks_get_report_promise.get_future();
+  bthh_callbacks.get_report_cb = [](RawAddress* bd_addr,
+                                    bthh_status_t hh_status, uint8_t* rpt_data,
+                                    int rpt_size) {
+    get_report_cb_t report = {
+        .raw_address = *bd_addr,
+        .status = hh_status,
+        .data = std::vector<uint8_t>(),
+    };
+    report.data.assign(rpt_data, rpt_data + rpt_size),
+        g_bthh_callbacks_get_report_promise.set_value(report);
+  };
+
+  bte_hh_evt(BTA_HH_GET_RPT_EVT, &data);
+  osi_free(data.hs_data.rsp_data.p_rpt_data);
+
+  ASSERT_EQ(std::future_status::ready, future.wait_for(2s));
+  auto report = future.get();
+
+  // Verify data was delivered
+  ASSERT_STREQ(kDeviceAddress.ToString().c_str(),
+               report.raw_address.ToString().c_str());
+  ASSERT_EQ(BTHH_OK, report.status);
+  int i = 0;
+  for (const auto& data : data32) {
+    ASSERT_EQ(data, report.data[i++]);
+  }
+}
diff --git a/system/btif/test/btif_rc_test.cc b/system/btif/test/btif_rc_test.cc
index 155b52e..a1ef7cc 100644
--- a/system/btif/test/btif_rc_test.cc
+++ b/system/btif/test/btif_rc_test.cc
@@ -41,6 +41,7 @@
 uint8_t appl_trace_level = BT_TRACE_LEVEL_WARNING;
 uint8_t btif_trace_level = BT_TRACE_LEVEL_WARNING;
 
+bool avrcp_absolute_volume_is_enabled() { return true; }
 tAVRC_STS AVRC_BldCommand(tAVRC_COMMAND* p_cmd, BT_HDR** pp_pkt) { return 0; }
 tAVRC_STS AVRC_BldResponse(uint8_t handle, tAVRC_RESPONSE* p_rsp,
                            BT_HDR** pp_pkt) {
diff --git a/system/device/include/interop.h b/system/device/include/interop.h
index 0f1ff22..7b8eb1a 100644
--- a/system/device/include/interop.h
+++ b/system/device/include/interop.h
@@ -118,7 +118,14 @@
   INTEROP_SLC_SKIP_BIND_COMMAND,
 
   // Respond AVRCP profile version only 1.3 for some device.
-  INTEROP_AVRCP_1_3_ONLY
+  INTEROP_AVRCP_1_3_ONLY,
+
+  // Some remote devices have LMP version in[5.0, 5.2] but do not support
+  // robust
+  // caching or correctly response with an error. We disable the
+  // database hash
+  // lookup for such devices.
+  INTEROP_DISABLE_ROBUST_CACHING,
 } interop_feature_t;
 
 // Check if a given |addr| matches a known interoperability workaround as
diff --git a/system/device/include/interop_database.h b/system/device/include/interop_database.h
index d9dd282..08891e5 100644
--- a/system/device/include/interop_database.h
+++ b/system/device/include/interop_database.h
@@ -119,6 +119,8 @@
 
     // Kenwood KMM-BT518HD - no audio when A2DP codec sample rate is changed
     {{{0x00, 0x1d, 0x86, 0, 0, 0}}, 3, INTEROP_DISABLE_AVDTP_RECONFIGURE},
+    // http://b/255387998
+    {{{0x00, 0x1d, 0x86, 0, 0, 0}}, 3, INTEROP_DISABLE_ROLE_SWITCH},
 
     // NAC FORD-2013 - Lincoln
     {{{0x00, 0x26, 0xb4, 0, 0, 0}}, 3, INTEROP_DISABLE_ROLE_SWITCH},
@@ -153,9 +155,6 @@
     // AirPods 2 - unacceptably loud volume
     {{{0x9c, 0x64, 0x8b, 0, 0, 0}}, 3, INTEROP_DISABLE_ABSOLUTE_VOLUME},
 
-    // Phonak AG - volume level not change
-    {{{0x00, 0x0f, 0x59, 0, 0, 0}}, 3, INTEROP_DISABLE_ABSOLUTE_VOLUME},
-
     // for skip name request,
     // because BR/EDR address and ADV random address are the same
     {{{0xd4, 0x7a, 0xe2, 0, 0, 0}}, 3, INTEROP_DISABLE_NAME_REQUEST},
@@ -193,6 +192,52 @@
 
     // BMW Carkit
     {{{0x00, 0x0a, 0x08, 0, 0, 0}}, 3, INTEROP_AVRCP_1_3_ONLY},
+
+    // Eero Wi-Fi Router
+    {{{0x08, 0x9b, 0xf1, 0, 0, 0}}, 3, INTEROP_DISABLE_ROBUST_CACHING},
+    {{{0x20, 0xbe, 0xcd, 0, 0, 0}}, 3, INTEROP_DISABLE_ROBUST_CACHING},
+    {{{0x30, 0x34, 0x22, 0, 0, 0}}, 3, INTEROP_DISABLE_ROBUST_CACHING},
+    {{{0x3c, 0x5c, 0xf1, 0, 0, 0}}, 3, INTEROP_DISABLE_ROBUST_CACHING},
+    {{{0x40, 0x47, 0x5e, 0, 0, 0}}, 3, INTEROP_DISABLE_ROBUST_CACHING},
+    {{{0x50, 0x27, 0xa9, 0, 0, 0}}, 3, INTEROP_DISABLE_ROBUST_CACHING},
+    {{{0x64, 0x97, 0x14, 0, 0, 0}}, 3, INTEROP_DISABLE_ROBUST_CACHING},
+    {{{0x64, 0xc2, 0x69, 0, 0, 0}}, 3, INTEROP_DISABLE_ROBUST_CACHING},
+    {{{0x68, 0x4a, 0x76, 0, 0, 0}}, 3, INTEROP_DISABLE_ROBUST_CACHING},
+    {{{0x6c, 0xae, 0xf6, 0, 0, 0}}, 3, INTEROP_DISABLE_ROBUST_CACHING},
+    {{{0x78, 0x76, 0x89, 0, 0, 0}}, 3, INTEROP_DISABLE_ROBUST_CACHING},
+    {{{0x78, 0xd6, 0xd6, 0, 0, 0}}, 3, INTEROP_DISABLE_ROBUST_CACHING},
+    {{{0x84, 0x70, 0xd7, 0, 0, 0}}, 3, INTEROP_DISABLE_ROBUST_CACHING},
+    {{{0x98, 0xed, 0x7e, 0, 0, 0}}, 3, INTEROP_DISABLE_ROBUST_CACHING},
+    {{{0x9c, 0x0b, 0x05, 0, 0, 0}}, 3, INTEROP_DISABLE_ROBUST_CACHING},
+    {{{0x9c, 0x57, 0xbc, 0, 0, 0}}, 3, INTEROP_DISABLE_ROBUST_CACHING},
+    {{{0x9c, 0xa5, 0x70, 0, 0, 0}}, 3, INTEROP_DISABLE_ROBUST_CACHING},
+    {{{0xa0, 0x8e, 0x24, 0, 0, 0}}, 3, INTEROP_DISABLE_ROBUST_CACHING},
+    {{{0xac, 0xec, 0x85, 0, 0, 0}}, 3, INTEROP_DISABLE_ROBUST_CACHING},
+    {{{0xb4, 0x20, 0x46, 0, 0, 0}}, 3, INTEROP_DISABLE_ROBUST_CACHING},
+    {{{0xb4, 0xb9, 0xe6, 0, 0, 0}}, 3, INTEROP_DISABLE_ROBUST_CACHING},
+    {{{0xc0, 0x36, 0x53, 0, 0, 0}}, 3, INTEROP_DISABLE_ROBUST_CACHING},
+    {{{0xc4, 0xf1, 0x74, 0, 0, 0}}, 3, INTEROP_DISABLE_ROBUST_CACHING},
+    {{{0xc8, 0xb8, 0x2f, 0, 0, 0}}, 3, INTEROP_DISABLE_ROBUST_CACHING},
+    {{{0xc8, 0xe3, 0x06, 0, 0, 0}}, 3, INTEROP_DISABLE_ROBUST_CACHING},
+    {{{0xd4, 0x05, 0xde, 0, 0, 0}}, 3, INTEROP_DISABLE_ROBUST_CACHING},
+    {{{0xd4, 0x3f, 0x32, 0, 0, 0}}, 3, INTEROP_DISABLE_ROBUST_CACHING},
+    {{{0xec, 0x74, 0x27, 0, 0, 0}}, 3, INTEROP_DISABLE_ROBUST_CACHING},
+    {{{0xf0, 0x21, 0xe0, 0, 0, 0}}, 3, INTEROP_DISABLE_ROBUST_CACHING},
+    {{{0xf0, 0xb6, 0x61, 0, 0, 0}}, 3, INTEROP_DISABLE_ROBUST_CACHING},
+    {{{0xfc, 0x3f, 0xa6, 0, 0, 0}}, 3, INTEROP_DISABLE_ROBUST_CACHING},
+};
+
+typedef struct {
+  RawAddress addr_start;
+  RawAddress addr_end;
+  interop_feature_t feature;
+} interop_addr_range_entry_t;
+
+static const interop_addr_range_entry_t interop_addr_range_database[] = {
+    // Phonak AG - volume level not change
+    {{{0x00, 0x0f, 0x59, 0x50, 0x00, 0x00}},
+     {{0x00, 0x0f, 0x59, 0x6f, 0xff, 0xff}},
+     INTEROP_DISABLE_ABSOLUTE_VOLUME},
 };
 
 typedef struct {
diff --git a/system/device/src/interop.cc b/system/device/src/interop.cc
index 5cde30d..bc6b691 100644
--- a/system/device/src/interop.cc
+++ b/system/device/src/interop.cc
@@ -44,6 +44,8 @@
                                  const RawAddress* addr);
 static bool interop_match_dynamic_(const interop_feature_t feature,
                                    const RawAddress* addr);
+static bool interop_match_range_(const interop_feature_t feature,
+                                 const RawAddress* addr);
 
 // Interface functions
 
@@ -52,7 +54,8 @@
   CHECK(addr);
 
   if (interop_match_fixed_(feature, addr) ||
-      interop_match_dynamic_(feature, addr)) {
+      interop_match_dynamic_(feature, addr) ||
+      interop_match_range_(feature, addr)) {
     LOG_INFO("%s() Device %s is a match for interop workaround %s.", __func__,
              addr->ToString().c_str(), interop_feature_string_(feature));
     return true;
@@ -138,7 +141,8 @@
     CASE_RETURN_STR(INTEROP_DISABLE_SNIFF)
     CASE_RETURN_STR(INTEROP_DISABLE_AVDTP_SUSPEND)
     CASE_RETURN_STR(INTEROP_SLC_SKIP_BIND_COMMAND)
-    CASE_RETURN_STR(INTEROP_AVRCP_1_3_ONLY);
+    CASE_RETURN_STR(INTEROP_AVRCP_1_3_ONLY)
+    CASE_RETURN_STR(INTEROP_DISABLE_ROBUST_CACHING);
   }
 
   return "UNKNOWN";
@@ -190,3 +194,20 @@
 
   return false;
 }
+
+static bool interop_match_range_(const interop_feature_t feature,
+                                 const RawAddress* addr) {
+  CHECK(addr);
+
+  const size_t db_size =
+      sizeof(interop_addr_range_database) / sizeof(interop_addr_range_entry_t);
+  for (size_t i = 0; i != db_size; ++i) {
+    if (feature == interop_addr_range_database[i].feature &&
+        *addr >= interop_addr_range_database[i].addr_start &&
+        *addr <= interop_addr_range_database[i].addr_end) {
+      return true;
+    }
+  }
+
+  return false;
+}
diff --git a/system/device/test/interop_test.cc b/system/device/test/interop_test.cc
index 2e1598e..6c7803a 100644
--- a/system/device/test/interop_test.cc
+++ b/system/device/test/interop_test.cc
@@ -82,3 +82,26 @@
   EXPECT_FALSE(interop_match_name(INTEROP_DISABLE_AUTO_PAIRING, "audi"));
   EXPECT_FALSE(interop_match_name(INTEROP_AUTO_RETRY_PAIRING, "BMW M3"));
 }
+
+TEST(InteropTest, test_range_hit) {
+  RawAddress test_address;
+  RawAddress::FromString("00:0f:59:50:00:00", test_address);
+  ASSERT_TRUE(
+      interop_match_addr(INTEROP_DISABLE_ABSOLUTE_VOLUME, &test_address));
+  RawAddress::FromString("00:0f:59:59:12:34", test_address);
+  ASSERT_TRUE(
+      interop_match_addr(INTEROP_DISABLE_ABSOLUTE_VOLUME, &test_address));
+  RawAddress::FromString("00:0f:59:6f:ff:ff", test_address);
+  ASSERT_TRUE(
+      interop_match_addr(INTEROP_DISABLE_ABSOLUTE_VOLUME, &test_address));
+}
+
+TEST(InteropTest, test_range_miss) {
+  RawAddress test_address;
+  RawAddress::FromString("00:0f:59:49:12:34", test_address);
+  ASSERT_FALSE(
+      interop_match_addr(INTEROP_DISABLE_ABSOLUTE_VOLUME, &test_address));
+  RawAddress::FromString("00:0f:59:70:12:34", test_address);
+  ASSERT_FALSE(
+      interop_match_addr(INTEROP_DISABLE_ABSOLUTE_VOLUME, &test_address));
+}
diff --git a/system/gd/Android.bp b/system/gd/Android.bp
index ed6c375..d2ecc50 100644
--- a/system/gd/Android.bp
+++ b/system/gd/Android.bp
@@ -394,6 +394,7 @@
         ":BluetoothShimTestSources",
         ":BluetoothSecurityUnitTestSources",
         ":BluetoothStorageUnitTestSources",
+        ":BluetoothBtaaSources_linux_generic_tests",
     ],
     generated_headers: [
         "BluetoothGeneratedBundlerSchema_h_bfbs",
diff --git a/system/gd/btaa/Android.bp b/system/gd/btaa/Android.bp
index 2aea7ee..6b4c269 100644
--- a/system/gd/btaa/Android.bp
+++ b/system/gd/btaa/Android.bp
@@ -30,3 +30,10 @@
         "linux_generic/wakelock_processor.cc",
     ],
 }
+
+filegroup {
+    name: "BluetoothBtaaSources_linux_generic_tests",
+    srcs: [
+        "linux_generic/attribution_processor_tests.cc",
+    ],
+}
diff --git a/system/gd/btaa/attribution_processor.h b/system/gd/btaa/attribution_processor.h
index d18a8ce..e28e6cb 100644
--- a/system/gd/btaa/attribution_processor.h
+++ b/system/gd/btaa/attribution_processor.h
@@ -82,7 +82,20 @@
   void Dump(
       std::promise<flatbuffers::Offset<ActivityAttributionData>> promise, flatbuffers::FlatBufferBuilder* fb_builder);
 
+  using ClockType = std::chrono::time_point<std::chrono::system_clock>;
+  using NowFunc = ClockType (*)();
+
+  // by default, we use the std::chrono::system_clock::now implementation to
+  // get the current timestamp
+  AttributionProcessor() : now_func_(std::chrono::system_clock::now) {}
+  // in other cases, we may need to use different implementation
+  // e.g., for testing purposes
+  AttributionProcessor(NowFunc func) : now_func_(func) {}
+
  private:
+  // this function is added for testing support in
+  // OnWakelockReleased
+  NowFunc now_func_ = std::chrono::system_clock::now;
   bool wakeup_ = false;
   std::unordered_map<AddressActivityKey, BtaaAggregationEntry, AddressActivityKeyHasher> btaa_aggregator_;
   std::unordered_map<AddressActivityKey, BtaaAggregationEntry, AddressActivityKeyHasher> wakelock_duration_aggregator_;
diff --git a/system/gd/btaa/linux_generic/attribution_processor.cc b/system/gd/btaa/linux_generic/attribution_processor.cc
index 62e62ae..eadad9d 100644
--- a/system/gd/btaa/linux_generic/attribution_processor.cc
+++ b/system/gd/btaa/linux_generic/attribution_processor.cc
@@ -69,7 +69,7 @@
     return;
   }
 
-  auto cur_time = std::chrono::system_clock::now();
+  auto cur_time = now_func_();
   for (auto& it : wakelock_duration_aggregator_) {
     it.second.wakelock_duration_ms = (uint64_t)duration_ms * it.second.byte_count / total_byte_count;
     if (btaa_aggregator_.find(it.first) == btaa_aggregator_.end()) {
@@ -126,23 +126,29 @@
   }
   // Trim down the transient entries in the aggregator to avoid that it overgrows
   if (btaa_aggregator_.size() > kMapSizeTrimDownAggregationEntry) {
-    for (auto& it : btaa_aggregator_) {
+    auto it = btaa_aggregator_.begin();
+    while (it != btaa_aggregator_.end()) {
       auto elapsed_time_sec =
-          std::chrono::duration_cast<std::chrono::seconds>(cur_time - it.second.creation_time).count();
+          std::chrono::duration_cast<std::chrono::seconds>(cur_time - it->second.creation_time).count();
       if (elapsed_time_sec > kDurationTransientDeviceActivityEntrySecs &&
-          it.second.byte_count < kByteCountTransientDeviceActivityEntry) {
-        btaa_aggregator_.erase(it.first);
+          it->second.byte_count < kByteCountTransientDeviceActivityEntry) {
+        it = btaa_aggregator_.erase(it);
+      } else {
+        it++;
       }
     }
   }
 
   if (app_activity_aggregator_.size() > kMapSizeTrimDownAggregationEntry) {
-    for (auto& it : app_activity_aggregator_) {
+    auto it = app_activity_aggregator_.begin();
+    while (it != app_activity_aggregator_.end()) {
       auto elapsed_time_sec =
-          std::chrono::duration_cast<std::chrono::seconds>(cur_time - it.second.creation_time).count();
+          std::chrono::duration_cast<std::chrono::seconds>(cur_time - it->second.creation_time).count();
       if (elapsed_time_sec > kDurationTransientDeviceActivityEntrySecs &&
-          it.second.byte_count < kByteCountTransientDeviceActivityEntry) {
-        app_activity_aggregator_.erase(it.first);
+          it->second.byte_count < kByteCountTransientDeviceActivityEntry) {
+        it = app_activity_aggregator_.erase(it);
+      } else {
+        it++;
       }
     }
   }
diff --git a/system/gd/btaa/linux_generic/attribution_processor_tests.cc b/system/gd/btaa/linux_generic/attribution_processor_tests.cc
new file mode 100644
index 0000000..1a27206
--- /dev/null
+++ b/system/gd/btaa/linux_generic/attribution_processor_tests.cc
@@ -0,0 +1,85 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <base/strings/stringprintf.h>
+#include <gtest/gtest.h>
+#include <unistd.h>
+
+#include <chrono>
+#include <functional>
+#include <memory>
+#include <vector>
+
+#include "btaa/activity_attribution.h"
+#include "btaa/attribution_processor.h"
+
+using bluetooth::hci::Address;
+using namespace bluetooth::activity_attribution;
+using namespace std::chrono;
+
+// mock for std::chrono::system_clock::now
+static AttributionProcessor::ClockType now_ret_val;
+static AttributionProcessor::ClockType fake_now() {
+  return now_ret_val;
+}
+
+class AttributionProcessorTest : public ::testing::Test {
+ protected:
+  void SetUp() override {
+    pAttProc = std::make_unique<AttributionProcessor>(fake_now);
+  }
+  void TearDown() override {
+    pAttProc.reset();
+  }
+
+  std::unique_ptr<AttributionProcessor> pAttProc;
+};
+
+static void fake_now_set_current() {
+  now_ret_val = system_clock::now();
+}
+
+static void fake_now_advance_1000sec() {
+  now_ret_val += seconds(1000s);
+}
+
+TEST_F(AttributionProcessorTest, UAFInOnWakelockReleasedRegressionTest) {
+  std::vector<BtaaHciPacket> btaaPackets;
+  Address addr;
+
+  fake_now_set_current();
+
+  // setup the condition 1 for triggering erase operation
+  // add 220 entries in app_activity_aggregator_
+  // and btaa_aggregator_
+  for (int i = 0; i < 220; i++) {
+    std::string addrStr = base::StringPrintf("21:43:65:87:a9:%02x", i + 10);
+    ASSERT_TRUE(Address::FromString(addrStr, addr));
+    BtaaHciPacket packet(Activity::ACL, addr, 30 * i);
+    btaaPackets.push_back(packet);
+    pAttProc->NotifyActivityAttributionInfo(i + 1000, "com.test.app" + std::to_string(i), addrStr);
+  }
+
+  pAttProc->OnBtaaPackets(btaaPackets);
+  pAttProc->OnWakelockReleased(100);
+
+  // setup the condition 2 for triggering erase operation
+  // make elapsed_time_sec > 900s
+  fake_now_advance_1000sec();
+
+  pAttProc->OnBtaaPackets(btaaPackets);
+  pAttProc->OnWakelockReleased(100);
+}
diff --git a/system/gd/common/circular_buffer_test.cc b/system/gd/common/circular_buffer_test.cc
index dc238c2..9a2ef4e 100644
--- a/system/gd/common/circular_buffer_test.cc
+++ b/system/gd/common/circular_buffer_test.cc
@@ -68,6 +68,7 @@
 }
 
 TEST(CircularBufferTest, test_timestamps) {
+  timestamp_ = 0;
   bluetooth::common::TimestampedCircularBuffer<std::string> buffer(10, std::make_unique<TestTimestamper>());
 
   buffer.Push(std::string("One"));
diff --git a/system/gd/common/init_flags.fbs b/system/gd/common/init_flags.fbs
index d1d8c12..0953465 100644
--- a/system/gd/common/init_flags.fbs
+++ b/system/gd/common/init_flags.fbs
@@ -14,8 +14,10 @@
 
     asynchronously_start_l2cap_coc_is_enabled:bool (privacy:"Any");
     btaa_hci_is_enabled:bool (privacy:"Any");
+    bta_dm_clear_conn_id_on_client_close_is_enabled:bool (privacy:"Any");
     btm_dm_flush_discovery_queue_on_search_cancel_is_enabled:bool (privacy:"Any");
-    finite_att_timeout_is_enabled:bool (privacy:"Any");
+    clear_hidd_interrupt_cid_on_disconnect_is_enabled:bool (privacy:"Any");
+    delay_hidh_cleanup_until_hidh_ready_start_is_enabled:bool (privacy:"Any");
     gatt_robust_caching_client_is_enabled:bool (privacy:"Any");
     gatt_robust_caching_server_is_enabled:bool (privacy:"Any");
     gd_core_is_enabled:bool (privacy:"Any");
diff --git a/system/gd/common/interfaces/ILoggable.h b/system/gd/common/interfaces/ILoggable.h
new file mode 100644
index 0000000..ad2b8ab
--- /dev/null
+++ b/system/gd/common/interfaces/ILoggable.h
@@ -0,0 +1,45 @@
+/******************************************************************************
+ *
+ *  Copyright 2022 Google, Inc.
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at:
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ ******************************************************************************/
+
+#pragma once
+
+#include <string>
+
+namespace bluetooth {
+namespace common {
+
+class ILoggable {
+ public:
+  // the interface for
+  // converting an object to a string for feeding to loggers
+  // e.g.. logcat
+  virtual std::string ToStringForLogging() const = 0;
+  virtual ~ILoggable() = default;
+};
+
+class IRedactableLoggable : public ILoggable {
+ public:
+  // the interface for
+  // converting an object to a string with sensitive info redacted
+  // to avoid violating privacy
+  virtual std::string ToRedactedStringForLogging() const = 0;
+  virtual ~IRedactableLoggable() = default;
+};
+
+}  // namespace common
+}  // namespace bluetooth
diff --git a/system/gd/common/testing/host/log_capture_test.cc b/system/gd/common/testing/host/log_capture_test.cc
index cbd6a1c..320b4fe 100644
--- a/system/gd/common/testing/host/log_capture_test.cc
+++ b/system/gd/common/testing/host/log_capture_test.cc
@@ -62,7 +62,8 @@
   ASSERT_TRUE(log_capture->Size() == 0);
 }
 
-TEST_F(LogCaptureTest, truncate) {
+// b/260917913
+TEST_F(LogCaptureTest, DISABLED_truncate) {
   std::unique_ptr<LogCapture> log_capture = std::make_unique<LogCapture>();
 
   CalibrateOneLine(kLogError);
@@ -76,7 +77,8 @@
   ASSERT_EQ(size, log_capture->Size());
 }
 
-TEST_F(LogCaptureTest, log_size) {
+// b/260917913
+TEST_F(LogCaptureTest, DISABLED_log_size) {
   std::unique_ptr<LogCapture> log_capture = std::make_unique<LogCapture>();
 
   CalibrateOneLine(kEmptyLine);
@@ -101,7 +103,8 @@
   ASSERT_TRUE(log_capture->Rewind()->Find(kLogInfo));
 }
 
-TEST_F(LogCaptureTest, typical) {
+// b/260917913
+TEST_F(LogCaptureTest, DISABLED_typical) {
   bluetooth::common::InitFlags::Load(nullptr);
   std::unique_ptr<LogCapture> log_capture = std::make_unique<LogCapture>();
 
@@ -118,7 +121,8 @@
   ASSERT_FALSE(log_capture->Rewind()->Find(kLogVerbose));
 }
 
-TEST_F(LogCaptureTest, with_logging_debug_enabled_for_all) {
+// b/260917913
+TEST_F(LogCaptureTest, DISABLED_with_logging_debug_enabled_for_all) {
   bluetooth::common::InitFlags::Load(test_flags);
   std::unique_ptr<LogCapture> log_capture = std::make_unique<LogCapture>();
 
@@ -136,7 +140,8 @@
   bluetooth::common::InitFlags::Load(nullptr);
 }
 
-TEST_F(LogCaptureTest, wait_until_log_contains) {
+// b/260917913
+TEST_F(LogCaptureTest, DISABLED_wait_until_log_contains) {
   bluetooth::common::InitFlags::Load(test_flags);
   std::unique_ptr<LogCapture> log_capture = std::make_unique<LogCapture>();
 
diff --git a/system/gd/dumpsys/bundler/Android.bp b/system/gd/dumpsys/bundler/Android.bp
index 3754611..41f5549 100644
--- a/system/gd/dumpsys/bundler/Android.bp
+++ b/system/gd/dumpsys/bundler/Android.bp
@@ -69,9 +69,8 @@
     ],
 }
 
-cc_test {
+cc_test_host {
     name: "bluetooth_flatbuffer_bundler_test",
-    host_supported: true,
     srcs: [
         ":BluetoothFlatbufferBundlerTestSources",
     ],
diff --git a/system/gd/dumpsys/init_flags.cc b/system/gd/dumpsys/init_flags.cc
index 79e1d8d..0bf3cd0 100644
--- a/system/gd/dumpsys/init_flags.cc
+++ b/system/gd/dumpsys/init_flags.cc
@@ -35,10 +35,14 @@
 
   builder.add_asynchronously_start_l2cap_coc_is_enabled(initFlags::asynchronously_start_l2cap_coc_is_enabled());
   builder.add_btaa_hci_is_enabled(initFlags::btaa_hci_is_enabled());
+  builder.add_bta_dm_clear_conn_id_on_client_close_is_enabled(
+      initFlags::bta_dm_clear_conn_id_on_client_close_is_enabled());
   builder.add_btm_dm_flush_discovery_queue_on_search_cancel_is_enabled(
       initFlags::btm_dm_flush_discovery_queue_on_search_cancel_is_enabled());
-  builder.add_finite_att_timeout_is_enabled(initFlags::finite_att_timeout_is_enabled());
-  builder.add_gatt_robust_caching_client_is_enabled(initFlags::gatt_robust_caching_client_is_enabled());
+  builder.add_clear_hidd_interrupt_cid_on_disconnect_is_enabled(
+      initFlags::clear_hidd_interrupt_cid_on_disconnect_is_enabled());
+  builder.add_delay_hidh_cleanup_until_hidh_ready_start_is_enabled(
+      initFlags::delay_hidh_cleanup_until_hidh_ready_start_is_enabled());
   builder.add_gatt_robust_caching_server_is_enabled(initFlags::gatt_robust_caching_server_is_enabled());
   builder.add_gd_core_is_enabled(initFlags::gd_core_is_enabled());
   builder.add_gd_l2cap_is_enabled(initFlags::gd_l2cap_is_enabled());
diff --git a/system/gd/hal/snoop_logger.cc b/system/gd/hal/snoop_logger.cc
index 1b03033..e3b585c 100644
--- a/system/gd/hal/snoop_logger.cc
+++ b/system/gd/hal/snoop_logger.cc
@@ -193,6 +193,7 @@
 }  // namespace
 
 const std::string SnoopLogger::kBtSnoopLogModeDisabled = "disabled";
+const std::string SnoopLogger::kBtSnoopLogModeTruncated = "truncated";
 const std::string SnoopLogger::kBtSnoopLogModeFiltered = "filtered";
 const std::string SnoopLogger::kBtSnoopLogModeFull = "full";
 const std::string SnoopLogger::kSoCManufacturerQualcomm = "Qualcomm";
@@ -203,6 +204,10 @@
 const std::string SnoopLogger::kBtSnoopDefaultLogModeProperty = "persist.bluetooth.btsnoopdefaultmode";
 const std::string SnoopLogger::kSoCManufacturerProperty = "ro.soc.manufacturer";
 
+// The max ACL packet size (in bytes) in truncated logging mode. All information
+// past this point is truncated from a packet.
+static constexpr uint32_t kMaxTruncatedAclPacketSize = 100;
+
 SnoopLogger::SnoopLogger(
     std::string snoop_log_path,
     std::string snooz_log_path,
@@ -236,6 +241,15 @@
     delete_btsnoop_files(get_btsnoop_log_path(snoop_log_path_, true));
     // delete snooz logs
     delete_btsnoop_files(snooz_log_path_);
+  } else if (btsnoop_mode == kBtSnoopLogModeTruncated) {
+    LOG_INFO("Snoop Logs truncated. Limiting to %u", kMaxTruncatedAclPacketSize);
+    is_enabled_ = true;
+    is_truncated_ = true;
+    is_filtered_ = false;
+    // delete filtered logs
+    delete_btsnoop_files(get_btsnoop_log_path(snoop_log_path_, true));
+    // delete snooz logs
+    delete_btsnoop_files(snooz_log_path_);
   } else {
     LOG_INFO("Snoop Logs disabled");
     is_enabled_ = false;
@@ -320,6 +334,9 @@
                              .dropped_packets = 0,
                              .timestamp = htonll(timestamp_us + kBtSnoopEpochDelta),
                              .type = static_cast<uint8_t>(type)};
+  if (is_truncated_ && type == PacketType::ACL) {
+    header.length_captured = htonl(std::min(length, kMaxTruncatedAclPacketSize));
+  }
   {
     std::lock_guard<std::recursive_mutex> lock(file_mutex_);
     if (!is_enabled_) {
@@ -445,9 +462,8 @@
 size_t SnoopLogger::GetMaxPacketsPerBuffer() {
   // We want to use at most 256 KB memory for btsnooz log for release builds
   // and 512 KB memory for userdebug/eng builds
-  auto is_debuggable = os::GetSystemProperty(kIsDebuggableProperty);
-  size_t btsnooz_max_memory_usage_bytes =
-      ((is_debuggable.has_value() && common::StringTrim(is_debuggable.value()) == "1") ? 1024 : 256) * 1024;
+  auto is_debuggable = os::GetSystemPropertyBool(kIsDebuggableProperty, false);
+  size_t btsnooz_max_memory_usage_bytes = (is_debuggable ? 1024 : 256) * 1024;
   // Calculate max number of packets based on max memory usage and max packet size
   return btsnooz_max_memory_usage_bytes / kDefaultBtSnoozMaxBytesPerPacket;
 }
@@ -457,8 +473,8 @@
   // In userdebug/eng build, it can also be overwritten by modifying the global setting
   std::string default_mode = kBtSnoopLogModeDisabled;
   {
-    auto is_debuggable = os::GetSystemProperty(kIsDebuggableProperty);
-    if (is_debuggable.has_value() && common::StringTrim(is_debuggable.value()) == "1") {
+    auto is_debuggable = os::GetSystemPropertyBool(kIsDebuggableProperty, false);
+    if (is_debuggable) {
       auto default_mode_property = os::GetSystemProperty(kBtSnoopDefaultLogModeProperty);
       if (default_mode_property) {
         default_mode = std::move(default_mode_property.value());
diff --git a/system/gd/hal/snoop_logger.h b/system/gd/hal/snoop_logger.h
index 7fa65aa..f879798 100644
--- a/system/gd/hal/snoop_logger.h
+++ b/system/gd/hal/snoop_logger.h
@@ -38,6 +38,7 @@
   static const ModuleFactory Factory;
 
   static const std::string kBtSnoopLogModeDisabled;
+  static const std::string kBtSnoopLogModeTruncated;
   static const std::string kBtSnoopLogModeFiltered;
   static const std::string kBtSnoopLogModeFull;
   static const std::string kSoCManufacturerQualcomm;
@@ -124,6 +125,7 @@
   std::ofstream btsnoop_ostream_;
   bool is_enabled_ = false;
   bool is_filtered_ = false;
+  bool is_truncated_ = false;
   size_t max_packets_per_file_;
   common::CircularBuffer<std::string> btsnooz_buffer_;
   bool qualcomm_debug_log_enabled_ = false;
diff --git a/system/gd/hci/Android.bp b/system/gd/hci/Android.bp
index ae026fc..3dad0d6 100644
--- a/system/gd/hci/Android.bp
+++ b/system/gd/hci/Android.bp
@@ -33,32 +33,25 @@
 filegroup {
     name: "BluetoothHciUnitTestSources",
     srcs: [
-        "acl_manager/le_impl_test.cc",
         "acl_builder_test.cc",
+        "acl_manager_test.cc",
         "acl_manager_unittest.cc",
+        "acl_manager/classic_acl_connection_test.cc",
+        "acl_manager/le_impl_test.cc",
+        "acl_manager/round_robin_scheduler_test.cc",
         "address_unittest.cc",
         "address_with_type_test.cc",
         "class_of_device_unittest.cc",
         "controller_test.cc",
         "hci_layer_fake.cc",
+        "hci_layer_test.cc",
         "hci_layer_unittest.cc",
         "hci_packets_test.cc",
         "uuid_unittest.cc",
         "le_periodic_sync_manager_test.cc",
         "le_scanning_manager_test.cc",
         "le_advertising_manager_test.cc",
-    ],
-}
-
-filegroup {
-    name: "BluetoothHciTestSources",
-    srcs: [
-        "acl_manager/round_robin_scheduler_test.cc",
-        "acl_manager_test.cc",
-        "hci_layer_test.cc",
         "le_address_manager_test.cc",
-        "le_advertising_manager_test.cc",
-        "le_scanning_manager_test.cc",
     ],
 }
 
diff --git a/system/gd/hci/acl_manager/classic_acl_connection_test.cc b/system/gd/hci/acl_manager/classic_acl_connection_test.cc
new file mode 100644
index 0000000..9ba38c9
--- /dev/null
+++ b/system/gd/hci/acl_manager/classic_acl_connection_test.cc
@@ -0,0 +1,340 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "hci/acl_manager/classic_acl_connection.h"
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+#include <chrono>
+#include <cstdint>
+#include <future>
+#include <list>
+#include <memory>
+#include <mutex>
+#include <queue>
+#include <vector>
+
+#include "hci/acl_connection_interface.h"
+#include "hci/acl_manager/connection_management_callbacks.h"
+#include "hci/address.h"
+#include "hci/hci_packets.h"
+#include "os/handler.h"
+#include "os/log.h"
+#include "os/thread.h"
+
+using namespace bluetooth;
+using namespace std::chrono_literals;
+
+namespace {
+constexpr char kAddress[] = "00:11:22:33:44:55";
+constexpr uint16_t kConnectionHandle = 123;
+constexpr size_t kQueueSize = 10;
+
+std::vector<hci::DisconnectReason> disconnect_reason_vector = {
+    hci::DisconnectReason::AUTHENTICATION_FAILURE,
+    hci::DisconnectReason::REMOTE_USER_TERMINATED_CONNECTION,
+    hci::DisconnectReason::REMOTE_DEVICE_TERMINATED_CONNECTION_LOW_RESOURCES,
+    hci::DisconnectReason::REMOTE_DEVICE_TERMINATED_CONNECTION_POWER_OFF,
+    hci::DisconnectReason::UNSUPPORTED_REMOTE_FEATURE,
+    hci::DisconnectReason::PAIRING_WITH_UNIT_KEY_NOT_SUPPORTED,
+    hci::DisconnectReason::UNACCEPTABLE_CONNECTION_PARAMETERS,
+};
+
+std::vector<hci::ErrorCode> error_code_vector = {
+    hci::ErrorCode::SUCCESS,
+    hci::ErrorCode::UNKNOWN_HCI_COMMAND,
+    hci::ErrorCode::UNKNOWN_CONNECTION,
+    hci::ErrorCode::HARDWARE_FAILURE,
+    hci::ErrorCode::PAGE_TIMEOUT,
+    hci::ErrorCode::AUTHENTICATION_FAILURE,
+    hci::ErrorCode::PIN_OR_KEY_MISSING,
+    hci::ErrorCode::MEMORY_CAPACITY_EXCEEDED,
+    hci::ErrorCode::CONNECTION_TIMEOUT,
+    hci::ErrorCode::CONNECTION_LIMIT_EXCEEDED,
+    hci::ErrorCode::SYNCHRONOUS_CONNECTION_LIMIT_EXCEEDED,
+    hci::ErrorCode::CONNECTION_ALREADY_EXISTS,
+    hci::ErrorCode::COMMAND_DISALLOWED,
+    hci::ErrorCode::CONNECTION_REJECTED_LIMITED_RESOURCES,
+    hci::ErrorCode::CONNECTION_REJECTED_SECURITY_REASONS,
+    hci::ErrorCode::CONNECTION_REJECTED_UNACCEPTABLE_BD_ADDR,
+    hci::ErrorCode::CONNECTION_ACCEPT_TIMEOUT,
+    hci::ErrorCode::UNSUPPORTED_FEATURE_OR_PARAMETER_VALUE,
+    hci::ErrorCode::INVALID_HCI_COMMAND_PARAMETERS,
+    hci::ErrorCode::REMOTE_USER_TERMINATED_CONNECTION,
+    hci::ErrorCode::REMOTE_DEVICE_TERMINATED_CONNECTION_LOW_RESOURCES,
+    hci::ErrorCode::REMOTE_DEVICE_TERMINATED_CONNECTION_POWER_OFF,
+    hci::ErrorCode::CONNECTION_TERMINATED_BY_LOCAL_HOST,
+    hci::ErrorCode::REPEATED_ATTEMPTS,
+    hci::ErrorCode::PAIRING_NOT_ALLOWED,
+    hci::ErrorCode::UNKNOWN_LMP_PDU,
+    hci::ErrorCode::UNSUPPORTED_REMOTE_OR_LMP_FEATURE,
+    hci::ErrorCode::SCO_OFFSET_REJECTED,
+    hci::ErrorCode::SCO_INTERVAL_REJECTED,
+    hci::ErrorCode::SCO_AIR_MODE_REJECTED,
+    hci::ErrorCode::INVALID_LMP_OR_LL_PARAMETERS,
+    hci::ErrorCode::UNSPECIFIED_ERROR,
+    hci::ErrorCode::UNSUPPORTED_LMP_OR_LL_PARAMETER,
+    hci::ErrorCode::ROLE_CHANGE_NOT_ALLOWED,
+    hci::ErrorCode::TRANSACTION_RESPONSE_TIMEOUT,
+    hci::ErrorCode::LINK_LAYER_COLLISION,
+    hci::ErrorCode::ENCRYPTION_MODE_NOT_ACCEPTABLE,
+    hci::ErrorCode::ROLE_SWITCH_FAILED,
+    hci::ErrorCode::CONTROLLER_BUSY,
+    hci::ErrorCode::ADVERTISING_TIMEOUT,
+    hci::ErrorCode::CONNECTION_FAILED_ESTABLISHMENT,
+    hci::ErrorCode::LIMIT_REACHED,
+    hci::ErrorCode::STATUS_UNKNOWN,
+};
+
+// Generic template for all commands
+template <typename T, typename U>
+T CreateCommand(U u) {
+  T command;
+  return command;
+}
+
+template <>
+hci::DisconnectView CreateCommand(std::shared_ptr<std::vector<uint8_t>> bytes) {
+  return hci::DisconnectView::Create(
+      hci::AclCommandView::Create(hci::CommandView::Create(hci::PacketView<hci::kLittleEndian>(bytes))));
+}
+
+}  // namespace
+
+class TestAclConnectionInterface : public hci::AclConnectionInterface {
+ private:
+  void EnqueueCommand(
+      std::unique_ptr<hci::AclCommandBuilder> command,
+      common::ContextualOnceCallback<void(hci::CommandStatusView)> on_status) override {
+    const std::lock_guard<std::mutex> lock(command_queue_mutex_);
+    command_queue_.push(std::move(command));
+    command_status_callbacks.push_back(std::move(on_status));
+    if (command_promise_ != nullptr) {
+      std::promise<void>* prom = command_promise_.release();
+      prom->set_value();
+      delete prom;
+    }
+  }
+
+  void EnqueueCommand(
+      std::unique_ptr<hci::AclCommandBuilder> command,
+      common::ContextualOnceCallback<void(hci::CommandCompleteView)> on_complete) override {
+    const std::lock_guard<std::mutex> lock(command_queue_mutex_);
+    command_queue_.push(std::move(command));
+    command_complete_callbacks.push_back(std::move(on_complete));
+    if (command_promise_ != nullptr) {
+      std::promise<void>* prom = command_promise_.release();
+      prom->set_value();
+      delete prom;
+    }
+  }
+
+ public:
+  virtual ~TestAclConnectionInterface() = default;
+
+  std::unique_ptr<hci::CommandBuilder> DequeueCommand() {
+    const std::lock_guard<std::mutex> lock(command_queue_mutex_);
+    auto packet = std::move(command_queue_.front());
+    command_queue_.pop();
+    return std::move(packet);
+  }
+
+  std::shared_ptr<std::vector<uint8_t>> DequeueCommandBytes() {
+    auto command = DequeueCommand();
+    auto bytes = std::make_shared<std::vector<uint8_t>>();
+    packet::BitInserter bi(*bytes);
+    command->Serialize(bi);
+    return bytes;
+  }
+
+  bool IsPacketQueueEmpty() const {
+    const std::lock_guard<std::mutex> lock(command_queue_mutex_);
+    return command_queue_.empty();
+  }
+
+  size_t NumberOfQueuedCommands() const {
+    const std::lock_guard<std::mutex> lock(command_queue_mutex_);
+    return command_queue_.size();
+  }
+
+ private:
+  std::list<common::ContextualOnceCallback<void(hci::CommandCompleteView)>> command_complete_callbacks;
+  std::list<common::ContextualOnceCallback<void(hci::CommandStatusView)>> command_status_callbacks;
+  std::queue<std::unique_ptr<hci::CommandBuilder>> command_queue_;
+  mutable std::mutex command_queue_mutex_;
+  std::unique_ptr<std::promise<void>> command_promise_;
+  std::unique_ptr<std::future<void>> command_future_;
+};
+
+class TestConnectionManagementCallbacks : public hci::acl_manager::ConnectionManagementCallbacks {
+ public:
+  ~TestConnectionManagementCallbacks() = default;
+  void OnConnectionPacketTypeChanged(uint16_t packet_type) override {}
+  void OnAuthenticationComplete(hci::ErrorCode hci_status) override {}
+  void OnEncryptionChange(hci::EncryptionEnabled enabled) override {}
+  void OnChangeConnectionLinkKeyComplete() override {}
+  void OnReadClockOffsetComplete(uint16_t clock_offset) override {}
+  void OnModeChange(hci::ErrorCode status, hci::Mode current_mode, uint16_t interval) override {}
+  void OnSniffSubrating(
+      hci::ErrorCode hci_status,
+      uint16_t maximum_transmit_latency,
+      uint16_t maximum_receive_latency,
+      uint16_t minimum_remote_timeout,
+      uint16_t minimum_local_timeout) override {}
+  void OnQosSetupComplete(
+      hci::ServiceType service_type,
+      uint32_t token_rate,
+      uint32_t peak_bandwidth,
+      uint32_t latency,
+      uint32_t delay_variation) override {}
+  void OnFlowSpecificationComplete(
+      hci::FlowDirection flow_direction,
+      hci::ServiceType service_type,
+      uint32_t token_rate,
+      uint32_t token_bucket_size,
+      uint32_t peak_bandwidth,
+      uint32_t access_latency) override {}
+  void OnFlushOccurred() override {}
+  void OnRoleDiscoveryComplete(hci::Role current_role) override {}
+  void OnReadLinkPolicySettingsComplete(uint16_t link_policy_settings) override {}
+  void OnReadAutomaticFlushTimeoutComplete(uint16_t flush_timeout) override {}
+  void OnReadTransmitPowerLevelComplete(uint8_t transmit_power_level) override {}
+  void OnReadLinkSupervisionTimeoutComplete(uint16_t link_supervision_timeout) override {}
+  void OnReadFailedContactCounterComplete(uint16_t failed_contact_counter) override {}
+  void OnReadLinkQualityComplete(uint8_t link_quality) override {}
+  void OnReadAfhChannelMapComplete(hci::AfhMode afh_mode, std::array<uint8_t, 10> afh_channel_map) override {}
+  void OnReadRssiComplete(uint8_t rssi) override {}
+  void OnReadClockComplete(uint32_t clock, uint16_t accuracy) override {}
+  void OnCentralLinkKeyComplete(hci::KeyFlag key_flag) override {}
+  void OnRoleChange(hci::ErrorCode hci_status, hci::Role new_role) override {}
+  void OnDisconnection(hci::ErrorCode reason) override {
+    on_disconnection_error_code_queue_.push(reason);
+  }
+  void OnReadRemoteVersionInformationComplete(
+      hci::ErrorCode hci_status, uint8_t lmp_version, uint16_t manufacturer_name, uint16_t sub_version) override {}
+  void OnReadRemoteSupportedFeaturesComplete(uint64_t features) override {}
+  void OnReadRemoteExtendedFeaturesComplete(uint8_t page_number, uint8_t max_page_number, uint64_t features) override {}
+
+  std::queue<hci::ErrorCode> on_disconnection_error_code_queue_;
+};
+
+namespace bluetooth {
+namespace hci {
+namespace acl_manager {
+
+class ClassicAclConnectionTest : public ::testing::Test {
+ protected:
+  void SetUp() override {
+    ASSERT_TRUE(hci::Address::FromString(kAddress, address_));
+    thread_ = new os::Thread("thread", os::Thread::Priority::NORMAL);
+    handler_ = new os::Handler(thread_);
+    queue_ = std::make_shared<hci::acl_manager::AclConnection::Queue>(kQueueSize);
+    sync_handler();
+  }
+
+  void TearDown() override {
+    handler_->Clear();
+    delete handler_;
+    delete thread_;
+  }
+
+  void sync_handler() {
+    ASSERT(handler_ != nullptr);
+
+    auto promise = std::promise<void>();
+    auto future = promise.get_future();
+    handler_->BindOnceOn(&promise, &std::promise<void>::set_value).Invoke();
+    auto status = future.wait_for(2s);
+    ASSERT_EQ(status, std::future_status::ready);
+  }
+
+  Address address_;
+  os::Handler* handler_{nullptr};
+  os::Thread* thread_{nullptr};
+  std::shared_ptr<hci::acl_manager::AclConnection::Queue> queue_;
+
+  TestAclConnectionInterface acl_connection_interface_;
+  TestConnectionManagementCallbacks callbacks_;
+};
+
+TEST_F(ClassicAclConnectionTest, simple) {
+  AclConnectionInterface* acl_connection_interface = nullptr;
+  ClassicAclConnection* connection =
+      new ClassicAclConnection(queue_, acl_connection_interface, kConnectionHandle, address_);
+  connection->RegisterCallbacks(&callbacks_, handler_);
+
+  delete connection;
+}
+
+class ClassicAclConnectionWithCallbacksTest : public ClassicAclConnectionTest {
+ protected:
+  void SetUp() override {
+    ClassicAclConnectionTest::SetUp();
+    connection_ =
+        std::make_unique<ClassicAclConnection>(queue_, &acl_connection_interface_, kConnectionHandle, address_);
+    connection_->RegisterCallbacks(&callbacks_, handler_);
+    is_callbacks_registered_ = true;
+    connection_management_callbacks_ =
+        connection_->GetEventCallbacks([this](uint16_t hci_handle) { is_callbacks_invalidated_ = true; });
+    is_callbacks_invalidated_ = false;
+  }
+
+  void TearDown() override {
+    connection_.reset();
+    ASSERT_TRUE(is_callbacks_invalidated_);
+    ClassicAclConnectionTest::TearDown();
+  }
+
+ protected:
+  std::unique_ptr<ClassicAclConnection> connection_;
+  ConnectionManagementCallbacks* connection_management_callbacks_;
+  bool is_callbacks_registered_{false};
+  bool is_callbacks_invalidated_{false};
+};
+
+TEST_F(ClassicAclConnectionWithCallbacksTest, Disconnect) {
+  for (const auto& reason : disconnect_reason_vector) {
+    ASSERT_TRUE(connection_->Disconnect(reason));
+  }
+
+  for (const auto& reason : disconnect_reason_vector) {
+    ASSERT_FALSE(acl_connection_interface_.IsPacketQueueEmpty());
+    auto command = CreateCommand<DisconnectView>(acl_connection_interface_.DequeueCommandBytes());
+    ASSERT_TRUE(command.IsValid());
+    ASSERT_EQ(reason, command.GetReason());
+    ASSERT_EQ(kConnectionHandle, command.GetConnectionHandle());
+  }
+  ASSERT_TRUE(acl_connection_interface_.IsPacketQueueEmpty());
+}
+
+TEST_F(ClassicAclConnectionWithCallbacksTest, OnDisconnection) {
+  for (const auto& error_code : error_code_vector) {
+    connection_management_callbacks_->OnDisconnection(error_code);
+  }
+
+  sync_handler();
+  ASSERT_TRUE(!callbacks_.on_disconnection_error_code_queue_.empty());
+
+  for (const auto& error_code : error_code_vector) {
+    ASSERT_EQ(error_code, callbacks_.on_disconnection_error_code_queue_.front());
+    callbacks_.on_disconnection_error_code_queue_.pop();
+  }
+}
+
+}  // namespace acl_manager
+}  // namespace hci
+}  // namespace bluetooth
diff --git a/system/gd/hci/acl_manager/le_impl_test.cc b/system/gd/hci/acl_manager/le_impl_test.cc
index ca9d022..d84f95b 100644
--- a/system/gd/hci/acl_manager/le_impl_test.cc
+++ b/system/gd/hci/acl_manager/le_impl_test.cc
@@ -62,12 +62,14 @@
 [[maybe_unused]] constexpr bool kSkipFilterAcceptList = !kAddToFilterAcceptList;
 [[maybe_unused]] constexpr bool kIsDirectConnection = true;
 [[maybe_unused]] constexpr bool kIsBackgroundConnection = !kIsDirectConnection;
-constexpr ::bluetooth::crypto_toolbox::Octet16 kRotationIrk = {};
+constexpr crypto_toolbox::Octet16 kRotationIrk = {};
 constexpr std::chrono::milliseconds kMinimumRotationTime(14 * 1000);
 constexpr std::chrono::milliseconds kMaximumRotationTime(16 * 1000);
-constexpr uint16_t kIntervalMin = 0x20;
 constexpr uint16_t kIntervalMax = 0x40;
+constexpr uint16_t kIntervalMin = 0x20;
 constexpr uint16_t kLatency = 0x60;
+constexpr uint16_t kLength = 0x5678;
+constexpr uint16_t kTime = 0x1234;
 constexpr uint16_t kTimeout = 0x80;
 constexpr std::array<uint8_t, 16> kPeerIdentityResolvingKey({
     0x00,
@@ -106,11 +108,12 @@
     0x8f,
 });
 
-// Generic template for all commands
-template <typename T, typename U>
-T CreateCommand(U u) {
-  T command;
-  return command;
+template <typename B>
+std::shared_ptr<std::vector<uint8_t>> Serialize(std::unique_ptr<B> build) {
+  auto bytes = std::make_shared<std::vector<uint8_t>>();
+  BitInserter bi(*bytes);
+  build->Serialize(bi);
+  return bytes;
 }
 
 template <typename T>
@@ -140,20 +143,16 @@
 
 [[maybe_unused]] hci::CommandCompleteView ReturnCommandComplete(hci::OpCode op_code, hci::ErrorCode error_code) {
   std::vector<uint8_t> success_vector{static_cast<uint8_t>(error_code)};
-  auto bytes = std::make_shared<std::vector<uint8_t>>();
-  BitInserter bi(*bytes);
   auto builder = hci::CommandCompleteBuilder::Create(uint8_t{1}, op_code, std::make_unique<RawBuilder>(success_vector));
-  builder->Serialize(bi);
+  auto bytes = Serialize<hci::CommandCompleteBuilder>(std::move(builder));
   return hci::CommandCompleteView::Create(hci::EventView::Create(hci::PacketView<hci::kLittleEndian>(bytes)));
 }
 
 [[maybe_unused]] hci::CommandStatusView ReturnCommandStatus(hci::OpCode op_code, hci::ErrorCode error_code) {
   std::vector<uint8_t> success_vector{static_cast<uint8_t>(error_code)};
-  auto bytes = std::make_shared<std::vector<uint8_t>>();
-  BitInserter bi(*bytes);
   auto builder = hci::CommandStatusBuilder::Create(
       hci::ErrorCode::SUCCESS, uint8_t{1}, op_code, std::make_unique<RawBuilder>(success_vector));
-  builder->Serialize(bi);
+  auto bytes = Serialize<hci::CommandStatusBuilder>(std::move(builder));
   return hci::CommandStatusView::Create(hci::EventView::Create(hci::PacketView<hci::kLittleEndian>(bytes)));
 }
 
@@ -256,8 +255,9 @@
     command_queue_.push(std::move(command));
     command_status_callbacks.push_back(std::move(on_status));
     if (command_promise_ != nullptr) {
-      command_promise_->set_value();
-      command_promise_.reset();
+      std::promise<void>* prom = command_promise_.release();
+      prom->set_value();
+      delete prom;
     }
   }
 
@@ -268,8 +268,9 @@
     command_queue_.push(std::move(command));
     command_complete_callbacks.push_back(std::move(on_complete));
     if (command_promise_ != nullptr) {
-      command_promise_->set_value();
-      command_promise_.reset();
+      std::promise<void>* prom = command_promise_.release();
+      prom->set_value();
+      delete prom;
     }
   }
 
@@ -300,7 +301,7 @@
   }
 
   void SetCommandFuture() {
-    ASSERT_LOG(command_promise_ == nullptr, "Promises, Promises, ... Only one at a time.");
+    ASSERT_EQ(command_promise_, nullptr) << "Promises, Promises, ... Only one at a time.";
     command_promise_ = std::make_unique<std::promise<void>>();
     command_future_ = std::make_unique<std::future<void>>(command_promise_->get_future());
   }
@@ -425,6 +426,7 @@
 class LeImplTest : public ::testing::Test {
  protected:
   void SetUp() override {
+    bluetooth::common::InitFlags::SetAllForTesting();
     thread_ = new Thread("thread", Thread::Priority::NORMAL);
     handler_ = new Handler(thread_);
     controller_ = new TestController();
@@ -449,7 +451,7 @@
 
   void set_random_device_address_policy() {
     // Set address policy
-    hci_layer_->SetCommandFuture();
+    ASSERT_NO_FATAL_FAILURE(hci_layer_->SetCommandFuture());
     hci::Address address;
     Address::FromString("D0:05:04:03:02:01", address);
     hci::AddressWithType address_with_type(address, hci::AddressType::RANDOM_DEVICE_ADDRESS);
@@ -571,6 +573,7 @@
  protected:
   void SetUp() override {
     LeImplTest::SetUp();
+    set_random_device_address_policy();
 
     EXPECT_CALL(mock_le_connection_callbacks_, OnLeConnectSuccess(_, _))
         .WillOnce([&](AddressWithType addr, std::unique_ptr<LeAclConnection> conn) {
@@ -579,8 +582,6 @@
           connection_->RegisterCallbacks(&connection_management_callbacks_, handler_);
         });
 
-    auto bytes = std::make_shared<std::vector<uint8_t>>();
-    BitInserter bi(*bytes);
     auto command = LeEnhancedConnectionCompleteBuilder::Create(
         ErrorCode::SUCCESS,
         kHciHandle,
@@ -593,7 +594,7 @@
         0x0000,
         0x0011,
         ClockAccuracy::PPM_30);
-    command->Serialize(bi);
+    auto bytes = Serialize<LeEnhancedConnectionCompleteBuilder>(std::move(command));
     auto view = CreateLeEventView<hci::LeEnhancedConnectionCompleteView>(bytes);
     ASSERT_TRUE(view.IsValid());
     le_impl_->on_le_event(view);
@@ -603,6 +604,7 @@
   }
 
   void TearDown() override {
+    connection_.reset();
     LeImplTest::TearDown();
   }
 
@@ -652,11 +654,11 @@
   set_random_device_address_policy();
 
   // Create connection
-  hci_layer_->SetCommandFuture();
+  ASSERT_NO_FATAL_FAILURE(hci_layer_->SetCommandFuture());
   le_impl_->create_le_connection(
       {{0x21, 0x22, 0x23, 0x24, 0x25, 0x26}, AddressType::PUBLIC_DEVICE_ADDRESS}, true, false);
   hci_layer_->GetCommand(OpCode::LE_ADD_DEVICE_TO_FILTER_ACCEPT_LIST);
-  hci_layer_->SetCommandFuture();
+  ASSERT_NO_FATAL_FAILURE(hci_layer_->SetCommandFuture());
   hci_layer_->CommandCompleteCallback(LeAddDeviceToFilterAcceptListCompleteBuilder::Create(0x01, ErrorCode::SUCCESS));
   hci_layer_->GetCommand(OpCode::LE_CREATE_CONNECTION);
   hci_layer_->CommandStatusCallback(LeCreateConnectionStatusBuilder::Create(ErrorCode::SUCCESS, 0x01));
@@ -669,7 +671,7 @@
   hci::Address remote_address;
   Address::FromString("D0:05:04:03:02:01", remote_address);
   hci::AddressWithType address_with_type(remote_address, hci::AddressType::PUBLIC_DEVICE_ADDRESS);
-  EXPECT_CALL(mock_le_connection_callbacks_, OnLeConnectSuccess(address_with_type, ::testing::_));
+  EXPECT_CALL(mock_le_connection_callbacks_, OnLeConnectSuccess(address_with_type, _));
   hci_layer_->IncomingLeMetaEvent(LeConnectionCompleteBuilder::Create(
       ErrorCode::SUCCESS,
       0x0041,
@@ -691,11 +693,11 @@
 
   controller_->AddSupported(OpCode::LE_EXTENDED_CREATE_CONNECTION);
   // Create connection
-  hci_layer_->SetCommandFuture();
+  ASSERT_NO_FATAL_FAILURE(hci_layer_->SetCommandFuture());
   le_impl_->create_le_connection(
       {{0x21, 0x22, 0x23, 0x24, 0x25, 0x26}, AddressType::PUBLIC_DEVICE_ADDRESS}, true, false);
   hci_layer_->GetCommand(OpCode::LE_ADD_DEVICE_TO_FILTER_ACCEPT_LIST);
-  hci_layer_->SetCommandFuture();
+  ASSERT_NO_FATAL_FAILURE(hci_layer_->SetCommandFuture());
   hci_layer_->CommandCompleteCallback(LeAddDeviceToFilterAcceptListCompleteBuilder::Create(0x01, ErrorCode::SUCCESS));
   hci_layer_->GetCommand(OpCode::LE_EXTENDED_CREATE_CONNECTION);
   hci_layer_->CommandStatusCallback(LeExtendedCreateConnectionStatusBuilder::Create(ErrorCode::SUCCESS, 0x01));
@@ -708,7 +710,7 @@
   hci::Address remote_address;
   Address::FromString("D0:05:04:03:02:01", remote_address);
   hci::AddressWithType address_with_type(remote_address, hci::AddressType::PUBLIC_DEVICE_ADDRESS);
-  EXPECT_CALL(mock_le_connection_callbacks_, OnLeConnectSuccess(address_with_type, ::testing::_));
+  EXPECT_CALL(mock_le_connection_callbacks_, OnLeConnectSuccess(address_with_type, _));
   hci_layer_->IncomingLeMetaEvent(LeEnhancedConnectionCompleteBuilder::Create(
       ErrorCode::SUCCESS,
       0x0041,
@@ -734,10 +736,10 @@
   Address::FromString("D0:05:04:03:02:01", remote_address);
   hci::AddressWithType address_with_type(remote_address, hci::AddressType::PUBLIC_DEVICE_ADDRESS);
   // Create connection
-  hci_layer_->SetCommandFuture();
+  ASSERT_NO_FATAL_FAILURE(hci_layer_->SetCommandFuture());
   le_impl_->create_le_connection(address_with_type, true, false);
   hci_layer_->GetCommand(OpCode::LE_ADD_DEVICE_TO_FILTER_ACCEPT_LIST);
-  hci_layer_->SetCommandFuture();
+  ASSERT_NO_FATAL_FAILURE(hci_layer_->SetCommandFuture());
   hci_layer_->CommandCompleteCallback(LeAddDeviceToFilterAcceptListCompleteBuilder::Create(0x01, ErrorCode::SUCCESS));
   hci_layer_->GetCommand(OpCode::LE_CREATE_CONNECTION);
   hci_layer_->CommandStatusCallback(LeCreateConnectionStatusBuilder::Create(ErrorCode::SUCCESS, 0x01));
@@ -747,7 +749,7 @@
   ASSERT_EQ(ConnectabilityState::ARMED, le_impl_->connectability_state_);
 
   // Receive connection complete of outgoing connection (Role::CENTRAL)
-  EXPECT_CALL(mock_le_connection_callbacks_, OnLeConnectSuccess(address_with_type, ::testing::_));
+  EXPECT_CALL(mock_le_connection_callbacks_, OnLeConnectSuccess(address_with_type, _));
   hci_layer_->IncomingLeMetaEvent(LeConnectionCompleteBuilder::Create(
       ErrorCode::SUCCESS,
       0x0041,
@@ -772,10 +774,10 @@
   Address::FromString("D0:05:04:03:02:01", remote_address);
   hci::AddressWithType address_with_type(remote_address, hci::AddressType::PUBLIC_DEVICE_ADDRESS);
   // Create connection
-  hci_layer_->SetCommandFuture();
+  ASSERT_NO_FATAL_FAILURE(hci_layer_->SetCommandFuture());
   le_impl_->create_le_connection(address_with_type, true, false);
   hci_layer_->GetCommand(OpCode::LE_ADD_DEVICE_TO_FILTER_ACCEPT_LIST);
-  hci_layer_->SetCommandFuture();
+  ASSERT_NO_FATAL_FAILURE(hci_layer_->SetCommandFuture());
   hci_layer_->CommandCompleteCallback(LeAddDeviceToFilterAcceptListCompleteBuilder::Create(0x01, ErrorCode::SUCCESS));
   hci_layer_->GetCommand(OpCode::LE_EXTENDED_CREATE_CONNECTION);
   hci_layer_->CommandStatusCallback(LeExtendedCreateConnectionStatusBuilder::Create(ErrorCode::SUCCESS, 0x01));
@@ -785,7 +787,7 @@
   ASSERT_EQ(ConnectabilityState::ARMED, le_impl_->connectability_state_);
 
   // Receive connection complete of outgoing connection (Role::CENTRAL)
-  EXPECT_CALL(mock_le_connection_callbacks_, OnLeConnectSuccess(address_with_type, ::testing::_));
+  EXPECT_CALL(mock_le_connection_callbacks_, OnLeConnectSuccess(address_with_type, _));
   hci_layer_->IncomingLeMetaEvent(LeEnhancedConnectionCompleteBuilder::Create(
       ErrorCode::SUCCESS,
       0x0041,
@@ -804,8 +806,8 @@
   ASSERT_EQ(ConnectabilityState::DISARMED, le_impl_->connectability_state_);
 }
 
-TEST_F(LeImplTest, register_with_address_manager__AddressPolicyNotSet) {
-  bluetooth::common::InitFlags::SetAllForTesting();
+// b/260917913
+TEST_F(LeImplTest, DISABLED_register_with_address_manager__AddressPolicyNotSet) {
   auto log_capture = std::make_unique<LogCapture>();
 
   std::promise<void> promise;
@@ -857,8 +859,8 @@
       std::move(log_capture)));
 }
 
-TEST_F(LeImplTest, disarm_connectability_DISARMED) {
-  bluetooth::common::InitFlags::SetAllForTesting();
+// b/260917913
+TEST_F(LeImplTest, DISABLED_disarm_connectability_DISARMED) {
   std::unique_ptr<LogCapture> log_capture = std::make_unique<LogCapture>();
 
   le_impl_->connectability_state_ = ConnectabilityState::DISARMED;
@@ -871,8 +873,8 @@
   ASSERT_TRUE(log_capture->Rewind()->Find("in unexpected state:ConnectabilityState::DISARMED"));
 }
 
-TEST_F(LeImplTest, disarm_connectability_DISARMED_extended) {
-  bluetooth::common::InitFlags::SetAllForTesting();
+// b/260917913
+TEST_F(LeImplTest, DISABLED_disarm_connectability_DISARMED_extended) {
   std::unique_ptr<LogCapture> log_capture = std::make_unique<LogCapture>();
 
   le_impl_->connectability_state_ = ConnectabilityState::DISARMED;
@@ -886,8 +888,8 @@
   ASSERT_TRUE(log_capture->Rewind()->Find("in unexpected state:ConnectabilityState::DISARMED"));
 }
 
-TEST_F(LeImplTest, disarm_connectability_ARMING) {
-  bluetooth::common::InitFlags::SetAllForTesting();
+// b/260917913
+TEST_F(LeImplTest, DISABLED_disarm_connectability_ARMING) {
   std::unique_ptr<LogCapture> log_capture = std::make_unique<LogCapture>();
 
   le_impl_->connectability_state_ = ConnectabilityState::ARMING;
@@ -899,8 +901,8 @@
   ASSERT_TRUE(log_capture->Rewind()->Find("Le connection state machine armed state"));
 }
 
-TEST_F(LeImplTest, disarm_connectability_ARMING_extended) {
-  bluetooth::common::InitFlags::SetAllForTesting();
+// b/260917913
+TEST_F(LeImplTest, DISABLED_disarm_connectability_ARMING_extended) {
   std::unique_ptr<LogCapture> log_capture = std::make_unique<LogCapture>();
 
   le_impl_->connectability_state_ = ConnectabilityState::ARMING;
@@ -914,8 +916,8 @@
   ASSERT_TRUE(log_capture->Rewind()->Find("Le connection state machine armed state"));
 }
 
-TEST_F(LeImplTest, disarm_connectability_ARMED) {
-  bluetooth::common::InitFlags::SetAllForTesting();
+// b/260917913
+TEST_F(LeImplTest, DISABLED_disarm_connectability_ARMED) {
   std::unique_ptr<LogCapture> log_capture = std::make_unique<LogCapture>();
 
   le_impl_->connectability_state_ = ConnectabilityState::ARMED;
@@ -928,8 +930,8 @@
   ASSERT_TRUE(log_capture->Rewind()->Find("Disarming LE connection state machine with create connection"));
 }
 
-TEST_F(LeImplTest, disarm_connectability_ARMED_extended) {
-  bluetooth::common::InitFlags::SetAllForTesting();
+// b/260917913
+TEST_F(LeImplTest, DISABLED_disarm_connectability_ARMED_extended) {
   std::unique_ptr<LogCapture> log_capture = std::make_unique<LogCapture>();
 
   le_impl_->connectability_state_ = ConnectabilityState::ARMED;
@@ -943,8 +945,8 @@
   ASSERT_TRUE(log_capture->Rewind()->Find("Disarming LE connection state machine with create connection"));
 }
 
-TEST_F(LeImplTest, disarm_connectability_DISARMING) {
-  bluetooth::common::InitFlags::SetAllForTesting();
+// b/260917913
+TEST_F(LeImplTest, DISABLED_disarm_connectability_DISARMING) {
   std::unique_ptr<LogCapture> log_capture = std::make_unique<LogCapture>();
 
   le_impl_->connectability_state_ = ConnectabilityState::DISARMING;
@@ -957,8 +959,8 @@
   ASSERT_TRUE(log_capture->Rewind()->Find("in unexpected state:ConnectabilityState::DISARMING"));
 }
 
-TEST_F(LeImplTest, disarm_connectability_DISARMING_extended) {
-  bluetooth::common::InitFlags::SetAllForTesting();
+// b/260917913
+TEST_F(LeImplTest, DISABLED_disarm_connectability_DISARMING_extended) {
   std::unique_ptr<LogCapture> log_capture = std::make_unique<LogCapture>();
 
   le_impl_->connectability_state_ = ConnectabilityState::DISARMING;
@@ -972,8 +974,8 @@
   ASSERT_TRUE(log_capture->Rewind()->Find("in unexpected state:ConnectabilityState::DISARMING"));
 }
 
-TEST_F(LeImplTest, register_with_address_manager__AddressPolicyPublicAddress) {
-  bluetooth::common::InitFlags::SetAllForTesting();
+// b/260917913
+TEST_F(LeImplTest, DISABLED_register_with_address_manager__AddressPolicyPublicAddress) {
   std::unique_ptr<LogCapture> log_capture = std::make_unique<LogCapture>();
 
   set_privacy_policy_for_initiator_address(fixed_address_, LeAddressManager::AddressPolicy::USE_PUBLIC_ADDRESS);
@@ -994,8 +996,8 @@
   ASSERT_TRUE(log_capture->Rewind()->Find("Client unregistered"));
 }
 
-TEST_F(LeImplTest, register_with_address_manager__AddressPolicyStaticAddress) {
-  bluetooth::common::InitFlags::SetAllForTesting();
+// b/260917913
+TEST_F(LeImplTest, DISABLED_register_with_address_manager__AddressPolicyStaticAddress) {
   std::unique_ptr<LogCapture> log_capture = std::make_unique<LogCapture>();
 
   set_privacy_policy_for_initiator_address(fixed_address_, LeAddressManager::AddressPolicy::USE_STATIC_ADDRESS);
@@ -1016,8 +1018,8 @@
   ASSERT_TRUE(log_capture->Rewind()->Find("Client unregistered"));
 }
 
-TEST_F(LeImplTest, register_with_address_manager__AddressPolicyNonResolvableAddress) {
-  bluetooth::common::InitFlags::SetAllForTesting();
+// b/260917913
+TEST_F(LeImplTest, DISABLED_register_with_address_manager__AddressPolicyNonResolvableAddress) {
   std::unique_ptr<LogCapture> log_capture = std::make_unique<LogCapture>();
 
   set_privacy_policy_for_initiator_address(fixed_address_, LeAddressManager::AddressPolicy::USE_NON_RESOLVABLE_ADDRESS);
@@ -1038,8 +1040,8 @@
   ASSERT_TRUE(log_capture->Rewind()->Find("Client unregistered"));
 }
 
-TEST_F(LeImplTest, register_with_address_manager__AddressPolicyResolvableAddress) {
-  bluetooth::common::InitFlags::SetAllForTesting();
+// b/260917913
+TEST_F(LeImplTest, DISABLED_register_with_address_manager__AddressPolicyResolvableAddress) {
   std::unique_ptr<LogCapture> log_capture = std::make_unique<LogCapture>();
 
   set_privacy_policy_for_initiator_address(fixed_address_, LeAddressManager::AddressPolicy::USE_RESOLVABLE_ADDRESS);
@@ -1060,9 +1062,8 @@
   ASSERT_TRUE(log_capture->Rewind()->Find("Client unregistered"));
 }
 
-TEST_F(LeImplTest, add_device_to_resolving_list) {
-  bluetooth::common::InitFlags::SetAllForTesting();
-
+// b/260920739
+TEST_F(LeImplTest, DISABLED_add_device_to_resolving_list) {
   // Some kind of privacy policy must be set for LeAddressManager to operate properly
   set_privacy_policy_for_initiator_address(fixed_address_, LeAddressManager::AddressPolicy::USE_PUBLIC_ADDRESS);
   // Let LeAddressManager::resume_registered_clients execute
@@ -1133,8 +1134,6 @@
 }
 
 TEST_F(LeImplTest, add_device_to_resolving_list__SupportsBlePrivacy) {
-  bluetooth::common::InitFlags::SetAllForTesting();
-
   controller_->supports_ble_privacy_ = true;
 
   // Some kind of privacy policy must be set for LeAddressManager to operate properly
@@ -1228,11 +1227,8 @@
 }
 
 TEST_F(LeImplTest, on_le_event__CONNECTION_COMPLETE_CENTRAL) {
-  bluetooth::common::InitFlags::SetAllForTesting();
+  EXPECT_CALL(mock_le_connection_callbacks_, OnLeConnectSuccess(_, _)).Times(1);
   set_random_device_address_policy();
-
-  EXPECT_CALL(mock_le_connection_callbacks_, OnLeConnectSuccess(::testing::_, ::testing::_)).Times(1);
-
   auto command = LeConnectionCompleteBuilder::Create(
       ErrorCode::SUCCESS,
       kHciHandle,
@@ -1243,22 +1239,15 @@
       0x0000,
       0x0011,
       ClockAccuracy::PPM_30);
-  auto bytes = std::make_shared<std::vector<uint8_t>>();
-  BitInserter bi(*bytes);
-  command->Serialize(bi);
+  auto bytes = Serialize<LeConnectionCompleteBuilder>(std::move(command));
   auto view = CreateLeEventView<hci::LeConnectionCompleteView>(bytes);
   ASSERT_TRUE(view.IsValid());
   le_impl_->on_le_event(view);
 }
 
 TEST_F(LeImplTest, on_le_event__CONNECTION_COMPLETE_PERIPHERAL) {
-  bluetooth::common::InitFlags::SetAllForTesting();
+  EXPECT_CALL(mock_le_connection_callbacks_, OnLeConnectSuccess(_, _)).Times(1);
   set_random_device_address_policy();
-
-  EXPECT_CALL(mock_le_connection_callbacks_, OnLeConnectSuccess(::testing::_, ::testing::_)).Times(1);
-
-  auto bytes = std::make_shared<std::vector<uint8_t>>();
-  BitInserter bi(*bytes);
   auto command = LeConnectionCompleteBuilder::Create(
       ErrorCode::SUCCESS,
       kHciHandle,
@@ -1269,18 +1258,15 @@
       0x0000,
       0x0011,
       ClockAccuracy::PPM_30);
-  command->Serialize(bi);
+  auto bytes = Serialize<LeConnectionCompleteBuilder>(std::move(command));
   auto view = CreateLeEventView<hci::LeConnectionCompleteView>(bytes);
   ASSERT_TRUE(view.IsValid());
   le_impl_->on_le_event(view);
 }
 
 TEST_F(LeImplTest, on_le_event__ENHANCED_CONNECTION_COMPLETE_CENTRAL) {
-  bluetooth::common::InitFlags::SetAllForTesting();
+  EXPECT_CALL(mock_le_connection_callbacks_, OnLeConnectSuccess(_, _)).Times(1);
   set_random_device_address_policy();
-
-  EXPECT_CALL(mock_le_connection_callbacks_, OnLeConnectSuccess(::testing::_, ::testing::_)).Times(1);
-
   auto command = LeEnhancedConnectionCompleteBuilder::Create(
       ErrorCode::SUCCESS,
       kHciHandle,
@@ -1293,20 +1279,15 @@
       0x0000,
       0x0011,
       ClockAccuracy::PPM_30);
-  auto bytes = std::make_shared<std::vector<uint8_t>>();
-  BitInserter bi(*bytes);
-  command->Serialize(bi);
+  auto bytes = Serialize<LeEnhancedConnectionCompleteBuilder>(std::move(command));
   auto view = CreateLeEventView<hci::LeEnhancedConnectionCompleteView>(bytes);
   ASSERT_TRUE(view.IsValid());
   le_impl_->on_le_event(view);
 }
 
 TEST_F(LeImplTest, on_le_event__ENHANCED_CONNECTION_COMPLETE_PERIPHERAL) {
-  EXPECT_CALL(mock_le_connection_callbacks_, OnLeConnectSuccess(::testing::_, ::testing::_)).Times(1);
-
-  bluetooth::common::InitFlags::SetAllForTesting();
+  EXPECT_CALL(mock_le_connection_callbacks_, OnLeConnectSuccess(_, _)).Times(1);
   set_random_device_address_policy();
-
   auto command = LeEnhancedConnectionCompleteBuilder::Create(
       ErrorCode::SUCCESS,
       kHciHandle,
@@ -1319,9 +1300,7 @@
       0x0000,
       0x0011,
       ClockAccuracy::PPM_30);
-  auto bytes = std::make_shared<std::vector<uint8_t>>();
-  BitInserter bi(*bytes);
-  command->Serialize(bi);
+  auto bytes = Serialize<LeEnhancedConnectionCompleteBuilder>(std::move(command));
   auto view = CreateLeEventView<hci::LeEnhancedConnectionCompleteView>(bytes);
   ASSERT_TRUE(view.IsValid());
   le_impl_->on_le_event(view);
@@ -1340,11 +1319,9 @@
 }
 
 TEST_F(LeImplWithConnectionTest, on_le_event__PHY_UPDATE_COMPLETE) {
-  bluetooth::common::InitFlags::SetAllForTesting();
-  set_random_device_address_policy();
-  hci::ErrorCode hci_status;
-  hci::PhyType tx_phy;
-  hci::PhyType rx_phy;
+  hci::ErrorCode hci_status{ErrorCode::STATUS_UNKNOWN};
+  hci::PhyType tx_phy{0};
+  hci::PhyType rx_phy{0};
 
   // Send a phy update
   {
@@ -1354,10 +1331,8 @@
           tx_phy = static_cast<PhyType>(_tx_phy);
           rx_phy = static_cast<PhyType>(_rx_phy);
         });
-    auto command = LePhyUpdateCompleteBuilder::Create(ErrorCode::SUCCESS, kHciHandle, 0x01, 0x01);
-    auto bytes = std::make_shared<std::vector<uint8_t>>();
-    BitInserter bi(*bytes);
-    command->Serialize(bi);
+    auto command = LePhyUpdateCompleteBuilder::Create(ErrorCode::SUCCESS, kHciHandle, 0x01, 0x02);
+    auto bytes = Serialize<LePhyUpdateCompleteBuilder>(std::move(command));
     auto view = CreateLeEventView<hci::LePhyUpdateCompleteView>(bytes);
     ASSERT_TRUE(view.IsValid());
     le_impl_->on_le_event(view);
@@ -1366,12 +1341,10 @@
   sync_handler();
   ASSERT_EQ(ErrorCode::SUCCESS, hci_status);
   ASSERT_EQ(PhyType::LE_1M, tx_phy);
+  ASSERT_EQ(PhyType::LE_2M, rx_phy);
 }
 
 TEST_F(LeImplWithConnectionTest, on_le_event__DATA_LENGTH_CHANGE) {
-  bluetooth::common::InitFlags::SetAllForTesting();
-  set_random_device_address_policy();
-
   uint16_t tx_octets{0};
   uint16_t tx_time{0};
   uint16_t rx_octets{0};
@@ -1387,9 +1360,7 @@
           rx_time = _rx_time;
         });
     auto command = LeDataLengthChangeBuilder::Create(kHciHandle, 0x1234, 0x5678, 0x9abc, 0xdef0);
-    auto bytes = std::make_shared<std::vector<uint8_t>>();
-    BitInserter bi(*bytes);
-    command->Serialize(bi);
+    auto bytes = Serialize<LeDataLengthChangeBuilder>(std::move(command));
     auto view = CreateLeEventView<hci::LeDataLengthChangeView>(bytes);
     ASSERT_TRUE(view.IsValid());
     le_impl_->on_le_event(view);
@@ -1403,15 +1374,10 @@
 }
 
 TEST_F(LeImplWithConnectionTest, on_le_event__REMOTE_CONNECTION_PARAMETER_REQUEST) {
-  bluetooth::common::InitFlags::SetAllForTesting();
-  set_random_device_address_policy();
-
   // Send a remote connection parameter request
-  auto bytes = std::make_shared<std::vector<uint8_t>>();
-  BitInserter bi(*bytes);
   auto command = hci::LeRemoteConnectionParameterRequestBuilder::Create(
       kHciHandle, kIntervalMin, kIntervalMax, kLatency, kTimeout);
-  command->Serialize(bi);
+  auto bytes = Serialize<LeRemoteConnectionParameterRequestBuilder>(std::move(command));
   {
     auto view = CreateLeEventView<hci::LeRemoteConnectionParameterRequestView>(bytes);
     ASSERT_TRUE(view.IsValid());
@@ -1432,6 +1398,99 @@
   ASSERT_EQ(kTimeout, view.GetTimeout());
 }
 
+// b/260920739
+TEST_F(LeImplRegisteredWithAddressManagerTest, DISABLED_clear_resolving_list) {
+  le_impl_->clear_resolving_list();
+  ASSERT_EQ(3UL, le_impl_->le_address_manager_->NumberCachedCommands());
+
+  sync_handler();  // Allow |LeAddressManager::pause_registered_clients| to complete
+  sync_handler();  // Allow |LeAddressManager::handle_next_command| to complete
+
+  ASSERT_EQ(1UL, hci_layer_->NumberOfQueuedCommands());
+  {
+    auto view = CreateLeSecurityCommandView<LeSetAddressResolutionEnableView>(hci_layer_->DequeueCommandBytes());
+    ASSERT_TRUE(view.IsValid());
+    ASSERT_EQ(Enable::DISABLED, view.GetAddressResolutionEnable());
+    le_impl_->le_address_manager_->OnCommandComplete(
+        ReturnCommandComplete(OpCode::LE_SET_ADDRESS_RESOLUTION_ENABLE, ErrorCode::SUCCESS));
+  }
+
+  sync_handler();  // Allow |LeAddressManager::check_cached_commands| to complete
+  ASSERT_EQ(1UL, hci_layer_->NumberOfQueuedCommands());
+  {
+    auto view = CreateLeSecurityCommandView<LeClearResolvingListView>(hci_layer_->DequeueCommandBytes());
+    ASSERT_TRUE(view.IsValid());
+    le_impl_->le_address_manager_->OnCommandComplete(
+        ReturnCommandComplete(OpCode::LE_CLEAR_RESOLVING_LIST, ErrorCode::SUCCESS));
+  }
+
+  sync_handler();  // Allow |LeAddressManager::handle_next_command| to complete
+  ASSERT_EQ(1UL, hci_layer_->NumberOfQueuedCommands());
+  {
+    auto view = CreateLeSecurityCommandView<LeSetAddressResolutionEnableView>(hci_layer_->DequeueCommandBytes());
+    ASSERT_TRUE(view.IsValid());
+    ASSERT_EQ(Enable::ENABLED, view.GetAddressResolutionEnable());
+    le_impl_->le_address_manager_->OnCommandComplete(
+        ReturnCommandComplete(OpCode::LE_SET_ADDRESS_RESOLUTION_ENABLE, ErrorCode::SUCCESS));
+  }
+  ASSERT_TRUE(hci_layer_->IsPacketQueueEmpty());
+}
+
+TEST_F(LeImplWithConnectionTest, HACK_get_handle) {
+  sync_handler();
+
+  ASSERT_EQ(kHciHandle, le_impl_->HACK_get_handle(remote_address_));
+}
+
+TEST_F(LeImplTest, on_le_connection_canceled_on_pause) {
+  set_random_device_address_policy();
+  le_impl_->pause_connection = true;
+  le_impl_->on_le_connection_canceled_on_pause();
+  ASSERT_TRUE(le_impl_->arm_on_resume_);
+  ASSERT_EQ(ConnectabilityState::DISARMED, le_impl_->connectability_state_);
+}
+
+TEST_F(LeImplTest, on_create_connection_timeout) {
+  EXPECT_CALL(mock_le_connection_callbacks_, OnLeConnectFail(_, ErrorCode::CONNECTION_ACCEPT_TIMEOUT)).Times(1);
+  le_impl_->create_connection_timeout_alarms_.emplace(
+      std::piecewise_construct,
+      std::forward_as_tuple(
+          remote_public_address_with_type_.GetAddress(), remote_public_address_with_type_.GetAddressType()),
+      std::forward_as_tuple(handler_));
+  le_impl_->on_create_connection_timeout(remote_public_address_with_type_);
+  sync_handler();
+  ASSERT_TRUE(le_impl_->create_connection_timeout_alarms_.empty());
+}
+
+// b/260917913
+TEST_F(LeImplTest, DISABLED_on_common_le_connection_complete__NoPriorConnection) {
+  auto log_capture = std::make_unique<LogCapture>();
+  le_impl_->on_common_le_connection_complete(remote_public_address_with_type_);
+  ASSERT_TRUE(le_impl_->connecting_le_.empty());
+  ASSERT_TRUE(log_capture->Rewind()->Find("No prior connection request for"));
+}
+
+TEST_F(LeImplTest, cancel_connect) {
+  le_impl_->create_connection_timeout_alarms_.emplace(
+      std::piecewise_construct,
+      std::forward_as_tuple(
+          remote_public_address_with_type_.GetAddress(), remote_public_address_with_type_.GetAddressType()),
+      std::forward_as_tuple(handler_));
+  le_impl_->cancel_connect(remote_public_address_with_type_);
+  sync_handler();
+  ASSERT_TRUE(le_impl_->create_connection_timeout_alarms_.empty());
+}
+
+TEST_F(LeImplTest, set_le_suggested_default_data_parameters) {
+  le_impl_->set_le_suggested_default_data_parameters(kLength, kTime);
+  sync_handler();
+  auto view =
+      CreateLeConnectionManagementCommandView<LeWriteSuggestedDefaultDataLengthView>(hci_layer_->DequeueCommandBytes());
+  ASSERT_TRUE(view.IsValid());
+  ASSERT_EQ(kLength, view.GetTxOctets());
+  ASSERT_EQ(kTime, view.GetTxTime());
+}
+
 }  // namespace acl_manager
 }  // namespace hci
 }  // namespace bluetooth
diff --git a/system/gd/hci/acl_manager/round_robin_scheduler_test.cc b/system/gd/hci/acl_manager/round_robin_scheduler_test.cc
index 9d43c0f..a8bd3d7 100644
--- a/system/gd/hci/acl_manager/round_robin_scheduler_test.cc
+++ b/system/gd/hci/acl_manager/round_robin_scheduler_test.cc
@@ -35,6 +35,7 @@
 namespace bluetooth {
 namespace hci {
 namespace acl_manager {
+namespace {
 
 class TestController : public Controller {
  public:
@@ -136,8 +137,9 @@
 
     packet_count_--;
     if (packet_count_ == 0) {
-      packet_promise_->set_value();
-      packet_promise_ = nullptr;
+      std::promise<void>* prom = packet_promise_.release();
+      prom->set_value();
+      delete prom;
     }
   }
 
@@ -152,7 +154,7 @@
   }
 
   void SetPacketFuture(uint16_t count) {
-    ASSERT_LOG(packet_promise_ == nullptr, "Promises, Promises, ... Only one at a time.");
+    ASSERT_EQ(packet_promise_, nullptr) << "Promises, Promises, ... Only one at a time.";
     packet_count_ = count;
     packet_promise_ = std::make_unique<std::promise<void>>();
     packet_future_ = std::make_unique<std::future<void>>(packet_promise_->get_future());
@@ -185,7 +187,7 @@
   auto connection_queue = std::make_shared<AclConnection::Queue>(10);
   round_robin_scheduler_->Register(RoundRobinScheduler::ConnectionType::CLASSIC, handle, connection_queue);
 
-  SetPacketFuture(2);
+  ASSERT_NO_FATAL_FAILURE(SetPacketFuture(2));
   AclConnection::QueueUpEnd* queue_up_end = connection_queue->GetUpEnd();
   std::vector<uint8_t> packet1 = {0x01, 0x02, 0x03};
   std::vector<uint8_t> packet2 = {0x04, 0x05, 0x06};
@@ -209,7 +211,7 @@
   round_robin_scheduler_->Register(RoundRobinScheduler::ConnectionType::CLASSIC, handle, connection_queue);
   round_robin_scheduler_->Register(RoundRobinScheduler::ConnectionType::LE, le_handle, le_connection_queue);
 
-  SetPacketFuture(2);
+  ASSERT_NO_FATAL_FAILURE(SetPacketFuture(2));
   AclConnection::QueueUpEnd* queue_up_end = connection_queue->GetUpEnd();
   AclConnection::QueueUpEnd* le_queue_up_end = le_connection_queue->GetUpEnd();
   std::vector<uint8_t> packet = {0x01, 0x02, 0x03};
@@ -232,7 +234,7 @@
   auto connection_queue = std::make_shared<AclConnection::Queue>(15);
   round_robin_scheduler_->Register(RoundRobinScheduler::ConnectionType::CLASSIC, handle, connection_queue);
 
-  SetPacketFuture(10);
+  ASSERT_NO_FATAL_FAILURE(SetPacketFuture(10));
   AclConnection::QueueUpEnd* queue_up_end = connection_queue->GetUpEnd();
   for (uint8_t i = 0; i < 15; i++) {
     std::vector<uint8_t> packet = {0x01, 0x02, 0x03, i};
@@ -246,7 +248,7 @@
   }
   ASSERT_EQ(round_robin_scheduler_->GetCredits(), 0);
 
-  SetPacketFuture(5);
+  ASSERT_NO_FATAL_FAILURE(SetPacketFuture(5));
   controller_->SendCompletedAclPacketsCallback(0x01, 10);
   sync_handler();
   packet_future_->wait();
@@ -276,7 +278,7 @@
   auto le_connection_queue1 = std::make_shared<AclConnection::Queue>(10);
   auto le_connection_queue2 = std::make_shared<AclConnection::Queue>(10);
 
-  SetPacketFuture(18);
+  ASSERT_NO_FATAL_FAILURE(SetPacketFuture(18));
   AclConnection::QueueUpEnd* queue_up_end1 = connection_queue1->GetUpEnd();
   AclConnection::QueueUpEnd* queue_up_end2 = connection_queue2->GetUpEnd();
   AclConnection::QueueUpEnd* le_queue_up_end1 = le_connection_queue1->GetUpEnd();
@@ -333,7 +335,7 @@
   round_robin_scheduler_->Register(RoundRobinScheduler::ConnectionType::CLASSIC, handle, connection_queue);
   round_robin_scheduler_->Register(RoundRobinScheduler::ConnectionType::LE, le_handle, le_connection_queue);
 
-  SetPacketFuture(5);
+  ASSERT_NO_FATAL_FAILURE(SetPacketFuture(5));
   AclConnection::QueueUpEnd* queue_up_end = connection_queue->GetUpEnd();
   AclConnection::QueueUpEnd* le_queue_up_end = le_connection_queue->GetUpEnd();
   std::vector<uint8_t> packet(controller_->hci_mtu_, 0xff);
@@ -379,7 +381,8 @@
   round_robin_scheduler_->Register(RoundRobinScheduler::ConnectionType::CLASSIC, handle, connection_queue);
   round_robin_scheduler_->Register(RoundRobinScheduler::ConnectionType::LE, le_handle, le_connection_queue);
 
-  SetPacketFuture(controller_->le_max_acl_packet_credits_ + controller_->max_acl_packet_credits_);
+  ASSERT_NO_FATAL_FAILURE(
+      SetPacketFuture(controller_->le_max_acl_packet_credits_ + controller_->max_acl_packet_credits_));
   AclConnection::QueueUpEnd* queue_up_end = connection_queue->GetUpEnd();
   AclConnection::QueueUpEnd* le_queue_up_end = le_connection_queue->GetUpEnd();
   std::vector<uint8_t> huge_packet(2000);
@@ -410,6 +413,7 @@
   round_robin_scheduler_->Unregister(le_handle);
 }
 
+}  // namespace
 }  // namespace acl_manager
 }  // namespace hci
 }  // namespace bluetooth
diff --git a/system/gd/hci/acl_manager_test.cc b/system/gd/hci/acl_manager_test.cc
index 73e3228..e4cdcae 100644
--- a/system/gd/hci/acl_manager_test.cc
+++ b/system/gd/hci/acl_manager_test.cc
@@ -16,19 +16,20 @@
 
 #include "hci/acl_manager.h"
 
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
 #include <algorithm>
 #include <chrono>
 #include <future>
 #include <map>
 
-#include <gmock/gmock.h>
-#include <gtest/gtest.h>
-
 #include "common/bind.h"
 #include "hci/address.h"
 #include "hci/class_of_device.h"
 #include "hci/controller.h"
 #include "hci/hci_layer.h"
+#include "hci/hci_layer_fake.h"
 #include "os/thread.h"
 #include "packet/raw_builder.h"
 
@@ -43,37 +44,14 @@
 using packet::PacketView;
 using packet::RawBuilder;
 
-constexpr std::chrono::seconds kTimeout = std::chrono::seconds(2);
+constexpr auto kTimeout = std::chrono::seconds(2);
+constexpr auto kShortTimeout = std::chrono::milliseconds(100);
 constexpr uint16_t kScanIntervalFast = 0x0060;
 constexpr uint16_t kScanWindowFast = 0x0030;
 constexpr uint16_t kScanIntervalSlow = 0x0800;
 constexpr uint16_t kScanWindowSlow = 0x0030;
 const AddressWithType empty_address_with_type = hci::AddressWithType();
 
-PacketView<kLittleEndian> GetPacketView(std::unique_ptr<packet::BasePacketBuilder> packet) {
-  auto bytes = std::make_shared<std::vector<uint8_t>>();
-  BitInserter i(*bytes);
-  bytes->reserve(packet->size());
-  packet->Serialize(i);
-  return packet::PacketView<packet::kLittleEndian>(bytes);
-}
-
-std::unique_ptr<BasePacketBuilder> NextPayload(uint16_t handle) {
-  static uint32_t packet_number = 1;
-  auto payload = std::make_unique<RawBuilder>();
-  payload->AddOctets2(6);  // L2CAP PDU size
-  payload->AddOctets2(2);  // L2CAP CID
-  payload->AddOctets2(handle);
-  payload->AddOctets4(packet_number++);
-  return std::move(payload);
-}
-
-std::unique_ptr<AclBuilder> NextAclPacket(uint16_t handle) {
-  PacketBoundaryFlag packet_boundary_flag = PacketBoundaryFlag::FIRST_AUTOMATICALLY_FLUSHABLE;
-  BroadcastFlag broadcast_flag = BroadcastFlag::POINT_TO_POINT;
-  return AclBuilder::Create(handle, packet_boundary_flag, broadcast_flag, NextPayload(handle));
-}
-
 class TestController : public Controller {
  public:
   void RegisterCompletedAclPacketsCallback(
@@ -118,200 +96,6 @@
   void ListDependencies(ModuleList* list) const {}
 };
 
-class TestHciLayer : public HciLayer {
- public:
-  void EnqueueCommand(
-      std::unique_ptr<CommandBuilder> command,
-      common::ContextualOnceCallback<void(CommandStatusView)> on_status) override {
-    command_queue_.push(std::move(command));
-    command_status_callbacks.push_back(std::move(on_status));
-    if (command_promise_ != nullptr) {
-      command_promise_->set_value();
-      command_promise_.reset();
-    }
-  }
-
-  void EnqueueCommand(
-      std::unique_ptr<CommandBuilder> command,
-      common::ContextualOnceCallback<void(CommandCompleteView)> on_complete) override {
-    command_queue_.push(std::move(command));
-    command_complete_callbacks.push_back(std::move(on_complete));
-    if (command_promise_ != nullptr) {
-      command_promise_->set_value();
-      command_promise_.reset();
-    }
-  }
-
-  void SetCommandFuture() {
-    ASSERT_LOG(command_promise_ == nullptr, "Promises, Promises, ... Only one at a time.");
-    command_promise_ = std::make_unique<std::promise<void>>();
-    command_future_ = std::make_unique<std::future<void>>(command_promise_->get_future());
-  }
-
-  CommandView GetLastCommand() {
-    if (command_queue_.size() == 0) {
-      return CommandView::Create(PacketView<kLittleEndian>(std::make_shared<std::vector<uint8_t>>()));
-    }
-    auto last = std::move(command_queue_.front());
-    command_queue_.pop();
-    return CommandView::Create(GetPacketView(std::move(last)));
-  }
-
-  ConnectionManagementCommandView GetCommand(OpCode op_code) {
-    if (command_future_ != nullptr) {
-      auto result = command_future_->wait_for(std::chrono::milliseconds(1000));
-      EXPECT_NE(std::future_status::timeout, result);
-    }
-    if (command_queue_.empty()) {
-      return ConnectionManagementCommandView::Create(AclCommandView::Create(
-          CommandView::Create(PacketView<kLittleEndian>(std::make_shared<std::vector<uint8_t>>()))));
-    }
-    CommandView command_packet_view = GetLastCommand();
-    ConnectionManagementCommandView command =
-        ConnectionManagementCommandView::Create(AclCommandView::Create(command_packet_view));
-    EXPECT_TRUE(command.IsValid());
-    EXPECT_EQ(command.GetOpCode(), op_code);
-
-    return command;
-  }
-
-  ConnectionManagementCommandView GetLastCommand(OpCode op_code) {
-    if (!command_queue_.empty() && command_future_ != nullptr) {
-      command_future_.reset();
-      command_promise_.reset();
-    } else if (command_future_ != nullptr) {
-      auto result = command_future_->wait_for(std::chrono::milliseconds(1000));
-      EXPECT_NE(std::future_status::timeout, result);
-    }
-    if (command_queue_.empty()) {
-      return ConnectionManagementCommandView::Create(AclCommandView::Create(
-          CommandView::Create(PacketView<kLittleEndian>(std::make_shared<std::vector<uint8_t>>()))));
-    }
-    CommandView command_packet_view = GetLastCommand();
-    ConnectionManagementCommandView command =
-        ConnectionManagementCommandView::Create(AclCommandView::Create(command_packet_view));
-    EXPECT_TRUE(command.IsValid());
-    EXPECT_EQ(command.GetOpCode(), op_code);
-
-    return command;
-  }
-
-  void RegisterEventHandler(EventCode event_code, common::ContextualCallback<void(EventView)> event_handler) override {
-    registered_events_[event_code] = event_handler;
-  }
-
-  void UnregisterEventHandler(EventCode event_code) override {
-    registered_events_.erase(event_code);
-  }
-
-  void RegisterLeEventHandler(SubeventCode subevent_code,
-                              common::ContextualCallback<void(LeMetaEventView)> event_handler) override {
-    registered_le_events_[subevent_code] = event_handler;
-  }
-
-  void UnregisterLeEventHandler(SubeventCode subevent_code) override {
-    registered_le_events_.erase(subevent_code);
-  }
-
-  void IncomingEvent(std::unique_ptr<EventBuilder> event_builder) {
-    auto packet = GetPacketView(std::move(event_builder));
-    EventView event = EventView::Create(packet);
-    ASSERT_TRUE(event.IsValid());
-    EventCode event_code = event.GetEventCode();
-    ASSERT_NE(registered_events_.find(event_code), registered_events_.end()) << EventCodeText(event_code);
-    registered_events_[event_code].Invoke(event);
-  }
-
-  void IncomingLeMetaEvent(std::unique_ptr<LeMetaEventBuilder> event_builder) {
-    auto packet = GetPacketView(std::move(event_builder));
-    EventView event = EventView::Create(packet);
-    LeMetaEventView meta_event_view = LeMetaEventView::Create(event);
-    EXPECT_TRUE(meta_event_view.IsValid());
-    SubeventCode subevent_code = meta_event_view.GetSubeventCode();
-    EXPECT_TRUE(registered_le_events_.find(subevent_code) != registered_le_events_.end());
-    registered_le_events_[subevent_code].Invoke(meta_event_view);
-  }
-
-  void IncomingAclData(uint16_t handle) {
-    os::Handler* hci_handler = GetHandler();
-    auto* queue_end = acl_queue_.GetDownEnd();
-    std::promise<void> promise;
-    auto future = promise.get_future();
-    queue_end->RegisterEnqueue(hci_handler,
-                               common::Bind(
-                                   [](decltype(queue_end) queue_end, uint16_t handle, std::promise<void> promise) {
-                                     auto packet = GetPacketView(NextAclPacket(handle));
-                                     AclView acl2 = AclView::Create(packet);
-                                     queue_end->UnregisterEnqueue();
-                                     promise.set_value();
-                                     return std::make_unique<AclView>(acl2);
-                                   },
-                                   queue_end, handle, common::Passed(std::move(promise))));
-    auto status = future.wait_for(kTimeout);
-    ASSERT_EQ(status, std::future_status::ready);
-  }
-
-  void AssertNoOutgoingAclData() {
-    auto queue_end = acl_queue_.GetDownEnd();
-    EXPECT_EQ(queue_end->TryDequeue(), nullptr);
-  }
-
-  void CommandCompleteCallback(EventView event) {
-    CommandCompleteView complete_view = CommandCompleteView::Create(event);
-    ASSERT_TRUE(complete_view.IsValid());
-    std::move(command_complete_callbacks.front()).Invoke(complete_view);
-    command_complete_callbacks.pop_front();
-  }
-
-  void CommandStatusCallback(EventView event) {
-    CommandStatusView status_view = CommandStatusView::Create(event);
-    ASSERT_TRUE(status_view.IsValid());
-    std::move(command_status_callbacks.front()).Invoke(status_view);
-    command_status_callbacks.pop_front();
-  }
-
-  PacketView<kLittleEndian> OutgoingAclData() {
-    auto queue_end = acl_queue_.GetDownEnd();
-    std::unique_ptr<AclBuilder> received;
-    do {
-      received = queue_end->TryDequeue();
-    } while (received == nullptr);
-
-    return GetPacketView(std::move(received));
-  }
-
-  BidiQueueEnd<AclBuilder, AclView>* GetAclQueueEnd() override {
-    return acl_queue_.GetUpEnd();
-  }
-
-  void ListDependencies(ModuleList* list) const {}
-  void Start() override {
-    RegisterEventHandler(EventCode::COMMAND_COMPLETE,
-                         GetHandler()->BindOn(this, &TestHciLayer::CommandCompleteCallback));
-    RegisterEventHandler(EventCode::COMMAND_STATUS, GetHandler()->BindOn(this, &TestHciLayer::CommandStatusCallback));
-  }
-  void Stop() override {}
-
-  void Disconnect(uint16_t handle, ErrorCode reason) override {
-    GetHandler()->Post(common::BindOnce(&TestHciLayer::do_disconnect, common::Unretained(this), handle, reason));
-  }
-
- private:
-  std::map<EventCode, common::ContextualCallback<void(EventView)>> registered_events_;
-  std::map<SubeventCode, common::ContextualCallback<void(LeMetaEventView)>> registered_le_events_;
-  std::list<common::ContextualOnceCallback<void(CommandCompleteView)>> command_complete_callbacks;
-  std::list<common::ContextualOnceCallback<void(CommandStatusView)>> command_status_callbacks;
-  BidiQueue<AclView, AclBuilder> acl_queue_{3 /* TODO: Set queue depth */};
-
-  std::queue<std::unique_ptr<CommandBuilder>> command_queue_;
-  std::unique_ptr<std::promise<void>> command_promise_;
-  std::unique_ptr<std::future<void>> command_future_;
-
-  void do_disconnect(uint16_t handle, ErrorCode reason) {
-    HciLayer::Disconnect(handle, reason);
-  }
-};
-
 class AclManagerNoCallbacksTest : public ::testing::Test {
  protected:
   void SetUp() override {
@@ -321,7 +105,6 @@
     fake_registry_.InjectTestModule(&Controller::Factory, test_controller_);
     client_handler_ = fake_registry_.GetTestModuleHandler(&HciLayer::Factory);
     ASSERT_NE(client_handler_, nullptr);
-    test_hci_layer_->SetCommandFuture();
     fake_registry_.Start<AclManager>(&thread_);
     acl_manager_ = static_cast<AclManager*>(fake_registry_.GetModuleUnderTest(&AclManager::Factory));
     Address::FromString("A1:A2:A3:A4:A5:A6", remote);
@@ -337,21 +120,29 @@
         minimum_rotation_time,
         maximum_rotation_time);
 
-    auto set_random_address_packet = LeSetRandomAddressView::Create(
-        LeAdvertisingCommandView::Create(test_hci_layer_->GetCommand(OpCode::LE_SET_RANDOM_ADDRESS)));
+    auto set_random_address_packet =
+        LeSetRandomAddressView::Create(LeAdvertisingCommandView::Create(
+            GetConnectionManagementCommand(OpCode::LE_SET_RANDOM_ADDRESS)));
     ASSERT_TRUE(set_random_address_packet.IsValid());
-    my_initiating_address =
-        AddressWithType(set_random_address_packet.GetRandomAddress(), AddressType::RANDOM_DEVICE_ADDRESS);
-    // Verify LE Set Random Address was sent during setup
-    test_hci_layer_->GetLastCommand(OpCode::LE_SET_RANDOM_ADDRESS);
+    my_initiating_address = AddressWithType(
+        set_random_address_packet.GetRandomAddress(), AddressType::RANDOM_DEVICE_ADDRESS);
     test_hci_layer_->IncomingEvent(LeSetRandomAddressCompleteBuilder::Create(0x01, ErrorCode::SUCCESS));
   }
 
   void TearDown() override {
+    // Invalid mutex exception is raised if the connections
+    // are cleared after the AclConnectionInterface is deleted
+    // through fake_registry_.
+    mock_connection_callback_.Clear();
+    mock_le_connection_callbacks_.Clear();
     fake_registry_.SynchronizeModuleHandler(&AclManager::Factory, std::chrono::milliseconds(20));
     fake_registry_.StopAll();
   }
 
+  void sync_client_handler() {
+    ASSERT(thread_.GetReactor()->WaitForIdle(std::chrono::seconds(2)));
+  }
+
   TestModuleRegistry fake_registry_;
   TestHciLayer* test_hci_layer_ = nullptr;
   TestController* test_controller_ = nullptr;
@@ -398,6 +189,15 @@
     ASSERT_EQ(status, std::future_status::ready);
   }
 
+  ConnectionManagementCommandView GetConnectionManagementCommand(OpCode op_code) {
+    auto base_command = test_hci_layer_->GetCommand();
+    ConnectionManagementCommandView command =
+        ConnectionManagementCommandView::Create(AclCommandView::Create(base_command));
+    EXPECT_TRUE(command.IsValid());
+    EXPECT_EQ(command.GetOpCode(), op_code);
+    return command;
+  }
+
   class MockConnectionCallback : public ConnectionCallbacks {
    public:
     void OnConnectSuccess(std::unique_ptr<ClassicAclConnection> connection) override {
@@ -408,6 +208,11 @@
         connection_promise_.reset();
       }
     }
+
+    void Clear() {
+      connections_.clear();
+    }
+
     MOCK_METHOD(void, OnConnectFail, (Address, ErrorCode reason), (override));
 
     MOCK_METHOD(void, HACK_OnEscoConnectRequest, (Address, ClassOfDevice), (override));
@@ -426,6 +231,11 @@
         le_connection_promise_.reset();
       }
     }
+
+    void Clear() {
+      le_connections_.clear();
+    }
+
     MOCK_METHOD(void, OnLeConnectFail, (AddressWithType, ErrorCode reason), (override));
 
     std::list<std::shared_ptr<LeAclConnection>> le_connections_;
@@ -451,9 +261,9 @@
     acl_manager_->CreateConnection(remote);
 
     // Wait for the connection request
-    auto last_command = test_hci_layer_->GetCommand(OpCode::CREATE_CONNECTION);
+    auto last_command = GetConnectionManagementCommand(OpCode::CREATE_CONNECTION);
     while (!last_command.IsValid()) {
-      last_command = test_hci_layer_->GetCommand(OpCode::CREATE_CONNECTION);
+      last_command = GetConnectionManagementCommand(OpCode::CREATE_CONNECTION);
     }
 
     EXPECT_CALL(mock_connection_management_callbacks_, OnRoleChange(hci::ErrorCode::SUCCESS, Role::CENTRAL));
@@ -470,19 +280,17 @@
   }
 
   void TearDown() override {
+    // Invalid mutex exception is raised if the connection
+    // is cleared after the AclConnectionInterface is deleted
+    // through fake_registry_.
+    mock_connection_callback_.Clear();
+    mock_le_connection_callbacks_.Clear();
+    connection_.reset();
     fake_registry_.SynchronizeModuleHandler(&HciLayer::Factory, std::chrono::milliseconds(20));
     fake_registry_.SynchronizeModuleHandler(&AclManager::Factory, std::chrono::milliseconds(20));
     fake_registry_.StopAll();
   }
 
-  void sync_client_handler() {
-    std::promise<void> promise;
-    auto future = promise.get_future();
-    client_handler_->Post(common::BindOnce(&std::promise<void>::set_value, common::Unretained(&promise)));
-    auto future_status = future.wait_for(std::chrono::seconds(1));
-    EXPECT_EQ(future_status, std::future_status::ready);
-  }
-
   uint16_t handle_;
   std::shared_ptr<ClassicAclConnection> connection_;
 
@@ -532,30 +340,15 @@
 
 TEST_F(AclManagerTest, startup_teardown) {}
 
-TEST_F(AclManagerNoCallbacksTest, acl_connection_before_registered_callbacks) {
-  ClassOfDevice class_of_device;
-
-  test_hci_layer_->IncomingEvent(
-      ConnectionRequestBuilder::Create(remote, class_of_device, ConnectionRequestLinkType::ACL));
-  fake_registry_.SynchronizeModuleHandler(&HciLayer::Factory, std::chrono::milliseconds(20));
-  fake_registry_.SynchronizeModuleHandler(&AclManager::Factory, std::chrono::milliseconds(20));
-  fake_registry_.SynchronizeModuleHandler(&HciLayer::Factory, std::chrono::milliseconds(20));
-  CommandView command = CommandView::Create(test_hci_layer_->GetLastCommand());
-  EXPECT_TRUE(command.IsValid());
-  OpCode op_code = command.GetOpCode();
-  EXPECT_EQ(op_code, OpCode::REJECT_CONNECTION_REQUEST);
-}
-
 TEST_F(AclManagerTest, invoke_registered_callback_connection_complete_success) {
   uint16_t handle = 1;
 
-  test_hci_layer_->SetCommandFuture();
   acl_manager_->CreateConnection(remote);
 
   // Wait for the connection request
-  auto last_command = test_hci_layer_->GetCommand(OpCode::CREATE_CONNECTION);
+  auto last_command = GetConnectionManagementCommand(OpCode::CREATE_CONNECTION);
   while (!last_command.IsValid()) {
-    last_command = test_hci_layer_->GetCommand(OpCode::CREATE_CONNECTION);
+    last_command = GetConnectionManagementCommand(OpCode::CREATE_CONNECTION);
   }
 
   auto first_connection = GetConnectionFuture();
@@ -573,13 +366,12 @@
 TEST_F(AclManagerTest, invoke_registered_callback_connection_complete_fail) {
   uint16_t handle = 0x123;
 
-  test_hci_layer_->SetCommandFuture();
   acl_manager_->CreateConnection(remote);
 
   // Wait for the connection request
-  auto last_command = test_hci_layer_->GetCommand(OpCode::CREATE_CONNECTION);
+  auto last_command = GetConnectionManagementCommand(OpCode::CREATE_CONNECTION);
   while (!last_command.IsValid()) {
-    last_command = test_hci_layer_->GetCommand(OpCode::CREATE_CONNECTION);
+    last_command = GetConnectionManagementCommand(OpCode::CREATE_CONNECTION);
   }
 
   EXPECT_CALL(mock_connection_callback_, OnConnectFail(remote, ErrorCode::PAGE_TIMEOUT));
@@ -596,12 +388,10 @@
     AclManagerTest::SetUp();
 
     remote_with_type_ = AddressWithType(remote, AddressType::PUBLIC_DEVICE_ADDRESS);
-    test_hci_layer_->SetCommandFuture();
     acl_manager_->CreateLeConnection(remote_with_type_, true);
-    test_hci_layer_->GetCommand(OpCode::LE_ADD_DEVICE_TO_FILTER_ACCEPT_LIST);
+    GetConnectionManagementCommand(OpCode::LE_ADD_DEVICE_TO_FILTER_ACCEPT_LIST);
     test_hci_layer_->IncomingEvent(LeAddDeviceToFilterAcceptListCompleteBuilder::Create(0x01, ErrorCode::SUCCESS));
-    test_hci_layer_->SetCommandFuture();
-    auto packet = test_hci_layer_->GetCommand(OpCode::LE_CREATE_CONNECTION);
+    auto packet = GetConnectionManagementCommand(OpCode::LE_CREATE_CONNECTION);
     auto le_connection_management_command_view =
         LeConnectionManagementCommandView::Create(AclCommandView::Create(packet));
     auto command_view = LeCreateConnectionView::Create(le_connection_management_command_view);
@@ -621,7 +411,7 @@
     test_hci_layer_->IncomingLeMetaEvent(LeConnectionCompleteBuilder::Create(
         ErrorCode::SUCCESS,
         handle_,
-        Role::PERIPHERAL,
+        Role::CENTRAL,
         AddressType::PUBLIC_DEVICE_ADDRESS,
         remote,
         0x0100,
@@ -629,8 +419,7 @@
         0x0C80,
         ClockAccuracy::PPM_30));
 
-    test_hci_layer_->SetCommandFuture();
-    test_hci_layer_->GetCommand(OpCode::LE_REMOVE_DEVICE_FROM_FILTER_ACCEPT_LIST);
+    GetConnectionManagementCommand(OpCode::LE_REMOVE_DEVICE_FROM_FILTER_ACCEPT_LIST);
     test_hci_layer_->IncomingEvent(LeRemoveDeviceFromFilterAcceptListCompleteBuilder::Create(0x01, ErrorCode::SUCCESS));
 
     auto first_connection_status = first_connection.wait_for(kTimeout);
@@ -640,19 +429,17 @@
   }
 
   void TearDown() override {
+    // Invalid mutex exception is raised if the connection
+    // is cleared after the AclConnectionInterface is deleted
+    // through fake_registry_.
+    mock_connection_callback_.Clear();
+    mock_le_connection_callbacks_.Clear();
+    connection_.reset();
     fake_registry_.SynchronizeModuleHandler(&HciLayer::Factory, std::chrono::milliseconds(20));
     fake_registry_.SynchronizeModuleHandler(&AclManager::Factory, std::chrono::milliseconds(20));
     fake_registry_.StopAll();
   }
 
-  void sync_client_handler() {
-    std::promise<void> promise;
-    auto future = promise.get_future();
-    client_handler_->Post(common::BindOnce(&std::promise<void>::set_value, common::Unretained(&promise)));
-    auto future_status = future.wait_for(std::chrono::seconds(1));
-    EXPECT_EQ(future_status, std::future_status::ready);
-  }
-
   uint16_t handle_ = 0x123;
   std::shared_ptr<LeAclConnection> connection_;
   AddressWithType remote_with_type_;
@@ -686,12 +473,10 @@
 
 TEST_F(AclManagerTest, invoke_registered_callback_le_connection_complete_fail) {
   AddressWithType remote_with_type(remote, AddressType::PUBLIC_DEVICE_ADDRESS);
-  test_hci_layer_->SetCommandFuture();
   acl_manager_->CreateLeConnection(remote_with_type, true);
-  test_hci_layer_->GetLastCommand(OpCode::LE_ADD_DEVICE_TO_FILTER_ACCEPT_LIST);
+  GetConnectionManagementCommand(OpCode::LE_ADD_DEVICE_TO_FILTER_ACCEPT_LIST);
   test_hci_layer_->IncomingEvent(LeAddDeviceToFilterAcceptListCompleteBuilder::Create(0x01, ErrorCode::SUCCESS));
-  test_hci_layer_->SetCommandFuture();
-  auto packet = test_hci_layer_->GetLastCommand(OpCode::LE_CREATE_CONNECTION);
+  auto packet = GetConnectionManagementCommand(OpCode::LE_CREATE_CONNECTION);
   auto le_connection_management_command_view =
       LeConnectionManagementCommandView::Create(AclCommandView::Create(packet));
   auto command_view = LeCreateConnectionView::Create(le_connection_management_command_view);
@@ -707,10 +492,11 @@
 
   EXPECT_CALL(mock_le_connection_callbacks_,
               OnLeConnectFail(remote_with_type, ErrorCode::CONNECTION_REJECTED_LIMITED_RESOURCES));
+
   test_hci_layer_->IncomingLeMetaEvent(LeConnectionCompleteBuilder::Create(
       ErrorCode::CONNECTION_REJECTED_LIMITED_RESOURCES,
       0x123,
-      Role::PERIPHERAL,
+      Role::CENTRAL,
       AddressType::PUBLIC_DEVICE_ADDRESS,
       remote,
       0x0100,
@@ -718,8 +504,7 @@
       0x0011,
       ClockAccuracy::PPM_30));
 
-  test_hci_layer_->SetCommandFuture();
-  packet = test_hci_layer_->GetLastCommand(OpCode::LE_REMOVE_DEVICE_FROM_FILTER_ACCEPT_LIST);
+  packet = GetConnectionManagementCommand(OpCode::LE_REMOVE_DEVICE_FROM_FILTER_ACCEPT_LIST);
   le_connection_management_command_view = LeConnectionManagementCommandView::Create(AclCommandView::Create(packet));
   auto remove_command_view = LeRemoveDeviceFromFilterAcceptListView::Create(le_connection_management_command_view);
   ASSERT_TRUE(remove_command_view.IsValid());
@@ -728,16 +513,14 @@
 
 TEST_F(AclManagerTest, cancel_le_connection) {
   AddressWithType remote_with_type(remote, AddressType::PUBLIC_DEVICE_ADDRESS);
-  test_hci_layer_->SetCommandFuture();
   acl_manager_->CreateLeConnection(remote_with_type, true);
-  test_hci_layer_->GetLastCommand(OpCode::LE_ADD_DEVICE_TO_FILTER_ACCEPT_LIST);
+  GetConnectionManagementCommand(OpCode::LE_ADD_DEVICE_TO_FILTER_ACCEPT_LIST);
   test_hci_layer_->IncomingEvent(LeAddDeviceToFilterAcceptListCompleteBuilder::Create(0x01, ErrorCode::SUCCESS));
-  test_hci_layer_->SetCommandFuture();
-  test_hci_layer_->GetLastCommand(OpCode::LE_CREATE_CONNECTION);
+  GetConnectionManagementCommand(OpCode::LE_CREATE_CONNECTION);
+  test_hci_layer_->IncomingEvent(LeCreateConnectionStatusBuilder::Create(ErrorCode::SUCCESS, 0x01));
 
-  test_hci_layer_->SetCommandFuture();
   acl_manager_->CancelLeConnect(remote_with_type);
-  auto packet = test_hci_layer_->GetLastCommand(OpCode::LE_CREATE_CONNECTION_CANCEL);
+  auto packet = GetConnectionManagementCommand(OpCode::LE_CREATE_CONNECTION_CANCEL);
   auto le_connection_management_command_view =
       LeConnectionManagementCommandView::Create(AclCommandView::Create(packet));
   auto command_view = LeCreateConnectionCancelView::Create(le_connection_management_command_view);
@@ -747,7 +530,7 @@
   test_hci_layer_->IncomingLeMetaEvent(LeConnectionCompleteBuilder::Create(
       ErrorCode::UNKNOWN_CONNECTION,
       0x123,
-      Role::PERIPHERAL,
+      Role::CENTRAL,
       AddressType::PUBLIC_DEVICE_ADDRESS,
       remote,
       0x0100,
@@ -755,8 +538,7 @@
       0x0011,
       ClockAccuracy::PPM_30));
 
-  test_hci_layer_->SetCommandFuture();
-  packet = test_hci_layer_->GetLastCommand(OpCode::LE_REMOVE_DEVICE_FROM_FILTER_ACCEPT_LIST);
+  packet = GetConnectionManagementCommand(OpCode::LE_REMOVE_DEVICE_FROM_FILTER_ACCEPT_LIST);
   le_connection_management_command_view = LeConnectionManagementCommandView::Create(AclCommandView::Create(packet));
   auto remove_command_view = LeRemoveDeviceFromFilterAcceptListView::Create(le_connection_management_command_view);
   ASSERT_TRUE(remove_command_view.IsValid());
@@ -766,31 +548,32 @@
 
 TEST_F(AclManagerTest, create_connection_with_fast_mode) {
   AddressWithType remote_with_type(remote, AddressType::PUBLIC_DEVICE_ADDRESS);
-  test_hci_layer_->SetCommandFuture();
   acl_manager_->CreateLeConnection(remote_with_type, true);
-  test_hci_layer_->GetLastCommand(OpCode::LE_ADD_DEVICE_TO_FILTER_ACCEPT_LIST);
-  test_hci_layer_->IncomingEvent(LeAddDeviceToFilterAcceptListCompleteBuilder::Create(0x01, ErrorCode::SUCCESS));
-  test_hci_layer_->SetCommandFuture();
-  auto packet = test_hci_layer_->GetLastCommand(OpCode::LE_CREATE_CONNECTION);
+  GetConnectionManagementCommand(OpCode::LE_ADD_DEVICE_TO_FILTER_ACCEPT_LIST);
+  test_hci_layer_->IncomingEvent(
+      LeAddDeviceToFilterAcceptListCompleteBuilder::Create(0x01, ErrorCode::SUCCESS));
+
+  auto packet = GetConnectionManagementCommand(OpCode::LE_CREATE_CONNECTION);
   auto command_view =
       LeCreateConnectionView::Create(LeConnectionManagementCommandView::Create(AclCommandView::Create(packet)));
   ASSERT_TRUE(command_view.IsValid());
   ASSERT_EQ(command_view.GetLeScanInterval(), kScanIntervalFast);
   ASSERT_EQ(command_view.GetLeScanWindow(), kScanWindowFast);
   test_hci_layer_->IncomingEvent(LeCreateConnectionStatusBuilder::Create(ErrorCode::SUCCESS, 0x01));
+
   auto first_connection = GetLeConnectionFuture();
   test_hci_layer_->IncomingLeMetaEvent(LeConnectionCompleteBuilder::Create(
       ErrorCode::SUCCESS,
       0x00,
-      Role::PERIPHERAL,
+      Role::CENTRAL,
       AddressType::PUBLIC_DEVICE_ADDRESS,
       remote,
       0x0100,
       0x0010,
       0x0C80,
       ClockAccuracy::PPM_30));
-  test_hci_layer_->SetCommandFuture();
-  test_hci_layer_->GetCommand(OpCode::LE_REMOVE_DEVICE_FROM_FILTER_ACCEPT_LIST);
+
+  GetConnectionManagementCommand(OpCode::LE_REMOVE_DEVICE_FROM_FILTER_ACCEPT_LIST);
   test_hci_layer_->IncomingEvent(LeRemoveDeviceFromFilterAcceptListCompleteBuilder::Create(0x01, ErrorCode::SUCCESS));
   auto first_connection_status = first_connection.wait_for(kTimeout);
   ASSERT_EQ(first_connection_status, std::future_status::ready);
@@ -798,12 +581,10 @@
 
 TEST_F(AclManagerTest, create_connection_with_slow_mode) {
   AddressWithType remote_with_type(remote, AddressType::PUBLIC_DEVICE_ADDRESS);
-  test_hci_layer_->SetCommandFuture();
   acl_manager_->CreateLeConnection(remote_with_type, false);
-  test_hci_layer_->GetLastCommand(OpCode::LE_ADD_DEVICE_TO_FILTER_ACCEPT_LIST);
+  GetConnectionManagementCommand(OpCode::LE_ADD_DEVICE_TO_FILTER_ACCEPT_LIST);
   test_hci_layer_->IncomingEvent(LeAddDeviceToFilterAcceptListCompleteBuilder::Create(0x01, ErrorCode::SUCCESS));
-  test_hci_layer_->SetCommandFuture();
-  auto packet = test_hci_layer_->GetLastCommand(OpCode::LE_CREATE_CONNECTION);
+  auto packet = GetConnectionManagementCommand(OpCode::LE_CREATE_CONNECTION);
   auto command_view =
       LeCreateConnectionView::Create(LeConnectionManagementCommandView::Create(AclCommandView::Create(packet)));
   ASSERT_TRUE(command_view.IsValid());
@@ -814,15 +595,14 @@
   test_hci_layer_->IncomingLeMetaEvent(LeConnectionCompleteBuilder::Create(
       ErrorCode::SUCCESS,
       0x00,
-      Role::PERIPHERAL,
+      Role::CENTRAL,
       AddressType::PUBLIC_DEVICE_ADDRESS,
       remote,
       0x0100,
       0x0010,
       0x0C80,
       ClockAccuracy::PPM_30));
-  test_hci_layer_->SetCommandFuture();
-  test_hci_layer_->GetCommand(OpCode::LE_REMOVE_DEVICE_FROM_FILTER_ACCEPT_LIST);
+  GetConnectionManagementCommand(OpCode::LE_REMOVE_DEVICE_FROM_FILTER_ACCEPT_LIST);
   test_hci_layer_->IncomingEvent(LeRemoveDeviceFromFilterAcceptListCompleteBuilder::Create(0x01, ErrorCode::SUCCESS));
   auto first_connection_status = first_connection.wait_for(kTimeout);
   ASSERT_EQ(first_connection_status, std::future_status::ready);
@@ -867,19 +647,25 @@
   uint16_t connection_interval = (connection_interval_max + connection_interval_min) / 2;
   uint16_t connection_latency = 0x0001;
   uint16_t supervision_timeout = 0x0A00;
-  test_hci_layer_->SetCommandFuture();
-  connection_->LeConnectionUpdate(connection_interval_min, connection_interval_max, connection_latency,
-                                  supervision_timeout, 0x10, 0x20);
-  auto update_packet = test_hci_layer_->GetCommand(OpCode::LE_CONNECTION_UPDATE);
+  connection_->LeConnectionUpdate(
+      connection_interval_min,
+      connection_interval_max,
+      connection_latency,
+      supervision_timeout,
+      0x10,
+      0x20);
+  auto update_packet = GetConnectionManagementCommand(OpCode::LE_CONNECTION_UPDATE);
   auto update_view =
       LeConnectionUpdateView::Create(LeConnectionManagementCommandView::Create(AclCommandView::Create(update_packet)));
   ASSERT_TRUE(update_view.IsValid());
   EXPECT_EQ(update_view.GetConnectionHandle(), handle_);
+  test_hci_layer_->IncomingEvent(LeConnectionUpdateStatusBuilder::Create(ErrorCode::SUCCESS, 0x1));
   EXPECT_CALL(
       mock_le_connection_management_callbacks_,
       OnConnectionUpdate(hci_status, connection_interval, connection_latency, supervision_timeout));
   test_hci_layer_->IncomingLeMetaEvent(LeConnectionUpdateCompleteBuilder::Create(
       ErrorCode::SUCCESS, handle_, connection_interval, connection_latency, supervision_timeout));
+  sync_client_handler();
 }
 
 TEST_F(AclManagerWithLeConnectionTest, invoke_registered_callback_le_disconnect) {
@@ -890,9 +676,10 @@
   auto reason = ErrorCode::REMOTE_USER_TERMINATED_CONNECTION;
   EXPECT_CALL(mock_le_connection_management_callbacks_, OnDisconnection(reason));
   test_hci_layer_->Disconnect(handle_, reason);
+  sync_client_handler();
 }
 
-TEST_F(AclManagerWithLeConnectionTest, DISABLED_invoke_registered_callback_le_disconnect_data_race) {
+TEST_F(AclManagerWithLeConnectionTest, invoke_registered_callback_le_disconnect_data_race) {
   ASSERT_EQ(connection_->GetRemoteAddress(), remote_with_type_);
   ASSERT_EQ(connection_->GetHandle(), handle_);
   connection_->RegisterCallbacks(&mock_le_connection_management_callbacks_, client_handler_);
@@ -901,6 +688,7 @@
   auto reason = ErrorCode::REMOTE_USER_TERMINATED_CONNECTION;
   EXPECT_CALL(mock_le_connection_management_callbacks_, OnDisconnection(reason));
   test_hci_layer_->Disconnect(handle_, reason);
+  sync_client_handler();
 }
 
 TEST_F(AclManagerWithLeConnectionTest, invoke_registered_callback_le_queue_disconnect) {
@@ -918,6 +706,7 @@
   auto reason = ErrorCode::REMOTE_USER_TERMINATED_CONNECTION;
   EXPECT_CALL(mock_connection_management_callbacks_, OnDisconnection(reason));
   test_hci_layer_->Disconnect(handle_, reason);
+  sync_client_handler();
 }
 
 TEST_F(AclManagerWithConnectionTest, acl_send_data_one_connection) {
@@ -941,15 +730,15 @@
   SendAclData(handle_, connection_->GetAclQueueEnd());
 
   sent_packet = test_hci_layer_->OutgoingAclData();
-  test_hci_layer_->SetCommandFuture();
   auto reason = ErrorCode::AUTHENTICATION_FAILURE;
   EXPECT_CALL(mock_connection_management_callbacks_, OnDisconnection(reason));
   connection_->Disconnect(DisconnectReason::AUTHENTICATION_FAILURE);
-  auto packet = test_hci_layer_->GetCommand(OpCode::DISCONNECT);
+  auto packet = GetConnectionManagementCommand(OpCode::DISCONNECT);
   auto command_view = DisconnectView::Create(packet);
   ASSERT_TRUE(command_view.IsValid());
   ASSERT_EQ(command_view.GetConnectionHandle(), handle_);
   test_hci_layer_->Disconnect(handle_, reason);
+  sync_client_handler();
 }
 
 TEST_F(AclManagerWithConnectionTest, acl_send_data_credits) {
@@ -969,12 +758,12 @@
   test_controller_->CompletePackets(handle_, 1);
 
   auto after_credits_sent_packet = test_hci_layer_->OutgoingAclData();
+  sync_client_handler();
 }
 
 TEST_F(AclManagerWithConnectionTest, send_switch_role) {
-  test_hci_layer_->SetCommandFuture();
   acl_manager_->SwitchRole(connection_->GetAddress(), Role::PERIPHERAL);
-  auto packet = test_hci_layer_->GetCommand(OpCode::SWITCH_ROLE);
+  auto packet = GetConnectionManagementCommand(OpCode::SWITCH_ROLE);
   auto command_view = SwitchRoleView::Create(packet);
   ASSERT_TRUE(command_view.IsValid());
   ASSERT_EQ(command_view.GetBdAddr(), connection_->GetAddress());
@@ -983,13 +772,13 @@
   EXPECT_CALL(mock_connection_management_callbacks_, OnRoleChange(hci::ErrorCode::SUCCESS, Role::PERIPHERAL));
   test_hci_layer_->IncomingEvent(
       RoleChangeBuilder::Create(ErrorCode::SUCCESS, connection_->GetAddress(), Role::PERIPHERAL));
+  sync_client_handler();
 }
 
 TEST_F(AclManagerWithConnectionTest, send_write_default_link_policy_settings) {
-  test_hci_layer_->SetCommandFuture();
   uint16_t link_policy_settings = 0x05;
   acl_manager_->WriteDefaultLinkPolicySettings(link_policy_settings);
-  auto packet = test_hci_layer_->GetCommand(OpCode::WRITE_DEFAULT_LINK_POLICY_SETTINGS);
+  auto packet = GetConnectionManagementCommand(OpCode::WRITE_DEFAULT_LINK_POLICY_SETTINGS);
   auto command_view = WriteDefaultLinkPolicySettingsView::Create(packet);
   ASSERT_TRUE(command_view.IsValid());
   ASSERT_EQ(command_view.GetDefaultLinkPolicySettings(), 0x05);
@@ -997,49 +786,52 @@
   uint8_t num_packets = 1;
   test_hci_layer_->IncomingEvent(
       WriteDefaultLinkPolicySettingsCompleteBuilder::Create(num_packets, ErrorCode::SUCCESS));
+  sync_client_handler();
 
   ASSERT_EQ(link_policy_settings, acl_manager_->ReadDefaultLinkPolicySettings());
 }
 
 TEST_F(AclManagerWithConnectionTest, send_authentication_requested) {
-  test_hci_layer_->SetCommandFuture();
   connection_->AuthenticationRequested();
-  auto packet = test_hci_layer_->GetCommand(OpCode::AUTHENTICATION_REQUESTED);
+  auto packet = GetConnectionManagementCommand(OpCode::AUTHENTICATION_REQUESTED);
   auto command_view = AuthenticationRequestedView::Create(packet);
   ASSERT_TRUE(command_view.IsValid());
 
   EXPECT_CALL(mock_connection_management_callbacks_, OnAuthenticationComplete);
-  test_hci_layer_->IncomingEvent(AuthenticationCompleteBuilder::Create(ErrorCode::SUCCESS, handle_));
+  test_hci_layer_->IncomingEvent(
+      AuthenticationCompleteBuilder::Create(ErrorCode::SUCCESS, handle_));
+  sync_client_handler();
 }
 
 TEST_F(AclManagerWithConnectionTest, send_read_clock_offset) {
-  test_hci_layer_->SetCommandFuture();
   connection_->ReadClockOffset();
-  auto packet = test_hci_layer_->GetCommand(OpCode::READ_CLOCK_OFFSET);
+  auto packet = GetConnectionManagementCommand(OpCode::READ_CLOCK_OFFSET);
   auto command_view = ReadClockOffsetView::Create(packet);
   ASSERT_TRUE(command_view.IsValid());
 
   EXPECT_CALL(mock_connection_management_callbacks_, OnReadClockOffsetComplete(0x0123));
-  test_hci_layer_->IncomingEvent(ReadClockOffsetCompleteBuilder::Create(ErrorCode::SUCCESS, handle_, 0x0123));
+  test_hci_layer_->IncomingEvent(
+      ReadClockOffsetCompleteBuilder::Create(ErrorCode::SUCCESS, handle_, 0x0123));
+  sync_client_handler();
 }
 
 TEST_F(AclManagerWithConnectionTest, send_hold_mode) {
-  test_hci_layer_->SetCommandFuture();
   connection_->HoldMode(0x0500, 0x0020);
-  auto packet = test_hci_layer_->GetCommand(OpCode::HOLD_MODE);
+  auto packet = GetConnectionManagementCommand(OpCode::HOLD_MODE);
   auto command_view = HoldModeView::Create(packet);
   ASSERT_TRUE(command_view.IsValid());
   ASSERT_EQ(command_view.GetHoldModeMaxInterval(), 0x0500);
   ASSERT_EQ(command_view.GetHoldModeMinInterval(), 0x0020);
 
   EXPECT_CALL(mock_connection_management_callbacks_, OnModeChange(ErrorCode::SUCCESS, Mode::HOLD, 0x0020));
-  test_hci_layer_->IncomingEvent(ModeChangeBuilder::Create(ErrorCode::SUCCESS, handle_, Mode::HOLD, 0x0020));
+  test_hci_layer_->IncomingEvent(
+      ModeChangeBuilder::Create(ErrorCode::SUCCESS, handle_, Mode::HOLD, 0x0020));
+  sync_client_handler();
 }
 
 TEST_F(AclManagerWithConnectionTest, send_sniff_mode) {
-  test_hci_layer_->SetCommandFuture();
   connection_->SniffMode(0x0500, 0x0020, 0x0040, 0x0014);
-  auto packet = test_hci_layer_->GetCommand(OpCode::SNIFF_MODE);
+  auto packet = GetConnectionManagementCommand(OpCode::SNIFF_MODE);
   auto command_view = SniffModeView::Create(packet);
   ASSERT_TRUE(command_view.IsValid());
   ASSERT_EQ(command_view.GetSniffMaxInterval(), 0x0500);
@@ -1048,101 +840,109 @@
   ASSERT_EQ(command_view.GetSniffTimeout(), 0x0014);
 
   EXPECT_CALL(mock_connection_management_callbacks_, OnModeChange(ErrorCode::SUCCESS, Mode::SNIFF, 0x0028));
-  test_hci_layer_->IncomingEvent(ModeChangeBuilder::Create(ErrorCode::SUCCESS, handle_, Mode::SNIFF, 0x0028));
+  test_hci_layer_->IncomingEvent(
+      ModeChangeBuilder::Create(ErrorCode::SUCCESS, handle_, Mode::SNIFF, 0x0028));
+  sync_client_handler();
 }
 
 TEST_F(AclManagerWithConnectionTest, send_exit_sniff_mode) {
-  test_hci_layer_->SetCommandFuture();
   connection_->ExitSniffMode();
-  auto packet = test_hci_layer_->GetCommand(OpCode::EXIT_SNIFF_MODE);
+  auto packet = GetConnectionManagementCommand(OpCode::EXIT_SNIFF_MODE);
   auto command_view = ExitSniffModeView::Create(packet);
   ASSERT_TRUE(command_view.IsValid());
 
   EXPECT_CALL(mock_connection_management_callbacks_, OnModeChange(ErrorCode::SUCCESS, Mode::ACTIVE, 0x00));
-  test_hci_layer_->IncomingEvent(ModeChangeBuilder::Create(ErrorCode::SUCCESS, handle_, Mode::ACTIVE, 0x00));
+  test_hci_layer_->IncomingEvent(
+      ModeChangeBuilder::Create(ErrorCode::SUCCESS, handle_, Mode::ACTIVE, 0x00));
+  sync_client_handler();
 }
 
 TEST_F(AclManagerWithConnectionTest, send_qos_setup) {
-  test_hci_layer_->SetCommandFuture();
   connection_->QosSetup(ServiceType::BEST_EFFORT, 0x1234, 0x1233, 0x1232, 0x1231);
-  auto packet = test_hci_layer_->GetCommand(OpCode::QOS_SETUP);
+  auto packet = GetConnectionManagementCommand(OpCode::QOS_SETUP);
   auto command_view = QosSetupView::Create(packet);
   ASSERT_TRUE(command_view.IsValid());
   ASSERT_EQ(command_view.GetServiceType(), ServiceType::BEST_EFFORT);
-  ASSERT_EQ(command_view.GetTokenRate(), 0x1234);
-  ASSERT_EQ(command_view.GetPeakBandwidth(), 0x1233);
-  ASSERT_EQ(command_view.GetLatency(), 0x1232);
-  ASSERT_EQ(command_view.GetDelayVariation(), 0x1231);
+  ASSERT_EQ(command_view.GetTokenRate(), 0x1234u);
+  ASSERT_EQ(command_view.GetPeakBandwidth(), 0x1233u);
+  ASSERT_EQ(command_view.GetLatency(), 0x1232u);
+  ASSERT_EQ(command_view.GetDelayVariation(), 0x1231u);
 
   EXPECT_CALL(mock_connection_management_callbacks_,
               OnQosSetupComplete(ServiceType::BEST_EFFORT, 0x1234, 0x1233, 0x1232, 0x1231));
-  test_hci_layer_->IncomingEvent(QosSetupCompleteBuilder::Create(ErrorCode::SUCCESS, handle_, ServiceType::BEST_EFFORT,
-                                                                 0x1234, 0x1233, 0x1232, 0x1231));
+  test_hci_layer_->IncomingEvent(QosSetupCompleteBuilder::Create(
+      ErrorCode::SUCCESS, handle_, ServiceType::BEST_EFFORT, 0x1234, 0x1233, 0x1232, 0x1231));
+  sync_client_handler();
 }
 
 TEST_F(AclManagerWithConnectionTest, send_flow_specification) {
-  test_hci_layer_->SetCommandFuture();
-  connection_->FlowSpecification(FlowDirection::OUTGOING_FLOW, ServiceType::BEST_EFFORT, 0x1234, 0x1233, 0x1232,
-                                 0x1231);
-  auto packet = test_hci_layer_->GetCommand(OpCode::FLOW_SPECIFICATION);
+  connection_->FlowSpecification(
+      FlowDirection::OUTGOING_FLOW, ServiceType::BEST_EFFORT, 0x1234, 0x1233, 0x1232, 0x1231);
+  auto packet = GetConnectionManagementCommand(OpCode::FLOW_SPECIFICATION);
   auto command_view = FlowSpecificationView::Create(packet);
   ASSERT_TRUE(command_view.IsValid());
   ASSERT_EQ(command_view.GetFlowDirection(), FlowDirection::OUTGOING_FLOW);
   ASSERT_EQ(command_view.GetServiceType(), ServiceType::BEST_EFFORT);
-  ASSERT_EQ(command_view.GetTokenRate(), 0x1234);
-  ASSERT_EQ(command_view.GetTokenBucketSize(), 0x1233);
-  ASSERT_EQ(command_view.GetPeakBandwidth(), 0x1232);
-  ASSERT_EQ(command_view.GetAccessLatency(), 0x1231);
+  ASSERT_EQ(command_view.GetTokenRate(), 0x1234u);
+  ASSERT_EQ(command_view.GetTokenBucketSize(), 0x1233u);
+  ASSERT_EQ(command_view.GetPeakBandwidth(), 0x1232u);
+  ASSERT_EQ(command_view.GetAccessLatency(), 0x1231u);
 
   EXPECT_CALL(mock_connection_management_callbacks_,
               OnFlowSpecificationComplete(FlowDirection::OUTGOING_FLOW, ServiceType::BEST_EFFORT, 0x1234, 0x1233,
                                           0x1232, 0x1231));
-  test_hci_layer_->IncomingEvent(
-      FlowSpecificationCompleteBuilder::Create(ErrorCode::SUCCESS, handle_, FlowDirection::OUTGOING_FLOW,
-                                               ServiceType::BEST_EFFORT, 0x1234, 0x1233, 0x1232, 0x1231));
+  test_hci_layer_->IncomingEvent(FlowSpecificationCompleteBuilder::Create(
+      ErrorCode::SUCCESS,
+      handle_,
+      FlowDirection::OUTGOING_FLOW,
+      ServiceType::BEST_EFFORT,
+      0x1234,
+      0x1233,
+      0x1232,
+      0x1231));
+  sync_client_handler();
 }
 
 TEST_F(AclManagerWithConnectionTest, send_flush) {
-  test_hci_layer_->SetCommandFuture();
   connection_->Flush();
-  auto packet = test_hci_layer_->GetCommand(OpCode::FLUSH);
+  auto packet = GetConnectionManagementCommand(OpCode::FLUSH);
   auto command_view = FlushView::Create(packet);
   ASSERT_TRUE(command_view.IsValid());
 
   EXPECT_CALL(mock_connection_management_callbacks_, OnFlushOccurred());
   test_hci_layer_->IncomingEvent(FlushOccurredBuilder::Create(handle_));
+  sync_client_handler();
 }
 
 TEST_F(AclManagerWithConnectionTest, send_role_discovery) {
-  test_hci_layer_->SetCommandFuture();
   connection_->RoleDiscovery();
-  auto packet = test_hci_layer_->GetCommand(OpCode::ROLE_DISCOVERY);
+  auto packet = GetConnectionManagementCommand(OpCode::ROLE_DISCOVERY);
   auto command_view = RoleDiscoveryView::Create(packet);
   ASSERT_TRUE(command_view.IsValid());
 
   EXPECT_CALL(mock_connection_management_callbacks_, OnRoleDiscoveryComplete(Role::CENTRAL));
   uint8_t num_packets = 1;
-  test_hci_layer_->IncomingEvent(
-      RoleDiscoveryCompleteBuilder::Create(num_packets, ErrorCode::SUCCESS, handle_, Role::CENTRAL));
+  test_hci_layer_->IncomingEvent(RoleDiscoveryCompleteBuilder::Create(
+      num_packets, ErrorCode::SUCCESS, handle_, Role::CENTRAL));
+  sync_client_handler();
 }
 
 TEST_F(AclManagerWithConnectionTest, send_read_link_policy_settings) {
-  test_hci_layer_->SetCommandFuture();
   connection_->ReadLinkPolicySettings();
-  auto packet = test_hci_layer_->GetCommand(OpCode::READ_LINK_POLICY_SETTINGS);
+  auto packet = GetConnectionManagementCommand(OpCode::READ_LINK_POLICY_SETTINGS);
   auto command_view = ReadLinkPolicySettingsView::Create(packet);
   ASSERT_TRUE(command_view.IsValid());
 
   EXPECT_CALL(mock_connection_management_callbacks_, OnReadLinkPolicySettingsComplete(0x07));
   uint8_t num_packets = 1;
-  test_hci_layer_->IncomingEvent(
-      ReadLinkPolicySettingsCompleteBuilder::Create(num_packets, ErrorCode::SUCCESS, handle_, 0x07));
+  test_hci_layer_->IncomingEvent(ReadLinkPolicySettingsCompleteBuilder::Create(
+      num_packets, ErrorCode::SUCCESS, handle_, 0x07));
+  sync_client_handler();
 }
 
 TEST_F(AclManagerWithConnectionTest, send_write_link_policy_settings) {
-  test_hci_layer_->SetCommandFuture();
   connection_->WriteLinkPolicySettings(0x05);
-  auto packet = test_hci_layer_->GetCommand(OpCode::WRITE_LINK_POLICY_SETTINGS);
+  auto packet = GetConnectionManagementCommand(OpCode::WRITE_LINK_POLICY_SETTINGS);
   auto command_view = WriteLinkPolicySettingsView::Create(packet);
   ASSERT_TRUE(command_view.IsValid());
   ASSERT_EQ(command_view.GetLinkPolicySettings(), 0x05);
@@ -1150,12 +950,12 @@
   uint8_t num_packets = 1;
   test_hci_layer_->IncomingEvent(
       WriteLinkPolicySettingsCompleteBuilder::Create(num_packets, ErrorCode::SUCCESS, handle_));
+  sync_client_handler();
 }
 
 TEST_F(AclManagerWithConnectionTest, send_sniff_subrating) {
-  test_hci_layer_->SetCommandFuture();
   connection_->SniffSubrating(0x1234, 0x1235, 0x1236);
-  auto packet = test_hci_layer_->GetCommand(OpCode::SNIFF_SUBRATING);
+  auto packet = GetConnectionManagementCommand(OpCode::SNIFF_SUBRATING);
   auto command_view = SniffSubratingView::Create(packet);
   ASSERT_TRUE(command_view.IsValid());
   ASSERT_EQ(command_view.GetMaximumLatency(), 0x1234);
@@ -1163,26 +963,27 @@
   ASSERT_EQ(command_view.GetMinimumLocalTimeout(), 0x1236);
 
   uint8_t num_packets = 1;
-  test_hci_layer_->IncomingEvent(SniffSubratingCompleteBuilder::Create(num_packets, ErrorCode::SUCCESS, handle_));
+  test_hci_layer_->IncomingEvent(
+      SniffSubratingCompleteBuilder::Create(num_packets, ErrorCode::SUCCESS, handle_));
+  sync_client_handler();
 }
 
 TEST_F(AclManagerWithConnectionTest, send_read_automatic_flush_timeout) {
-  test_hci_layer_->SetCommandFuture();
   connection_->ReadAutomaticFlushTimeout();
-  auto packet = test_hci_layer_->GetCommand(OpCode::READ_AUTOMATIC_FLUSH_TIMEOUT);
+  auto packet = GetConnectionManagementCommand(OpCode::READ_AUTOMATIC_FLUSH_TIMEOUT);
   auto command_view = ReadAutomaticFlushTimeoutView::Create(packet);
   ASSERT_TRUE(command_view.IsValid());
 
   EXPECT_CALL(mock_connection_management_callbacks_, OnReadAutomaticFlushTimeoutComplete(0x07ff));
   uint8_t num_packets = 1;
-  test_hci_layer_->IncomingEvent(
-      ReadAutomaticFlushTimeoutCompleteBuilder::Create(num_packets, ErrorCode::SUCCESS, handle_, 0x07ff));
+  test_hci_layer_->IncomingEvent(ReadAutomaticFlushTimeoutCompleteBuilder::Create(
+      num_packets, ErrorCode::SUCCESS, handle_, 0x07ff));
+  sync_client_handler();
 }
 
 TEST_F(AclManagerWithConnectionTest, send_write_automatic_flush_timeout) {
-  test_hci_layer_->SetCommandFuture();
   connection_->WriteAutomaticFlushTimeout(0x07FF);
-  auto packet = test_hci_layer_->GetCommand(OpCode::WRITE_AUTOMATIC_FLUSH_TIMEOUT);
+  auto packet = GetConnectionManagementCommand(OpCode::WRITE_AUTOMATIC_FLUSH_TIMEOUT);
   auto command_view = WriteAutomaticFlushTimeoutView::Create(packet);
   ASSERT_TRUE(command_view.IsValid());
   ASSERT_EQ(command_view.GetFlushTimeout(), 0x07FF);
@@ -1190,39 +991,39 @@
   uint8_t num_packets = 1;
   test_hci_layer_->IncomingEvent(
       WriteAutomaticFlushTimeoutCompleteBuilder::Create(num_packets, ErrorCode::SUCCESS, handle_));
+  sync_client_handler();
 }
 
 TEST_F(AclManagerWithConnectionTest, send_read_transmit_power_level) {
-  test_hci_layer_->SetCommandFuture();
   connection_->ReadTransmitPowerLevel(TransmitPowerLevelType::CURRENT);
-  auto packet = test_hci_layer_->GetCommand(OpCode::READ_TRANSMIT_POWER_LEVEL);
+  auto packet = GetConnectionManagementCommand(OpCode::READ_TRANSMIT_POWER_LEVEL);
   auto command_view = ReadTransmitPowerLevelView::Create(packet);
   ASSERT_TRUE(command_view.IsValid());
   ASSERT_EQ(command_view.GetTransmitPowerLevelType(), TransmitPowerLevelType::CURRENT);
 
   EXPECT_CALL(mock_connection_management_callbacks_, OnReadTransmitPowerLevelComplete(0x07));
   uint8_t num_packets = 1;
-  test_hci_layer_->IncomingEvent(
-      ReadTransmitPowerLevelCompleteBuilder::Create(num_packets, ErrorCode::SUCCESS, handle_, 0x07));
+  test_hci_layer_->IncomingEvent(ReadTransmitPowerLevelCompleteBuilder::Create(
+      num_packets, ErrorCode::SUCCESS, handle_, 0x07));
+  sync_client_handler();
 }
 
 TEST_F(AclManagerWithConnectionTest, send_read_link_supervision_timeout) {
-  test_hci_layer_->SetCommandFuture();
   connection_->ReadLinkSupervisionTimeout();
-  auto packet = test_hci_layer_->GetCommand(OpCode::READ_LINK_SUPERVISION_TIMEOUT);
+  auto packet = GetConnectionManagementCommand(OpCode::READ_LINK_SUPERVISION_TIMEOUT);
   auto command_view = ReadLinkSupervisionTimeoutView::Create(packet);
   ASSERT_TRUE(command_view.IsValid());
 
   EXPECT_CALL(mock_connection_management_callbacks_, OnReadLinkSupervisionTimeoutComplete(0x5677));
   uint8_t num_packets = 1;
-  test_hci_layer_->IncomingEvent(
-      ReadLinkSupervisionTimeoutCompleteBuilder::Create(num_packets, ErrorCode::SUCCESS, handle_, 0x5677));
+  test_hci_layer_->IncomingEvent(ReadLinkSupervisionTimeoutCompleteBuilder::Create(
+      num_packets, ErrorCode::SUCCESS, handle_, 0x5677));
+  sync_client_handler();
 }
 
 TEST_F(AclManagerWithConnectionTest, send_write_link_supervision_timeout) {
-  test_hci_layer_->SetCommandFuture();
   connection_->WriteLinkSupervisionTimeout(0x5678);
-  auto packet = test_hci_layer_->GetCommand(OpCode::WRITE_LINK_SUPERVISION_TIMEOUT);
+  auto packet = GetConnectionManagementCommand(OpCode::WRITE_LINK_SUPERVISION_TIMEOUT);
   auto command_view = WriteLinkSupervisionTimeoutView::Create(packet);
   ASSERT_TRUE(command_view.IsValid());
   ASSERT_EQ(command_view.GetLinkSupervisionTimeout(), 0x5678);
@@ -1230,37 +1031,37 @@
   uint8_t num_packets = 1;
   test_hci_layer_->IncomingEvent(
       WriteLinkSupervisionTimeoutCompleteBuilder::Create(num_packets, ErrorCode::SUCCESS, handle_));
+  sync_client_handler();
 }
 
 TEST_F(AclManagerWithConnectionTest, send_read_failed_contact_counter) {
-  test_hci_layer_->SetCommandFuture();
   connection_->ReadFailedContactCounter();
-  auto packet = test_hci_layer_->GetCommand(OpCode::READ_FAILED_CONTACT_COUNTER);
+  auto packet = GetConnectionManagementCommand(OpCode::READ_FAILED_CONTACT_COUNTER);
   auto command_view = ReadFailedContactCounterView::Create(packet);
   ASSERT_TRUE(command_view.IsValid());
 
   EXPECT_CALL(mock_connection_management_callbacks_, OnReadFailedContactCounterComplete(0x00));
   uint8_t num_packets = 1;
-  test_hci_layer_->IncomingEvent(
-      ReadFailedContactCounterCompleteBuilder::Create(num_packets, ErrorCode::SUCCESS, handle_, 0x00));
+  test_hci_layer_->IncomingEvent(ReadFailedContactCounterCompleteBuilder::Create(
+      num_packets, ErrorCode::SUCCESS, handle_, 0x00));
+  sync_client_handler();
 }
 
 TEST_F(AclManagerWithConnectionTest, send_reset_failed_contact_counter) {
-  test_hci_layer_->SetCommandFuture();
   connection_->ResetFailedContactCounter();
-  auto packet = test_hci_layer_->GetCommand(OpCode::RESET_FAILED_CONTACT_COUNTER);
+  auto packet = GetConnectionManagementCommand(OpCode::RESET_FAILED_CONTACT_COUNTER);
   auto command_view = ResetFailedContactCounterView::Create(packet);
   ASSERT_TRUE(command_view.IsValid());
 
   uint8_t num_packets = 1;
   test_hci_layer_->IncomingEvent(
       ResetFailedContactCounterCompleteBuilder::Create(num_packets, ErrorCode::SUCCESS, handle_));
+  sync_client_handler();
 }
 
 TEST_F(AclManagerWithConnectionTest, send_read_link_quality) {
-  test_hci_layer_->SetCommandFuture();
   connection_->ReadLinkQuality();
-  auto packet = test_hci_layer_->GetCommand(OpCode::READ_LINK_QUALITY);
+  auto packet = GetConnectionManagementCommand(OpCode::READ_LINK_QUALITY);
   auto command_view = ReadLinkQualityView::Create(packet);
   ASSERT_TRUE(command_view.IsValid());
 
@@ -1268,12 +1069,12 @@
   uint8_t num_packets = 1;
   test_hci_layer_->IncomingEvent(
       ReadLinkQualityCompleteBuilder::Create(num_packets, ErrorCode::SUCCESS, handle_, 0xa9));
+  sync_client_handler();
 }
 
 TEST_F(AclManagerWithConnectionTest, send_read_afh_channel_map) {
-  test_hci_layer_->SetCommandFuture();
   connection_->ReadAfhChannelMap();
-  auto packet = test_hci_layer_->GetCommand(OpCode::READ_AFH_CHANNEL_MAP);
+  auto packet = GetConnectionManagementCommand(OpCode::READ_AFH_CHANNEL_MAP);
   auto command_view = ReadAfhChannelMapView::Create(packet);
   ASSERT_TRUE(command_view.IsValid());
   std::array<uint8_t, 10> afh_channel_map = {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09};
@@ -1281,34 +1082,36 @@
   EXPECT_CALL(mock_connection_management_callbacks_,
               OnReadAfhChannelMapComplete(AfhMode::AFH_ENABLED, afh_channel_map));
   uint8_t num_packets = 1;
-  test_hci_layer_->IncomingEvent(ReadAfhChannelMapCompleteBuilder::Create(num_packets, ErrorCode::SUCCESS, handle_,
-                                                                          AfhMode::AFH_ENABLED, afh_channel_map));
+  test_hci_layer_->IncomingEvent(ReadAfhChannelMapCompleteBuilder::Create(
+      num_packets, ErrorCode::SUCCESS, handle_, AfhMode::AFH_ENABLED, afh_channel_map));
+  sync_client_handler();
 }
 
 TEST_F(AclManagerWithConnectionTest, send_read_rssi) {
-  test_hci_layer_->SetCommandFuture();
   connection_->ReadRssi();
-  auto packet = test_hci_layer_->GetCommand(OpCode::READ_RSSI);
+  auto packet = GetConnectionManagementCommand(OpCode::READ_RSSI);
   auto command_view = ReadRssiView::Create(packet);
   ASSERT_TRUE(command_view.IsValid());
   sync_client_handler();
   EXPECT_CALL(mock_connection_management_callbacks_, OnReadRssiComplete(0x00));
   uint8_t num_packets = 1;
-  test_hci_layer_->IncomingEvent(ReadRssiCompleteBuilder::Create(num_packets, ErrorCode::SUCCESS, handle_, 0x00));
+  test_hci_layer_->IncomingEvent(
+      ReadRssiCompleteBuilder::Create(num_packets, ErrorCode::SUCCESS, handle_, 0x00));
+  sync_client_handler();
 }
 
 TEST_F(AclManagerWithConnectionTest, send_read_clock) {
-  test_hci_layer_->SetCommandFuture();
   connection_->ReadClock(WhichClock::LOCAL);
-  auto packet = test_hci_layer_->GetCommand(OpCode::READ_CLOCK);
+  auto packet = GetConnectionManagementCommand(OpCode::READ_CLOCK);
   auto command_view = ReadClockView::Create(packet);
   ASSERT_TRUE(command_view.IsValid());
   ASSERT_EQ(command_view.GetWhichClock(), WhichClock::LOCAL);
 
   EXPECT_CALL(mock_connection_management_callbacks_, OnReadClockComplete(0x00002e6a, 0x0000));
   uint8_t num_packets = 1;
-  test_hci_layer_->IncomingEvent(
-      ReadClockCompleteBuilder::Create(num_packets, ErrorCode::SUCCESS, handle_, 0x00002e6a, 0x0000));
+  test_hci_layer_->IncomingEvent(ReadClockCompleteBuilder::Create(
+      num_packets, ErrorCode::SUCCESS, handle_, 0x00002e6a, 0x0000));
+  sync_client_handler();
 }
 
 class AclManagerWithResolvableAddressTest : public AclManagerNoCallbacksTest {
@@ -1320,7 +1123,6 @@
     fake_registry_.InjectTestModule(&Controller::Factory, test_controller_);
     client_handler_ = fake_registry_.GetTestModuleHandler(&HciLayer::Factory);
     ASSERT_NE(client_handler_, nullptr);
-    test_hci_layer_->SetCommandFuture();
     fake_registry_.Start<AclManager>(&thread_);
     acl_manager_ = static_cast<AclManager*>(fake_registry_.GetModuleUnderTest(&AclManager::Factory));
     Address::FromString("A1:A2:A3:A4:A5:A6", remote);
@@ -1338,28 +1140,22 @@
         minimum_rotation_time,
         maximum_rotation_time);
 
-    test_hci_layer_->GetLastCommand(OpCode::LE_SET_RANDOM_ADDRESS);
+    GetConnectionManagementCommand(OpCode::LE_SET_RANDOM_ADDRESS);
     test_hci_layer_->IncomingEvent(LeSetRandomAddressCompleteBuilder::Create(0x01, ErrorCode::SUCCESS));
   }
 };
 
 TEST_F(AclManagerWithResolvableAddressTest, create_connection_cancel_fail) {
   auto remote_with_type_ = AddressWithType(remote, AddressType::PUBLIC_DEVICE_ADDRESS);
-  test_hci_layer_->SetCommandFuture();
   acl_manager_->CreateLeConnection(remote_with_type_, true);
 
-  // Set random address
-  test_hci_layer_->GetLastCommand(OpCode::LE_SET_RANDOM_ADDRESS);
-  test_hci_layer_->SetCommandFuture();
-  test_hci_layer_->IncomingEvent(LeSetRandomAddressCompleteBuilder::Create(0x01, ErrorCode::SUCCESS));
-
   // Add device to connect list
-  test_hci_layer_->GetLastCommand(OpCode::LE_ADD_DEVICE_TO_FILTER_ACCEPT_LIST);
-  test_hci_layer_->SetCommandFuture();
-  test_hci_layer_->IncomingEvent(LeAddDeviceToFilterAcceptListCompleteBuilder::Create(0x01, ErrorCode::SUCCESS));
+  GetConnectionManagementCommand(OpCode::LE_ADD_DEVICE_TO_FILTER_ACCEPT_LIST);
+  test_hci_layer_->IncomingEvent(
+      LeAddDeviceToFilterAcceptListCompleteBuilder::Create(0x01, ErrorCode::SUCCESS));
 
   // send create connection command
-  test_hci_layer_->GetLastCommand(OpCode::LE_CREATE_CONNECTION);
+  GetConnectionManagementCommand(OpCode::LE_CREATE_CONNECTION);
   test_hci_layer_->IncomingEvent(LeCreateConnectionStatusBuilder::Create(ErrorCode::SUCCESS, 0x01));
 
   fake_registry_.SynchronizeModuleHandler(&HciLayer::Factory, std::chrono::milliseconds(20));
@@ -1370,11 +1166,10 @@
   auto remote_with_type2 = AddressWithType(remote2, AddressType::PUBLIC_DEVICE_ADDRESS);
 
   // create another connection
-  test_hci_layer_->SetCommandFuture();
   acl_manager_->CreateLeConnection(remote_with_type2, true);
 
   // cancel previous connection
-  test_hci_layer_->GetLastCommand(OpCode::LE_CREATE_CONNECTION_CANCEL);
+  GetConnectionManagementCommand(OpCode::LE_CREATE_CONNECTION_CANCEL);
 
   // receive connection complete of first device
   test_hci_layer_->IncomingLeMetaEvent(LeConnectionCompleteBuilder::Create(
@@ -1389,13 +1184,14 @@
       ClockAccuracy::PPM_30));
 
   // receive create connection cancel complete with ErrorCode::CONNECTION_ALREADY_EXISTS
-  test_hci_layer_->SetCommandFuture();
   test_hci_layer_->IncomingEvent(
       LeCreateConnectionCancelCompleteBuilder::Create(0x01, ErrorCode::CONNECTION_ALREADY_EXISTS));
 
   // Add another device to connect list
-  test_hci_layer_->GetLastCommand(OpCode::LE_ADD_DEVICE_TO_FILTER_ACCEPT_LIST);
+  GetConnectionManagementCommand(OpCode::LE_ADD_DEVICE_TO_FILTER_ACCEPT_LIST);
   test_hci_layer_->IncomingEvent(LeAddDeviceToFilterAcceptListCompleteBuilder::Create(0x01, ErrorCode::SUCCESS));
+
+  // Sync events.
 }
 
 class AclManagerLifeCycleTest : public AclManagerNoCallbacksTest {
@@ -1412,9 +1208,8 @@
 
 TEST_F(AclManagerLifeCycleTest, unregister_classic_after_create_connection) {
   // Inject create connection
-  test_hci_layer_->SetCommandFuture();
   acl_manager_->CreateConnection(remote);
-  auto connection_command = test_hci_layer_->GetCommand(OpCode::CREATE_CONNECTION);
+  auto connection_command = GetConnectionManagementCommand(OpCode::CREATE_CONNECTION);
 
   // Unregister callbacks after sending connection request
   auto promise = std::promise<void>();
@@ -1426,38 +1221,19 @@
   auto connection_future = GetConnectionFuture();
   test_hci_layer_->IncomingEvent(
       ConnectionCompleteBuilder::Create(ErrorCode::SUCCESS, handle_, remote, LinkType::ACL, Enable::DISABLED));
-  auto connection_future_status = connection_future.wait_for(kTimeout);
+
+  sync_client_handler();
+  auto connection_future_status = connection_future.wait_for(kShortTimeout);
   ASSERT_NE(connection_future_status, std::future_status::ready);
 }
 
-TEST_F(AclManagerLifeCycleTest, unregister_classic_before_connection_request) {
-  ClassOfDevice class_of_device;
-
-  // Unregister callbacks before receiving connection request
-  auto promise = std::promise<void>();
-  auto future = promise.get_future();
-  acl_manager_->UnregisterCallbacks(&mock_connection_callback_, std::move(promise));
-  future.get();
-
-  // Inject peer sending connection request
-  auto connection_future = GetConnectionFuture();
-  test_hci_layer_->IncomingEvent(
-      ConnectionRequestBuilder::Create(remote, class_of_device, ConnectionRequestLinkType::ACL));
-  auto connection_future_status = connection_future.wait_for(kTimeout);
-  ASSERT_NE(connection_future_status, std::future_status::ready);
-
-  test_hci_layer_->GetLastCommand(OpCode::REJECT_CONNECTION_REQUEST);
-}
-
 TEST_F(AclManagerLifeCycleTest, unregister_le_before_connection_complete) {
   AddressWithType remote_with_type(remote, AddressType::PUBLIC_DEVICE_ADDRESS);
-  test_hci_layer_->SetCommandFuture();
   acl_manager_->CreateLeConnection(remote_with_type, true);
-  test_hci_layer_->GetLastCommand(OpCode::LE_ADD_DEVICE_TO_FILTER_ACCEPT_LIST);
+  GetConnectionManagementCommand(OpCode::LE_ADD_DEVICE_TO_FILTER_ACCEPT_LIST);
   test_hci_layer_->IncomingEvent(LeAddDeviceToFilterAcceptListCompleteBuilder::Create(0x01, ErrorCode::SUCCESS));
 
-  test_hci_layer_->SetCommandFuture();
-  auto packet = test_hci_layer_->GetLastCommand(OpCode::LE_CREATE_CONNECTION);
+  auto packet = GetConnectionManagementCommand(OpCode::LE_CREATE_CONNECTION);
   auto le_connection_management_command_view =
       LeConnectionManagementCommandView::Create(AclCommandView::Create(packet));
   auto command_view = LeCreateConnectionView::Create(le_connection_management_command_view);
@@ -1487,19 +1263,18 @@
       0x0500,
       ClockAccuracy::PPM_30));
 
-  auto connection_future_status = connection_future.wait_for(kTimeout);
+  sync_client_handler();
+  auto connection_future_status = connection_future.wait_for(kShortTimeout);
   ASSERT_NE(connection_future_status, std::future_status::ready);
 }
 
 TEST_F(AclManagerLifeCycleTest, unregister_le_before_enhanced_connection_complete) {
   AddressWithType remote_with_type(remote, AddressType::PUBLIC_DEVICE_ADDRESS);
-  test_hci_layer_->SetCommandFuture();
   acl_manager_->CreateLeConnection(remote_with_type, true);
-  test_hci_layer_->GetLastCommand(OpCode::LE_ADD_DEVICE_TO_FILTER_ACCEPT_LIST);
+  GetConnectionManagementCommand(OpCode::LE_ADD_DEVICE_TO_FILTER_ACCEPT_LIST);
   test_hci_layer_->IncomingEvent(LeAddDeviceToFilterAcceptListCompleteBuilder::Create(0x01, ErrorCode::SUCCESS));
 
-  test_hci_layer_->SetCommandFuture();
-  auto packet = test_hci_layer_->GetLastCommand(OpCode::LE_CREATE_CONNECTION);
+  auto packet = GetConnectionManagementCommand(OpCode::LE_CREATE_CONNECTION);
   auto le_connection_management_command_view =
       LeConnectionManagementCommandView::Create(AclCommandView::Create(packet));
   auto command_view = LeCreateConnectionView::Create(le_connection_management_command_view);
@@ -1531,7 +1306,8 @@
       0x0500,
       ClockAccuracy::PPM_30));
 
-  auto connection_future_status = connection_future.wait_for(kTimeout);
+  sync_client_handler();
+  auto connection_future_status = connection_future.wait_for(kShortTimeout);
   ASSERT_NE(connection_future_status, std::future_status::ready);
 }
 
diff --git a/system/gd/hci/acl_manager_unittest.cc b/system/gd/hci/acl_manager_unittest.cc
index 37d96db..31c71eb 100644
--- a/system/gd/hci/acl_manager_unittest.cc
+++ b/system/gd/hci/acl_manager_unittest.cc
@@ -21,11 +21,15 @@
 
 #include <algorithm>
 #include <chrono>
+#include <deque>
 #include <future>
+#include <list>
 #include <map>
 
 #include "common/bind.h"
+#include "common/init_flags.h"
 #include "hci/address.h"
+#include "hci/address_with_type.h"
 #include "hci/class_of_device.h"
 #include "hci/controller.h"
 #include "hci/hci_layer.h"
@@ -45,9 +49,28 @@
 using packet::PacketView;
 using packet::RawBuilder;
 
-constexpr std::chrono::seconds kTimeout = std::chrono::seconds(2);
+namespace {
+constexpr char kLocalRandomAddressString[] = "D0:05:04:03:02:01";
+constexpr char kRemotePublicDeviceStringA[] = "11:A2:A3:A4:A5:A6";
+constexpr char kRemotePublicDeviceStringB[] = "11:B2:B3:B4:B5:B6";
+constexpr uint16_t kHciHandleA = 123;
+constexpr uint16_t kHciHandleB = 456;
+
+constexpr auto kMinimumRotationTime = std::chrono::milliseconds(7 * 60 * 1000);
+constexpr auto kMaximumRotationTime = std::chrono::milliseconds(15 * 60 * 1000);
+
 const AddressWithType empty_address_with_type = hci::AddressWithType();
 
+struct {
+  Address address;
+  ClassOfDevice class_of_device;
+  const uint16_t handle;
+} remote_device[2] = {
+    {.address = {}, .class_of_device = {}, .handle = kHciHandleA},
+    {.address = {}, .class_of_device = {}, .handle = kHciHandleB},
+};
+}  // namespace
+
 PacketView<kLittleEndian> GetPacketView(std::unique_ptr<packet::BasePacketBuilder> packet) {
   auto bytes = std::make_shared<std::vector<uint8_t>>();
   BitInserter i(*bytes);
@@ -74,15 +97,6 @@
 
 class TestController : public Controller {
  public:
-  void RegisterCompletedAclPacketsCallback(
-      common::ContextualCallback<void(uint16_t /* handle */, uint16_t /* packets */)> cb) override {
-    acl_cb_ = cb;
-  }
-
-  void UnregisterCompletedAclPacketsCallback() override {
-    acl_cb_ = {};
-  }
-
   uint16_t GetAclPacketLength() const override {
     return acl_buffer_length_;
   }
@@ -102,18 +116,15 @@
     return le_buffer_size;
   }
 
-  void CompletePackets(uint16_t handle, uint16_t packets) {
-    acl_cb_.Invoke(handle, packets);
-  }
-
-  uint16_t acl_buffer_length_ = 1024;
-  uint16_t total_acl_buffers_ = 2;
-  common::ContextualCallback<void(uint16_t /* handle */, uint16_t /* packets */)> acl_cb_;
-
  protected:
   void Start() override {}
   void Stop() override {}
   void ListDependencies(ModuleList* list) const {}
+
+ private:
+  uint16_t acl_buffer_length_ = 1024;
+  uint16_t total_acl_buffers_ = 2;
+  common::ContextualCallback<void(uint16_t /* handle */, uint16_t /* packets */)> acl_cb_;
 };
 
 class TestHciLayer : public HciLayer {
@@ -123,10 +134,7 @@
       common::ContextualOnceCallback<void(CommandStatusView)> on_status) override {
     command_queue_.push(std::move(command));
     command_status_callbacks.push_back(std::move(on_status));
-    if (command_promise_ != nullptr) {
-      command_promise_->set_value();
-      command_promise_.reset();
-    }
+    Notify();
   }
 
   void EnqueueCommand(
@@ -134,16 +142,18 @@
       common::ContextualOnceCallback<void(CommandCompleteView)> on_complete) override {
     command_queue_.push(std::move(command));
     command_complete_callbacks.push_back(std::move(on_complete));
-    if (command_promise_ != nullptr) {
-      command_promise_->set_value();
-      command_promise_.reset();
-    }
+    Notify();
   }
 
   void SetCommandFuture() {
-    ASSERT_TRUE(command_promise_ == nullptr) << "Promises, Promises, ... Only one at a time.";
-    command_promise_ = std::make_unique<std::promise<void>>();
-    command_future_ = std::make_unique<std::future<void>>(command_promise_->get_future());
+    ASSERT_EQ(hci_command_promise_, nullptr) << "Promises, Promises, ... Only one at a time.";
+    hci_command_promise_ = std::make_unique<std::promise<void>>();
+    command_future_ = std::make_unique<std::future<void>>(hci_command_promise_->get_future());
+  }
+
+  std::future<void> GetOutgoingCommandFuture() {
+    hci_command_promise_ = std::make_unique<std::promise<void>>();
+    return hci_command_promise_->get_future();
   }
 
   CommandView GetLastCommand() {
@@ -173,9 +183,10 @@
   ConnectionManagementCommandView GetLastCommand(OpCode op_code) {
     if (!command_queue_.empty() && command_future_ != nullptr) {
       command_future_.reset();
-      command_promise_.reset();
+      hci_command_promise_.reset();
     } else if (command_future_ != nullptr) {
       command_future_->wait_for(std::chrono::milliseconds(1000));
+      hci_command_promise_.reset();
     }
     if (command_queue_.empty()) {
       return ConnectionManagementCommandView::Create(AclCommandView::Create(
@@ -188,6 +199,19 @@
     return command;
   }
 
+  ConnectionManagementCommandView GetLastOutgoingCommand() {
+    if (command_queue_.empty()) {
+      // An empty packet will force a failure on |IsValid()| required by all packets before usage
+      return ConnectionManagementCommandView::Create(AclCommandView::Create(
+          CommandView::Create(PacketView<kLittleEndian>(std::make_shared<std::vector<uint8_t>>()))));
+    } else {
+      CommandView command_packet_view = GetLastCommand();
+      ConnectionManagementCommandView command =
+          ConnectionManagementCommandView::Create(AclCommandView::Create(command_packet_view));
+      return command;
+    }
+  }
+
   void RegisterEventHandler(EventCode event_code, common::ContextualCallback<void(EventView)> event_handler) override {
     registered_events_[event_code] = event_handler;
   }
@@ -205,7 +229,7 @@
     registered_le_events_.erase(subevent_code);
   }
 
-  void IncomingEvent(std::unique_ptr<EventBuilder> event_builder) {
+  void SendIncomingEvent(std::unique_ptr<EventBuilder> event_builder) {
     auto packet = GetPacketView(std::move(event_builder));
     EventView event = EventView::Create(packet);
     ASSERT_TRUE(event.IsValid());
@@ -242,7 +266,7 @@
             queue_end,
             handle,
             common::Passed(std::move(promise))));
-    auto status = future.wait_for(kTimeout);
+    auto status = future.wait_for(2s);
     ASSERT_EQ(status, std::future_status::ready);
   }
 
@@ -291,7 +315,17 @@
     GetHandler()->Post(common::BindOnce(&TestHciLayer::do_disconnect, common::Unretained(this), handle, reason));
   }
 
+  std::unique_ptr<std::promise<void>> hci_command_promise_;
+
  private:
+  void Notify() {
+    if (hci_command_promise_ != nullptr) {
+      std::promise<void>* prom = hci_command_promise_.release();
+      prom->set_value();
+      delete prom;
+    }
+  }
+
   std::map<EventCode, common::ContextualCallback<void(EventView)>> registered_events_;
   std::map<SubeventCode, common::ContextualCallback<void(LeMetaEventView)>> registered_le_events_;
   std::list<common::ContextualOnceCallback<void(CommandCompleteView)>> command_complete_callbacks;
@@ -299,7 +333,6 @@
   BidiQueue<AclView, AclBuilder> acl_queue_{3 /* TODO: Set queue depth */};
 
   std::queue<std::unique_ptr<CommandBuilder>> command_queue_;
-  std::unique_ptr<std::promise<void>> command_promise_;
   std::unique_ptr<std::future<void>> command_future_;
 
   void do_disconnect(uint16_t handle, ErrorCode reason) {
@@ -307,90 +340,122 @@
   }
 };
 
-class AclManagerNoCallbacksTest : public ::testing::Test {
+class MockConnectionCallback : public ConnectionCallbacks {
+ public:
+  void OnConnectSuccess(std::unique_ptr<ClassicAclConnection> connection) override {
+    // Convert to std::shared_ptr during push_back()
+    connections_.push_back(std::move(connection));
+    if (is_promise_set_) {
+      is_promise_set_ = false;
+      connection_promise_.set_value(connections_.back());
+    }
+  }
+  MOCK_METHOD(void, OnConnectFail, (Address, ErrorCode reason), (override));
+
+  MOCK_METHOD(void, HACK_OnEscoConnectRequest, (Address, ClassOfDevice), (override));
+  MOCK_METHOD(void, HACK_OnScoConnectRequest, (Address, ClassOfDevice), (override));
+
+  size_t NumberOfConnections() const {
+    return connections_.size();
+  }
+
+ private:
+  friend class AclManagerWithCallbacksTest;
+  friend class AclManagerNoCallbacksTest;
+
+  std::deque<std::shared_ptr<ClassicAclConnection>> connections_;
+  std::promise<std::shared_ptr<ClassicAclConnection>> connection_promise_;
+  bool is_promise_set_{false};
+};
+
+class MockLeConnectionCallbacks : public LeConnectionCallbacks {
+ public:
+  void OnLeConnectSuccess(AddressWithType address_with_type, std::unique_ptr<LeAclConnection> connection) override {
+    le_connections_.push_back(std::move(connection));
+    if (le_connection_promise_ != nullptr) {
+      std::promise<void>* prom = le_connection_promise_.release();
+      prom->set_value();
+      delete prom;
+    }
+  }
+  MOCK_METHOD(void, OnLeConnectFail, (AddressWithType, ErrorCode reason), (override));
+
+  std::deque<std::shared_ptr<LeAclConnection>> le_connections_;
+  std::unique_ptr<std::promise<void>> le_connection_promise_;
+};
+
+class AclManagerBaseTest : public ::testing::Test {
  protected:
   void SetUp() override {
+    common::InitFlags::SetAllForTesting();
     test_hci_layer_ = new TestHciLayer;  // Ownership is transferred to registry
+    ASSERT_TRUE(test_hci_layer_->hci_command_promise_ == nullptr) << "hci command is nullptr";
     test_controller_ = new TestController;
     fake_registry_.InjectTestModule(&HciLayer::Factory, test_hci_layer_);
     fake_registry_.InjectTestModule(&Controller::Factory, test_controller_);
     client_handler_ = fake_registry_.GetTestModuleHandler(&HciLayer::Factory);
     ASSERT_NE(client_handler_, nullptr);
-    test_hci_layer_->SetCommandFuture();
     fake_registry_.Start<AclManager>(&thread_);
-    acl_manager_ = static_cast<AclManager*>(fake_registry_.GetModuleUnderTest(&AclManager::Factory));
-    Address::FromString("A1:A2:A3:A4:A5:A6", remote);
-
-    hci::Address address;
-    Address::FromString("D0:05:04:03:02:01", address);
-    hci::AddressWithType address_with_type(address, hci::AddressType::RANDOM_DEVICE_ADDRESS);
-    auto minimum_rotation_time = std::chrono::milliseconds(7 * 60 * 1000);
-    auto maximum_rotation_time = std::chrono::milliseconds(15 * 60 * 1000);
-    acl_manager_->SetPrivacyPolicyForInitiatorAddress(
-        LeAddressManager::AddressPolicy::USE_STATIC_ADDRESS,
-        address_with_type,
-        minimum_rotation_time,
-        maximum_rotation_time);
-
-    auto set_random_address_packet = LeSetRandomAddressView::Create(
-        LeAdvertisingCommandView::Create(test_hci_layer_->GetCommand(OpCode::LE_SET_RANDOM_ADDRESS)));
-    ASSERT_TRUE(set_random_address_packet.IsValid());
-    my_initiating_address =
-        AddressWithType(set_random_address_packet.GetRandomAddress(), AddressType::RANDOM_DEVICE_ADDRESS);
-    // Verify LE Set Random Address was sent during setup
-    test_hci_layer_->GetLastCommand(OpCode::LE_SET_RANDOM_ADDRESS);
-    test_hci_layer_->IncomingEvent(LeSetRandomAddressCompleteBuilder::Create(0x01, ErrorCode::SUCCESS));
+    ASSERT_TRUE(test_hci_layer_->hci_command_promise_ == nullptr) << "hci command is nullptr";
   }
 
   void TearDown() override {
-    mock_connection_callbacks_.connections_.clear();
-    mock_le_connection_callbacks_.le_connections_.clear();
-
     fake_registry_.SynchronizeModuleHandler(&AclManager::Factory, std::chrono::milliseconds(20));
     fake_registry_.StopAll();
   }
 
-  TestModuleRegistry fake_registry_;
+  void sync_client_handler() {
+    std::promise<void> promise;
+    auto future = promise.get_future();
+    client_handler_->Post(common::BindOnce(&std::promise<void>::set_value, common::Unretained(&promise)));
+    auto future_status = future.wait_for(std::chrono::seconds(1));
+    ASSERT_EQ(future_status, std::future_status::ready);
+  }
+
   TestHciLayer* test_hci_layer_ = nullptr;
   TestController* test_controller_ = nullptr;
+
+  TestModuleRegistry fake_registry_;
   os::Thread& thread_ = fake_registry_.GetTestThread();
   AclManager* acl_manager_ = nullptr;
   os::Handler* client_handler_ = nullptr;
-  Address remote;
-  AddressWithType my_initiating_address;
+};
+
+class AclManagerNoCallbacksTest : public AclManagerBaseTest {
+ protected:
+  void SetUp() override {
+    AclManagerBaseTest::SetUp();
+    ASSERT_TRUE(test_hci_layer_->hci_command_promise_ == nullptr) << "hci command is nullptr";
+
+    acl_manager_ = static_cast<AclManager*>(fake_registry_.GetModuleUnderTest(&AclManager::Factory));
+
+    local_address_with_type_ = AddressWithType(
+        Address::FromString(kLocalRandomAddressString).value(), hci::AddressType::RANDOM_DEVICE_ADDRESS);
+
+    ASSERT_TRUE(test_hci_layer_->hci_command_promise_ == nullptr) << "hci command is nullptr";
+    auto future = test_hci_layer_->GetOutgoingCommandFuture();
+
+    acl_manager_->SetPrivacyPolicyForInitiatorAddress(
+        LeAddressManager::AddressPolicy::USE_STATIC_ADDRESS,
+        local_address_with_type_,
+        kMinimumRotationTime,
+        kMaximumRotationTime);
+
+    ASSERT_EQ(std::future_status::ready, future.wait_for(2s));
+    sync_client_handler();
+    ASSERT_TRUE(test_hci_layer_->hci_command_promise_ == nullptr) << "hci command is nullptr";
+    auto command = test_hci_layer_->GetLastOutgoingCommand();
+    ASSERT_TRUE(command.IsValid());
+    ASSERT_EQ(OpCode::LE_SET_RANDOM_ADDRESS, command.GetOpCode());
+  }
+
+  void TearDown() override {
+    AclManagerBaseTest::TearDown();
+  }
+
+  AddressWithType local_address_with_type_;
   const bool use_connect_list_ = true;  // gd currently only supports connect list
 
-  void check_connection_promise_not_null() {
-    ASSERT_TRUE(mock_connection_callbacks_.connection_promise_ == nullptr)
-        << "Promises promises ... Only one at a time";
-  }
-
-  std::future<void> GetConnectionFuture() {
-    check_connection_promise_not_null();
-    mock_connection_callbacks_.connection_promise_ = std::make_unique<std::promise<void>>();
-    return mock_connection_callbacks_.connection_promise_->get_future();
-  }
-
-  std::future<void> GetLeConnectionFuture() {
-    check_connection_promise_not_null();
-    mock_le_connection_callbacks_.le_connection_promise_ = std::make_unique<std::promise<void>>();
-    return mock_le_connection_callbacks_.le_connection_promise_->get_future();
-  }
-
-  void check_connections_not_empty() {
-    ASSERT_TRUE(!mock_connection_callbacks_.connections_.empty()) << "There are no classic ACL connections";
-  }
-
-  std::shared_ptr<ClassicAclConnection> GetLastConnection() {
-    check_connections_not_empty();
-    return mock_connection_callbacks_.connections_.back();
-  }
-
-  std::shared_ptr<LeAclConnection> GetLastLeConnection() {
-    check_connections_not_empty();
-    return mock_le_connection_callbacks_.le_connections_.back();
-  }
-
   void SendAclData(uint16_t handle, AclConnection::QueueUpEnd* queue_end) {
     std::promise<void> promise;
     auto future = promise.get_future();
@@ -405,51 +470,18 @@
             queue_end,
             handle,
             common::Passed(std::move(promise))));
-    auto status = future.wait_for(kTimeout);
+    auto status = future.wait_for(2s);
     ASSERT_EQ(status, std::future_status::ready);
   }
-
-  class MockConnectionCallback : public ConnectionCallbacks {
-   public:
-    void OnConnectSuccess(std::unique_ptr<ClassicAclConnection> connection) override {
-      // Convert to std::shared_ptr during push_back()
-      connections_.push_back(std::move(connection));
-      if (connection_promise_ != nullptr) {
-        connection_promise_->set_value();
-        connection_promise_.reset();
-      }
-    }
-    MOCK_METHOD(void, OnConnectFail, (Address, ErrorCode reason), (override));
-
-    MOCK_METHOD(void, HACK_OnEscoConnectRequest, (Address, ClassOfDevice), (override));
-    MOCK_METHOD(void, HACK_OnScoConnectRequest, (Address, ClassOfDevice), (override));
-
-    std::list<std::shared_ptr<ClassicAclConnection>> connections_;
-    std::unique_ptr<std::promise<void>> connection_promise_;
-  } mock_connection_callbacks_;
-
-  class MockLeConnectionCallbacks : public LeConnectionCallbacks {
-   public:
-    void OnLeConnectSuccess(AddressWithType address_with_type, std::unique_ptr<LeAclConnection> connection) override {
-      le_connections_.push_back(std::move(connection));
-      if (le_connection_promise_ != nullptr) {
-        le_connection_promise_->set_value();
-        le_connection_promise_.reset();
-      }
-    }
-    MOCK_METHOD(void, OnLeConnectFail, (AddressWithType, ErrorCode reason), (override));
-
-    std::list<std::shared_ptr<LeAclConnection>> le_connections_;
-    std::unique_ptr<std::promise<void>> le_connection_promise_;
-  } mock_le_connection_callbacks_;
 };
 
-class AclManagerTest : public AclManagerNoCallbacksTest {
+class AclManagerWithCallbacksTest : public AclManagerNoCallbacksTest {
  protected:
   void SetUp() override {
     AclManagerNoCallbacksTest::SetUp();
     acl_manager_->RegisterCallbacks(&mock_connection_callbacks_, client_handler_);
     acl_manager_->RegisterLeCallbacks(&mock_le_connection_callbacks_, client_handler_);
+    ASSERT_TRUE(test_hci_layer_->hci_command_promise_ == nullptr) << "hci command is nullptr";
   }
 
   void TearDown() override {
@@ -468,16 +500,53 @@
       acl_manager_->UnregisterCallbacks(&mock_connection_callbacks_, std::move(promise));
       future.wait_for(2s);
     }
+
+    mock_connection_callbacks_.connections_.clear();
+    mock_le_connection_callbacks_.le_connections_.clear();
+
     AclManagerNoCallbacksTest::TearDown();
   }
+
+  std::future<std::shared_ptr<ClassicAclConnection>> GetConnectionFuture() {
+    // Run on main thread
+    mock_connection_callbacks_.connection_promise_ = std::promise<std::shared_ptr<ClassicAclConnection>>();
+    mock_connection_callbacks_.is_promise_set_ = true;
+    return mock_connection_callbacks_.connection_promise_.get_future();
+  }
+
+  std::future<void> GetLeConnectionFuture() {
+    mock_le_connection_callbacks_.le_connection_promise_ = std::make_unique<std::promise<void>>();
+    return mock_le_connection_callbacks_.le_connection_promise_->get_future();
+  }
+
+  std::shared_ptr<ClassicAclConnection> GetLastConnection() {
+    return mock_connection_callbacks_.connections_.back();
+  }
+
+  size_t NumberOfConnections() {
+    return mock_connection_callbacks_.connections_.size();
+  }
+
+  std::shared_ptr<LeAclConnection> GetLastLeConnection() {
+    return mock_le_connection_callbacks_.le_connections_.back();
+  }
+
+  size_t NumberOfLeConnections() {
+    return mock_le_connection_callbacks_.le_connections_.size();
+  }
+
+  MockConnectionCallback mock_connection_callbacks_;
+  MockLeConnectionCallbacks mock_le_connection_callbacks_;
 };
 
-class AclManagerWithConnectionTest : public AclManagerTest {
+class AclManagerWithConnectionTest : public AclManagerWithCallbacksTest {
  protected:
   void SetUp() override {
-    AclManagerTest::SetUp();
+    AclManagerWithCallbacksTest::SetUp();
 
     handle_ = 0x123;
+    Address::FromString("A1:A2:A3:A4:A5:A6", remote);
+
     acl_manager_->CreateConnection(remote);
 
     // Wait for the connection request
@@ -489,10 +558,10 @@
     EXPECT_CALL(mock_connection_management_callbacks_, OnRoleChange(hci::ErrorCode::SUCCESS, Role::CENTRAL));
 
     auto first_connection = GetConnectionFuture();
-    test_hci_layer_->IncomingEvent(
+    test_hci_layer_->SendIncomingEvent(
         ConnectionCompleteBuilder::Create(ErrorCode::SUCCESS, handle_, remote, LinkType::ACL, Enable::DISABLED));
 
-    auto first_connection_status = first_connection.wait_for(kTimeout);
+    auto first_connection_status = first_connection.wait_for(2s);
     ASSERT_EQ(first_connection_status, std::future_status::ready);
 
     connection_ = GetLastConnection();
@@ -505,15 +574,8 @@
     fake_registry_.StopAll();
   }
 
-  void sync_client_handler() {
-    std::promise<void> promise;
-    auto future = promise.get_future();
-    client_handler_->Post(common::BindOnce(&std::promise<void>::set_value, common::Unretained(&promise)));
-    auto future_status = future.wait_for(std::chrono::seconds(1));
-    ASSERT_EQ(future_status, std::future_status::ready);
-  }
-
   uint16_t handle_;
+  Address remote;
   std::shared_ptr<ClassicAclConnection> connection_;
 
   class MockConnectionManagementCallbacks : public ConnectionManagementCallbacks {
@@ -572,19 +634,20 @@
   } mock_connection_management_callbacks_;
 };
 
-TEST_F(AclManagerTest, startup_teardown) {}
+TEST_F(AclManagerWithCallbacksTest, startup_teardown) {}
 
-class AclManagerWithLeConnectionTest : public AclManagerTest {
+class AclManagerWithLeConnectionTest : public AclManagerWithCallbacksTest {
  protected:
   void SetUp() override {
-    AclManagerTest::SetUp();
+    AclManagerWithCallbacksTest::SetUp();
 
-    remote_with_type_ = AddressWithType(remote, AddressType::PUBLIC_DEVICE_ADDRESS);
-    test_hci_layer_->SetCommandFuture();
+    Address remote_public_address = Address::FromString(kRemotePublicDeviceStringA).value();
+    remote_with_type_ = AddressWithType(remote_public_address, AddressType::PUBLIC_DEVICE_ADDRESS);
+    ASSERT_NO_FATAL_FAILURE(test_hci_layer_->SetCommandFuture());
     acl_manager_->CreateLeConnection(remote_with_type_, true);
     test_hci_layer_->GetCommand(OpCode::LE_ADD_DEVICE_TO_FILTER_ACCEPT_LIST);
-    test_hci_layer_->IncomingEvent(LeAddDeviceToFilterAcceptListCompleteBuilder::Create(0x01, ErrorCode::SUCCESS));
-    test_hci_layer_->SetCommandFuture();
+    test_hci_layer_->SendIncomingEvent(LeAddDeviceToFilterAcceptListCompleteBuilder::Create(0x01, ErrorCode::SUCCESS));
+    ASSERT_NO_FATAL_FAILURE(test_hci_layer_->SetCommandFuture());
     auto packet = test_hci_layer_->GetCommand(OpCode::LE_CREATE_CONNECTION);
     auto le_connection_management_command_view =
         LeConnectionManagementCommandView::Create(AclCommandView::Create(packet));
@@ -594,11 +657,11 @@
       ASSERT_EQ(command_view.GetPeerAddress(), empty_address_with_type.GetAddress());
       ASSERT_EQ(command_view.GetPeerAddressType(), empty_address_with_type.GetAddressType());
     } else {
-      ASSERT_EQ(command_view.GetPeerAddress(), remote);
+      ASSERT_EQ(command_view.GetPeerAddress(), remote_public_address);
       ASSERT_EQ(command_view.GetPeerAddressType(), AddressType::PUBLIC_DEVICE_ADDRESS);
     }
 
-    test_hci_layer_->IncomingEvent(LeCreateConnectionStatusBuilder::Create(ErrorCode::SUCCESS, 0x01));
+    test_hci_layer_->SendIncomingEvent(LeCreateConnectionStatusBuilder::Create(ErrorCode::SUCCESS, 0x01));
 
     auto first_connection = GetLeConnectionFuture();
 
@@ -607,17 +670,18 @@
         handle_,
         Role::PERIPHERAL,
         AddressType::PUBLIC_DEVICE_ADDRESS,
-        remote,
+        remote_public_address,
         0x0100,
         0x0010,
         0x0C80,
         ClockAccuracy::PPM_30));
 
-    test_hci_layer_->SetCommandFuture();
+    ASSERT_NO_FATAL_FAILURE(test_hci_layer_->SetCommandFuture());
     test_hci_layer_->GetCommand(OpCode::LE_REMOVE_DEVICE_FROM_FILTER_ACCEPT_LIST);
-    test_hci_layer_->IncomingEvent(LeRemoveDeviceFromFilterAcceptListCompleteBuilder::Create(0x01, ErrorCode::SUCCESS));
+    test_hci_layer_->SendIncomingEvent(
+        LeRemoveDeviceFromFilterAcceptListCompleteBuilder::Create(0x01, ErrorCode::SUCCESS));
 
-    auto first_connection_status = first_connection.wait_for(kTimeout);
+    auto first_connection_status = first_connection.wait_for(2s);
     ASSERT_EQ(first_connection_status, std::future_status::ready);
 
     connection_ = GetLastLeConnection();
@@ -661,7 +725,7 @@
   } mock_le_connection_management_callbacks_;
 };
 
-class AclManagerWithResolvableAddressTest : public AclManagerNoCallbacksTest {
+class AclManagerWithResolvableAddressTest : public AclManagerWithCallbacksTest {
  protected:
   void SetUp() override {
     test_hci_layer_ = new TestHciLayer;  // Ownership is transferred to registry
@@ -670,11 +734,9 @@
     fake_registry_.InjectTestModule(&Controller::Factory, test_controller_);
     client_handler_ = fake_registry_.GetTestModuleHandler(&HciLayer::Factory);
     ASSERT_NE(client_handler_, nullptr);
-    test_hci_layer_->SetCommandFuture();
+    ASSERT_NO_FATAL_FAILURE(test_hci_layer_->SetCommandFuture());
     fake_registry_.Start<AclManager>(&thread_);
     acl_manager_ = static_cast<AclManager*>(fake_registry_.GetModuleUnderTest(&AclManager::Factory));
-    Address::FromString("A1:A2:A3:A4:A5:A6", remote);
-
     hci::Address address;
     Address::FromString("D0:05:04:03:02:01", address);
     hci::AddressWithType address_with_type(address, hci::AddressType::RANDOM_DEVICE_ADDRESS);
@@ -689,25 +751,17 @@
         maximum_rotation_time);
 
     test_hci_layer_->GetLastCommand(OpCode::LE_SET_RANDOM_ADDRESS);
-    test_hci_layer_->IncomingEvent(LeSetRandomAddressCompleteBuilder::Create(0x01, ErrorCode::SUCCESS));
+    test_hci_layer_->SendIncomingEvent(LeSetRandomAddressCompleteBuilder::Create(0x01, ErrorCode::SUCCESS));
   }
 };
 
-class AclManagerLifeCycleTest : public AclManagerNoCallbacksTest {
- protected:
-  void SetUp() override {
-    AclManagerNoCallbacksTest::SetUp();
-    acl_manager_->RegisterCallbacks(&mock_connection_callbacks_, client_handler_);
-    acl_manager_->RegisterLeCallbacks(&mock_le_connection_callbacks_, client_handler_);
-  }
-
-  AddressWithType remote_with_type_;
-  uint16_t handle_{0x123};
-};
-
-TEST_F(AclManagerLifeCycleTest, unregister_classic_before_connection_request) {
+TEST_F(AclManagerNoCallbacksTest, unregister_classic_before_connection_request) {
   ClassOfDevice class_of_device;
 
+  MockConnectionCallback mock_connection_callbacks_;
+
+  acl_manager_->RegisterCallbacks(&mock_connection_callbacks_, client_handler_);
+
   // Unregister callbacks before receiving connection request
   auto promise = std::promise<void>();
   auto future = promise.get_future();
@@ -715,103 +769,126 @@
   future.get();
 
   // Inject peer sending connection request
-  auto connection_future = GetConnectionFuture();
-  test_hci_layer_->IncomingEvent(
-      ConnectionRequestBuilder::Create(remote, class_of_device, ConnectionRequestLinkType::ACL));
-  auto connection_future_status = connection_future.wait_for(kTimeout);
-  ASSERT_NE(connection_future_status, std::future_status::ready);
+  test_hci_layer_->SendIncomingEvent(ConnectionRequestBuilder::Create(
+      local_address_with_type_.GetAddress(), class_of_device, ConnectionRequestLinkType::ACL));
+  sync_client_handler();
 
-  test_hci_layer_->GetLastCommand(OpCode::REJECT_CONNECTION_REQUEST);
+  // There should be no connections
+  ASSERT_EQ(0UL, mock_connection_callbacks_.NumberOfConnections());
+
+  auto command = test_hci_layer_->GetLastOutgoingCommand();
+  ASSERT_TRUE(command.IsValid());
+  ASSERT_EQ(OpCode::REJECT_CONNECTION_REQUEST, command.GetOpCode());
 }
 
-TEST_F(AclManagerTest, two_remote_connection_requests_ABAB) {
-  struct {
-    Address address;
-    ClassOfDevice class_of_device;
-    const uint16_t handle;
-  } remote[2] = {
-      {
-          .address = {},
-          .class_of_device = {},
-          .handle = 123,
-      },
-      {.address = {}, .class_of_device = {}, .handle = 456},
-  };
-  Address::FromString("A1:A2:A3:A4:A5:A6", remote[0].address);
-  Address::FromString("B1:B2:B3:B4:B5:B6", remote[1].address);
-
-  test_hci_layer_->SetCommandFuture();
-  test_hci_layer_->IncomingEvent(
-      ConnectionRequestBuilder::Create(remote[0].address, remote[0].class_of_device, ConnectionRequestLinkType::ACL));
-  test_hci_layer_->GetLastCommand(OpCode::ACCEPT_CONNECTION_REQUEST);
-
-  test_hci_layer_->SetCommandFuture();
-  test_hci_layer_->IncomingEvent(
-      ConnectionRequestBuilder::Create(remote[1].address, remote[1].class_of_device, ConnectionRequestLinkType::ACL));
-  test_hci_layer_->GetLastCommand(OpCode::ACCEPT_CONNECTION_REQUEST);
+TEST_F(AclManagerWithCallbacksTest, two_remote_connection_requests_ABAB) {
+  Address::FromString(kRemotePublicDeviceStringA, remote_device[0].address);
+  Address::FromString(kRemotePublicDeviceStringB, remote_device[1].address);
 
   {
-    auto first_connection = GetConnectionFuture();
-    test_hci_layer_->IncomingEvent(ConnectionCompleteBuilder::Create(
-        ErrorCode::SUCCESS, remote[0].handle, remote[0].address, LinkType::ACL, Enable::DISABLED));
-    auto first_connection_status = first_connection.wait_for(kTimeout);
-    ASSERT_EQ(first_connection_status, std::future_status::ready);
+    // Device A sends connection request
+    auto future = test_hci_layer_->GetOutgoingCommandFuture();
+    test_hci_layer_->SendIncomingEvent(ConnectionRequestBuilder::Create(
+        remote_device[0].address, remote_device[0].class_of_device, ConnectionRequestLinkType::ACL));
+    sync_client_handler();
+    // Verify we accept this connection
+    ASSERT_EQ(std::future_status::ready, future.wait_for(2s));
+    auto command = test_hci_layer_->GetLastOutgoingCommand();
+    ASSERT_TRUE(command.IsValid());
+    ASSERT_EQ(OpCode::ACCEPT_CONNECTION_REQUEST, command.GetOpCode());
   }
-  ASSERT_EQ(GetLastConnection()->GetAddress(), remote[0].address);
 
   {
-    auto first_connection = GetConnectionFuture();
-    test_hci_layer_->IncomingEvent(ConnectionCompleteBuilder::Create(
-        ErrorCode::SUCCESS, remote[1].handle, remote[1].address, LinkType::ACL, Enable::DISABLED));
-    auto first_connection_status = first_connection.wait_for(2s);
-    ASSERT_EQ(first_connection_status, std::future_status::ready);
+    // Device B sends connection request
+    auto future = test_hci_layer_->GetOutgoingCommandFuture();
+    test_hci_layer_->SendIncomingEvent(ConnectionRequestBuilder::Create(
+        remote_device[1].address, remote_device[1].class_of_device, ConnectionRequestLinkType::ACL));
+    sync_client_handler();
+    // Verify we accept this connection
+    ASSERT_EQ(std::future_status::ready, future.wait_for(2s));
+    auto command = test_hci_layer_->GetLastOutgoingCommand();
+    ASSERT_TRUE(command.IsValid());
+    ASSERT_EQ(OpCode::ACCEPT_CONNECTION_REQUEST, command.GetOpCode());
   }
-  ASSERT_EQ(GetLastConnection()->GetAddress(), remote[1].address);
+
+  ASSERT_EQ(0UL, NumberOfConnections());
+
+  {
+    // Device A completes first connection
+    auto future = GetConnectionFuture();
+    test_hci_layer_->SendIncomingEvent(ConnectionCompleteBuilder::Create(
+        ErrorCode::SUCCESS, remote_device[0].handle, remote_device[0].address, LinkType::ACL, Enable::DISABLED));
+    ASSERT_EQ(std::future_status::ready, future.wait_for(2s)) << "Timeout waiting for first connection complete";
+    ASSERT_EQ(1UL, NumberOfConnections());
+    auto connection = future.get();
+    ASSERT_EQ(connection->GetAddress(), remote_device[0].address) << "First connection remote address mismatch";
+  }
+
+  {
+    // Device B completes second connection
+    auto future = GetConnectionFuture();
+    test_hci_layer_->SendIncomingEvent(ConnectionCompleteBuilder::Create(
+        ErrorCode::SUCCESS, remote_device[1].handle, remote_device[1].address, LinkType::ACL, Enable::DISABLED));
+    ASSERT_EQ(std::future_status::ready, future.wait_for(2s)) << "Timeout waiting for second connection complete";
+    ASSERT_EQ(2UL, NumberOfConnections());
+    auto connection = future.get();
+    ASSERT_EQ(connection->GetAddress(), remote_device[1].address) << "Second connection remote address mismatch";
+  }
 }
 
-TEST_F(AclManagerTest, two_remote_connection_requests_ABBA) {
-  struct {
-    Address address;
-    ClassOfDevice class_of_device;
-    const uint16_t handle;
-  } remote[2] = {
-      {
-          .address = {},
-          .class_of_device = {},
-          .handle = 123,
-      },
-      {.address = {}, .class_of_device = {}, .handle = 456},
-  };
-  Address::FromString("A1:A2:A3:A4:A5:A6", remote[0].address);
-  Address::FromString("B1:B2:B3:B4:B5:B6", remote[1].address);
-
-  test_hci_layer_->SetCommandFuture();
-  test_hci_layer_->IncomingEvent(
-      ConnectionRequestBuilder::Create(remote[0].address, remote[0].class_of_device, ConnectionRequestLinkType::ACL));
-  test_hci_layer_->GetLastCommand(OpCode::ACCEPT_CONNECTION_REQUEST);
-
-  test_hci_layer_->SetCommandFuture();
-  test_hci_layer_->IncomingEvent(
-      ConnectionRequestBuilder::Create(remote[1].address, remote[1].class_of_device, ConnectionRequestLinkType::ACL));
-  test_hci_layer_->GetLastCommand(OpCode::ACCEPT_CONNECTION_REQUEST);
+TEST_F(AclManagerWithCallbacksTest, two_remote_connection_requests_ABBA) {
+  Address::FromString(kRemotePublicDeviceStringA, remote_device[0].address);
+  Address::FromString(kRemotePublicDeviceStringB, remote_device[1].address);
 
   {
-    auto first_connection = GetConnectionFuture();
-    test_hci_layer_->IncomingEvent(ConnectionCompleteBuilder::Create(
-        ErrorCode::SUCCESS, remote[1].handle, remote[1].address, LinkType::ACL, Enable::DISABLED));
-    auto first_connection_status = first_connection.wait_for(2s);
-    ASSERT_EQ(first_connection_status, std::future_status::ready);
+    // Device A sends connection request
+    auto future = test_hci_layer_->GetOutgoingCommandFuture();
+    test_hci_layer_->SendIncomingEvent(ConnectionRequestBuilder::Create(
+        remote_device[0].address, remote_device[0].class_of_device, ConnectionRequestLinkType::ACL));
+    sync_client_handler();
+    // Verify we accept this connection
+    ASSERT_EQ(std::future_status::ready, future.wait_for(2s));
+    auto command = test_hci_layer_->GetLastOutgoingCommand();
+    ASSERT_TRUE(command.IsValid());
+    ASSERT_EQ(OpCode::ACCEPT_CONNECTION_REQUEST, command.GetOpCode());
   }
-  ASSERT_EQ(GetLastConnection()->GetAddress(), remote[1].address);
 
   {
-    auto first_connection = GetConnectionFuture();
-    test_hci_layer_->IncomingEvent(ConnectionCompleteBuilder::Create(
-        ErrorCode::SUCCESS, remote[0].handle, remote[0].address, LinkType::ACL, Enable::DISABLED));
-    auto first_connection_status = first_connection.wait_for(kTimeout);
-    ASSERT_EQ(first_connection_status, std::future_status::ready);
+    // Device B sends connection request
+    auto future = test_hci_layer_->GetOutgoingCommandFuture();
+    test_hci_layer_->SendIncomingEvent(ConnectionRequestBuilder::Create(
+        remote_device[1].address, remote_device[1].class_of_device, ConnectionRequestLinkType::ACL));
+    sync_client_handler();
+    // Verify we accept this connection
+    ASSERT_EQ(std::future_status::ready, future.wait_for(2s));
+    auto command = test_hci_layer_->GetLastOutgoingCommand();
+    ASSERT_TRUE(command.IsValid());
+    ASSERT_EQ(OpCode::ACCEPT_CONNECTION_REQUEST, command.GetOpCode());
   }
-  ASSERT_EQ(GetLastConnection()->GetAddress(), remote[0].address);
+
+  ASSERT_EQ(0UL, NumberOfConnections());
+
+  {
+    // Device B completes first connection
+    auto future = GetConnectionFuture();
+    test_hci_layer_->SendIncomingEvent(ConnectionCompleteBuilder::Create(
+        ErrorCode::SUCCESS, remote_device[1].handle, remote_device[1].address, LinkType::ACL, Enable::DISABLED));
+    ASSERT_EQ(std::future_status::ready, future.wait_for(2s)) << "Timeout waiting for first connection complete";
+    ASSERT_EQ(1UL, NumberOfConnections());
+    auto connection = future.get();
+    ASSERT_EQ(connection->GetAddress(), remote_device[1].address) << "First connection remote address mismatch";
+  }
+
+  {
+    // Device A completes second connection
+    auto future = GetConnectionFuture();
+    test_hci_layer_->SendIncomingEvent(ConnectionCompleteBuilder::Create(
+        ErrorCode::SUCCESS, remote_device[0].handle, remote_device[0].address, LinkType::ACL, Enable::DISABLED));
+    ASSERT_EQ(std::future_status::ready, future.wait_for(2s)) << "Timeout waiting for second connection complete";
+    ASSERT_EQ(2UL, NumberOfConnections());
+    auto connection = future.get();
+    ASSERT_EQ(connection->GetAddress(), remote_device[0].address) << "Second connection remote address mismatch";
+  }
 }
 
 }  // namespace
diff --git a/system/gd/hci/address.cc b/system/gd/hci/address.cc
index 3dc013f..7409418 100644
--- a/system/gd/hci/address.cc
+++ b/system/gd/hci/address.cc
@@ -42,10 +42,15 @@
   std::copy(l.begin(), std::min(l.begin() + kLength, l.end()), data());
 }
 
-std::string Address::ToString() const {
+std::string Address::_ToMaskedColonSepHexString(int bytes_to_mask) const {
   std::stringstream ss;
+  int count = 0;
   for (auto it = address.rbegin(); it != address.rend(); it++) {
-    ss << std::nouppercase << std::hex << std::setw(2) << std::setfill('0') << +*it;
+    if (count++ < bytes_to_mask) {
+      ss << "xx";
+    } else {
+      ss << std::nouppercase << std::hex << std::setw(2) << std::setfill('0') << +*it;
+    }
     if (std::next(it) != address.rend()) {
       ss << ':';
     }
@@ -53,6 +58,22 @@
   return ss.str();
 }
 
+std::string Address::ToString() const {
+  return _ToMaskedColonSepHexString(0);
+}
+
+std::string Address::ToColonSepHexString() const {
+  return _ToMaskedColonSepHexString(0);
+}
+
+std::string Address::ToStringForLogging() const {
+  return _ToMaskedColonSepHexString(0);
+}
+
+std::string Address::ToRedactedStringForLogging() const {
+  return _ToMaskedColonSepHexString(4);
+}
+
 std::string Address::ToLegacyConfigString() const {
   return ToString();
 }
diff --git a/system/gd/hci/address.h b/system/gd/hci/address.h
index c5e43d4..2c1dfff 100644
--- a/system/gd/hci/address.h
+++ b/system/gd/hci/address.h
@@ -25,13 +25,16 @@
 #include <ostream>
 #include <string>
 
+#include "common/interfaces/ILoggable.h"
 #include "packet/custom_field_fixed_size_interface.h"
 #include "storage/serializable.h"
 
 namespace bluetooth {
 namespace hci {
 
-class Address final : public packet::CustomFieldFixedSizeInterface<Address>, public storage::Serializable<Address> {
+class Address final : public packet::CustomFieldFixedSizeInterface<Address>,
+                      public storage::Serializable<Address>,
+                      public bluetooth::common::IRedactableLoggable {
  public:
   static constexpr size_t kLength = 6;
 
@@ -51,6 +54,10 @@
 
   // storage::Serializable methods
   std::string ToString() const override;
+  std::string ToColonSepHexString() const;
+  std::string ToStringForLogging() const override;
+  std::string ToRedactedStringForLogging() const override;
+
   static std::optional<Address> FromString(const std::string& from);
   std::string ToLegacyConfigString() const override;
   static std::optional<Address> FromLegacyConfigString(const std::string& str);
@@ -91,8 +98,12 @@
 
   static const Address kEmpty;  // 00:00:00:00:00:00
   static const Address kAny;    // FF:FF:FF:FF:FF:FF
+ private:
+  std::string _ToMaskedColonSepHexString(int bytes_to_mask) const;
 };
 
+// TODO: to fine-tune this.
+// we need an interface between the logger and ILoggable
 inline std::ostream& operator<<(std::ostream& os, const Address& a) {
   os << a.ToString();
   return os;
diff --git a/system/gd/hci/address_unittest.cc b/system/gd/hci/address_unittest.cc
index ae2c2e7..a675682 100644
--- a/system/gd/hci/address_unittest.cc
+++ b/system/gd/hci/address_unittest.cc
@@ -16,12 +16,14 @@
  *
  ******************************************************************************/
 
-#include <unordered_map>
+#include "hci/address.h"
 
 #include <gmock/gmock.h>
 #include <gtest/gtest.h>
 
-#include "hci/address.h"
+#include <cstdint>
+#include <string>
+#include <unordered_map>
 
 using bluetooth::hci::Address;
 
@@ -233,3 +235,14 @@
   struct std::hash<Address> hasher;
   ASSERT_NE(hasher(Address::kEmpty), hasher(Address::kAny));
 }
+
+TEST(AddressTest, ToStringForLoggingTestOutputUnderDebuggablePropAndInitFlag) {
+  Address addr{{0xab, 0x55, 0x44, 0x33, 0x22, 0x11}};
+  const std::string redacted_loggable_str = "xx:xx:xx:xx:55:ab";
+  const std::string loggable_str = "11:22:33:44:55:ab";
+
+  std::string ret1 = addr.ToStringForLogging();
+  ASSERT_STREQ(ret1.c_str(), loggable_str.c_str());
+  std::string ret2 = addr.ToRedactedStringForLogging();
+  ASSERT_STREQ(ret2.c_str(), redacted_loggable_str.c_str());
+}
diff --git a/system/gd/hci/address_with_type.h b/system/gd/hci/address_with_type.h
index ec4d40c..75a0b3c 100644
--- a/system/gd/hci/address_with_type.h
+++ b/system/gd/hci/address_with_type.h
@@ -22,6 +22,7 @@
 #include <string>
 #include <utility>
 
+#include "common/interfaces/ILoggable.h"
 #include "crypto_toolbox/crypto_toolbox.h"
 #include "hci/address.h"
 #include "hci/hci_packets.h"
@@ -29,7 +30,7 @@
 namespace bluetooth {
 namespace hci {
 
-class AddressWithType final {
+class AddressWithType final : public bluetooth::common::IRedactableLoggable {
  public:
   AddressWithType(Address address, AddressType address_type)
       : address_(std::move(address)), address_type_(address_type) {}
@@ -119,6 +120,14 @@
     return ss.str();
   }
 
+  std::string ToStringForLogging() const override {
+    return address_.ToStringForLogging() + "[" + AddressTypeText(address_type_) + "]";
+  }
+
+  std::string ToRedactedStringForLogging() const override {
+    return address_.ToStringForLogging() + "[" + AddressTypeText(address_type_) + "]";
+  }
+
  private:
   Address address_;
   AddressType address_type_;
diff --git a/system/gd/hci/controller.cc b/system/gd/hci/controller.cc
index 3c8b69d..9dac2b6 100644
--- a/system/gd/hci/controller.cc
+++ b/system/gd/hci/controller.cc
@@ -870,27 +870,27 @@
 
   CompletedAclPacketsCallback acl_credits_callback_{};
   CompletedAclPacketsCallback acl_monitor_credits_callback_{};
-  LocalVersionInformation local_version_information_;
-  std::array<uint8_t, 64> local_supported_commands_;
-  std::vector<uint64_t> extended_lmp_features_array_;
-  uint16_t acl_buffer_length_ = 0;
-  uint16_t acl_buffers_ = 0;
-  uint8_t sco_buffer_length_ = 0;
-  uint16_t sco_buffers_ = 0;
-  Address mac_address_;
-  std::string local_name_;
-  LeBufferSize le_buffer_size_;
-  LeBufferSize iso_buffer_size_;
-  uint64_t le_local_supported_features_;
-  uint64_t le_supported_states_;
-  uint8_t le_connect_list_size_;
-  uint8_t le_resolving_list_size_;
-  LeMaximumDataLength le_maximum_data_length_;
-  uint16_t le_maximum_advertising_data_length_;
-  uint16_t le_suggested_default_data_length_;
-  uint8_t le_number_supported_advertising_sets_;
-  uint8_t le_periodic_advertiser_list_size_;
-  VendorCapabilities vendor_capabilities_;
+  LocalVersionInformation local_version_information_{};
+  std::array<uint8_t, 64> local_supported_commands_{};
+  std::vector<uint64_t> extended_lmp_features_array_{};
+  uint16_t acl_buffer_length_{};
+  uint16_t acl_buffers_{};
+  uint8_t sco_buffer_length_{};
+  uint16_t sco_buffers_{};
+  Address mac_address_{};
+  std::string local_name_{};
+  LeBufferSize le_buffer_size_{};
+  LeBufferSize iso_buffer_size_{};
+  uint64_t le_local_supported_features_{};
+  uint64_t le_supported_states_{};
+  uint8_t le_connect_list_size_{};
+  uint8_t le_resolving_list_size_{};
+  LeMaximumDataLength le_maximum_data_length_{};
+  uint16_t le_maximum_advertising_data_length_{};
+  uint16_t le_suggested_default_data_length_{};
+  uint8_t le_number_supported_advertising_sets_{};
+  uint8_t le_periodic_advertiser_list_size_{};
+  VendorCapabilities vendor_capabilities_{};
 };  // namespace hci
 
 Controller::Controller() : impl_(std::make_unique<impl>(*this)) {}
diff --git a/system/gd/hci/controller_test.cc b/system/gd/hci/controller_test.cc
index dacdef7..6353482 100644
--- a/system/gd/hci/controller_test.cc
+++ b/system/gd/hci/controller_test.cc
@@ -54,7 +54,6 @@
 uint16_t feature_spec_version = 55;
 constexpr char title[] = "hci_controller_test";
 
-
 PacketView<kLittleEndian> GetPacketView(std::unique_ptr<packet::BasePacketBuilder> packet) {
   auto bytes = std::make_shared<std::vector<uint8_t>>();
   BitInserter i(*bytes);
@@ -65,6 +64,8 @@
 
 }  // namespace
 
+namespace {
+
 class TestHciLayer : public HciLayer {
  public:
   void EnqueueCommand(
@@ -281,9 +282,11 @@
   std::condition_variable not_empty_;
 };
 
+}  // namespace
 class ControllerTest : public ::testing::Test {
  protected:
   void SetUp() override {
+    feature_spec_version = feature_spec_version_;
     bluetooth::common::InitFlags::SetAllForTesting();
     test_hci_layer_ = new TestHciLayer;
     fake_registry_.InjectTestModule(&HciLayer::Factory, test_hci_layer_);
@@ -301,6 +304,31 @@
   os::Thread& thread_ = fake_registry_.GetTestThread();
   Controller* controller_ = nullptr;
   os::Handler* client_handler_ = nullptr;
+  uint16_t feature_spec_version_ = 98;
+};
+
+class Controller055Test : public ControllerTest {
+ protected:
+  void SetUp() override {
+    feature_spec_version_ = 55;
+    ControllerTest::SetUp();
+  }
+};
+
+class Controller095Test : public ControllerTest {
+ protected:
+  void SetUp() override {
+    feature_spec_version_ = 95;
+    ControllerTest::SetUp();
+  }
+};
+
+class Controller096Test : public ControllerTest {
+ protected:
+  void SetUp() override {
+    feature_spec_version_ = 96;
+    ControllerTest::SetUp();
+  }
 };
 
 TEST_F(ControllerTest, startup_teardown) {}
@@ -407,28 +435,25 @@
   ASSERT_FALSE(controller_->IsSupported(OpCode::LE_SET_PERIODIC_ADVERTISING_PARAM));
 }
 
-TEST_F(ControllerTest, feature_spec_version_055_test) {
+TEST_F(Controller055Test, feature_spec_version_055_test) {
   ASSERT_EQ(controller_->GetVendorCapabilities().version_supported_, 55);
   ASSERT_TRUE(controller_->IsSupported(OpCode::LE_MULTI_ADVT));
   ASSERT_FALSE(controller_->IsSupported(OpCode::CONTROLLER_DEBUG_INFO));
   ASSERT_FALSE(controller_->IsSupported(OpCode::CONTROLLER_A2DP_OPCODE));
-  feature_spec_version = 95;
 }
 
-TEST_F(ControllerTest, feature_spec_version_095_test) {
+TEST_F(Controller095Test, feature_spec_version_095_test) {
   ASSERT_EQ(controller_->GetVendorCapabilities().version_supported_, 95);
   ASSERT_TRUE(controller_->IsSupported(OpCode::LE_MULTI_ADVT));
   ASSERT_FALSE(controller_->IsSupported(OpCode::CONTROLLER_DEBUG_INFO));
   ASSERT_FALSE(controller_->IsSupported(OpCode::CONTROLLER_A2DP_OPCODE));
-  feature_spec_version = 96;
 }
 
-TEST_F(ControllerTest, feature_spec_version_096_test) {
+TEST_F(Controller096Test, feature_spec_version_096_test) {
   ASSERT_EQ(controller_->GetVendorCapabilities().version_supported_, 96);
   ASSERT_TRUE(controller_->IsSupported(OpCode::LE_MULTI_ADVT));
   ASSERT_FALSE(controller_->IsSupported(OpCode::CONTROLLER_DEBUG_INFO));
   ASSERT_FALSE(controller_->IsSupported(OpCode::CONTROLLER_A2DP_OPCODE));
-  feature_spec_version = 98;
 }
 
 TEST_F(ControllerTest, feature_spec_version_098_test) {
@@ -436,7 +461,6 @@
   ASSERT_TRUE(controller_->IsSupported(OpCode::LE_MULTI_ADVT));
   ASSERT_FALSE(controller_->IsSupported(OpCode::CONTROLLER_DEBUG_INFO));
   ASSERT_TRUE(controller_->IsSupported(OpCode::CONTROLLER_A2DP_OPCODE));
-  feature_spec_version = 55;
 }
 
 std::promise<void> credits1_set;
diff --git a/system/gd/hci/hci_layer_fake.cc b/system/gd/hci/hci_layer_fake.cc
index 65ef988..3000c56 100644
--- a/system/gd/hci/hci_layer_fake.cc
+++ b/system/gd/hci/hci_layer_fake.cc
@@ -25,6 +25,8 @@
 namespace bluetooth {
 namespace hci {
 
+using common::BidiQueue;
+using common::BidiQueueEnd;
 using packet::kLittleEndian;
 using packet::PacketView;
 using packet::RawBuilder;
@@ -37,6 +39,22 @@
   return packet::PacketView<packet::kLittleEndian>(bytes);
 }
 
+std::unique_ptr<BasePacketBuilder> NextPayload(uint16_t handle) {
+  static uint32_t packet_number = 1;
+  auto payload = std::make_unique<RawBuilder>();
+  payload->AddOctets2(6);  // L2CAP PDU size
+  payload->AddOctets2(2);  // L2CAP CID
+  payload->AddOctets2(handle);
+  payload->AddOctets4(packet_number++);
+  return std::move(payload);
+}
+
+static std::unique_ptr<AclBuilder> NextAclPacket(uint16_t handle) {
+  PacketBoundaryFlag packet_boundary_flag = PacketBoundaryFlag::FIRST_AUTOMATICALLY_FLUSHABLE;
+  BroadcastFlag broadcast_flag = BroadcastFlag::POINT_TO_POINT;
+  return AclBuilder::Create(handle, packet_boundary_flag, broadcast_flag, NextPayload(handle));
+}
+
 void TestHciLayer::EnqueueCommand(
     std::unique_ptr<CommandBuilder> command, common::ContextualOnceCallback<void(CommandStatusView)> on_status) {
   std::lock_guard<std::mutex> lock(mutex_);
@@ -150,10 +168,59 @@
   ASSERT_TRUE(empty_command_view_.IsValid());
 }
 
+void TestHciLayer::IncomingAclData(uint16_t handle) {
+  os::Handler* hci_handler = GetHandler();
+  auto* queue_end = acl_queue_.GetDownEnd();
+  std::promise<void> promise;
+  auto future = promise.get_future();
+  queue_end->RegisterEnqueue(
+      hci_handler,
+      common::Bind(
+          [](decltype(queue_end) queue_end, uint16_t handle, std::promise<void> promise) {
+            auto packet = GetPacketView(NextAclPacket(handle));
+            AclView acl2 = AclView::Create(packet);
+            queue_end->UnregisterEnqueue();
+            promise.set_value();
+            return std::make_unique<AclView>(acl2);
+          },
+          queue_end,
+          handle,
+          common::Passed(std::move(promise))));
+  auto status = future.wait_for(std::chrono::milliseconds(1000));
+  ASSERT_EQ(status, std::future_status::ready);
+}
+
+void TestHciLayer::AssertNoOutgoingAclData() {
+  auto queue_end = acl_queue_.GetDownEnd();
+  EXPECT_EQ(queue_end->TryDequeue(), nullptr);
+}
+
+PacketView<kLittleEndian> TestHciLayer::OutgoingAclData() {
+  auto queue_end = acl_queue_.GetDownEnd();
+  std::unique_ptr<AclBuilder> received;
+  do {
+    received = queue_end->TryDequeue();
+  } while (received == nullptr);
+
+  return GetPacketView(std::move(received));
+}
+
+BidiQueueEnd<AclBuilder, AclView>* TestHciLayer::GetAclQueueEnd() {
+  return acl_queue_.GetUpEnd();
+}
+
+void TestHciLayer::Disconnect(uint16_t handle, ErrorCode reason) {
+  GetHandler()->Post(
+      common::BindOnce(&TestHciLayer::do_disconnect, common::Unretained(this), handle, reason));
+}
+
+void TestHciLayer::do_disconnect(uint16_t handle, ErrorCode reason) {
+  HciLayer::Disconnect(handle, reason);
+}
+
 void TestHciLayer::ListDependencies(ModuleList* list) const {}
 void TestHciLayer::Start() {
   std::lock_guard<std::mutex> lock(mutex_);
-
   InitEmptyCommand();
 }
 void TestHciLayer::Stop() {}
diff --git a/system/gd/hci/hci_layer_fake.h b/system/gd/hci/hci_layer_fake.h
index a944286..a2c3ea1 100644
--- a/system/gd/hci/hci_layer_fake.h
+++ b/system/gd/hci/hci_layer_fake.h
@@ -25,10 +25,10 @@
 namespace bluetooth {
 namespace hci {
 
-using packet::kLittleEndian;
-using packet::PacketView;
+packet::PacketView<packet::kLittleEndian> GetPacketView(
+    std::unique_ptr<packet::BasePacketBuilder> packet);
 
-PacketView<kLittleEndian> GetPacketView(std::unique_ptr<packet::BasePacketBuilder> packet);
+std::unique_ptr<BasePacketBuilder> NextPayload(uint16_t handle);
 
 class TestHciLayer : public HciLayer {
  public:
@@ -59,6 +59,16 @@
 
   void CommandStatusCallback(EventView event);
 
+  void IncomingAclData(uint16_t handle);
+
+  void AssertNoOutgoingAclData();
+
+  packet::PacketView<packet::kLittleEndian> OutgoingAclData();
+
+  common::BidiQueueEnd<AclBuilder, AclView>* GetAclQueueEnd() override;
+
+  void Disconnect(uint16_t handle, ErrorCode reason) override;
+
  protected:
   void ListDependencies(ModuleList* list) const override;
   void Start() override;
@@ -66,6 +76,7 @@
 
  private:
   void InitEmptyCommand();
+  void do_disconnect(uint16_t handle, ErrorCode reason);
 
   // Handler-only state. Mutexes are not needed when accessing these fields.
   std::list<common::ContextualOnceCallback<void(CommandCompleteView)>> command_complete_callbacks;
@@ -73,9 +84,12 @@
   std::map<EventCode, common::ContextualCallback<void(EventView)>> registered_events_;
   std::map<SubeventCode, common::ContextualCallback<void(LeMetaEventView)>> registered_le_events_;
 
-  // Most operations must acquire this mutex before manipulating shared state. The ONLY exception is blocking on a
-  // promise, IF your thread is the only one mutating it. Note that SETTING a promise REQUIRES a lock, since another
-  // thread may replace the promise while you are doing so.
+  // thread-safe
+  common::BidiQueue<AclView, AclBuilder> acl_queue_{3 /* TODO: Set queue depth */};
+
+  // Most operations must acquire this mutex before manipulating shared state. The ONLY exception
+  // is blocking on a promise, IF your thread is the only one mutating it. Note that SETTING a
+  // promise REQUIRES a lock, since another thread may replace the promise while you are doing so.
   mutable std::mutex mutex_{};
 
   // Shared state between the test and stack threads
@@ -83,13 +97,14 @@
 
   // We start with Consumed=Set, Command=Unset.
   // When a command is enqueued, we set Command=set
-  // When a command is popped, we block until Command=Set, then (if the queue is now empty) we reset Command=Unset and
-  // set Consumed=Set. This way we emulate a blocking queue.
-  std::promise<void> command_promise_{};                              // Set when at least one command is in the queue
-  std::future<void> command_future_ = command_promise_.get_future();  // GetCommand() blocks until this is fulfilled
+  // When a command is popped, we block until Command=Set, then (if the queue is now empty) we
+  // reset Command=Unset and set Consumed=Set. This way we emulate a blocking queue.
+  std::promise<void> command_promise_{};  // Set when at least one command is in the queue
+  std::future<void> command_future_ =
+      command_promise_.get_future();  // GetCommand() blocks until this is fulfilled
 
-  CommandView empty_command_view_ =
-      CommandView::Create(PacketView<packet::kLittleEndian>(std::make_shared<std::vector<uint8_t>>()));
+  CommandView empty_command_view_ = CommandView::Create(
+      PacketView<packet::kLittleEndian>(std::make_shared<std::vector<uint8_t>>()));
 };
 
 }  // namespace hci
diff --git a/system/gd/hci/hci_layer_test.cc b/system/gd/hci/hci_layer_test.cc
index b0aaeaa..8735dcb 100644
--- a/system/gd/hci/hci_layer_test.cc
+++ b/system/gd/hci/hci_layer_test.cc
@@ -60,6 +60,7 @@
 
 namespace bluetooth {
 namespace hci {
+namespace {
 
 constexpr std::chrono::milliseconds kTimeout = HciLayer::kHciTimeoutMs / 2;
 constexpr std::chrono::milliseconds kAclTimeout = std::chrono::milliseconds(1000);
@@ -83,18 +84,18 @@
   void sendHciCommand(hal::HciPacket command) override {
     outgoing_commands_.push_back(std::move(command));
     if (sent_command_promise_ != nullptr) {
-      auto promise = std::move(sent_command_promise_);
-      sent_command_promise_.reset();
-      promise->set_value();
+      std::promise<void>* prom = sent_command_promise_.release();
+      prom->set_value();
+      delete prom;
     }
   }
 
   void sendAclData(hal::HciPacket data) override {
     outgoing_acl_.push_back(std::move(data));
     if (sent_acl_promise_ != nullptr) {
-      auto promise = std::move(sent_acl_promise_);
-      sent_acl_promise_.reset();
-      promise->set_value();
+      std::promise<void>* prom = sent_acl_promise_.release();
+      prom->set_value();
+      delete prom;
     }
   }
 
@@ -105,9 +106,9 @@
   void sendIsoData(hal::HciPacket data) override {
     outgoing_iso_.push_back(std::move(data));
     if (sent_iso_promise_ != nullptr) {
-      auto promise = std::move(sent_iso_promise_);
-      sent_iso_promise_.reset();
-      promise->set_value();
+      std::promise<void>* prom = sent_iso_promise_.release();
+      prom->set_value();
+      delete prom;
     }
   }
 
@@ -290,7 +291,7 @@
     hci_->GetIsoQueueEnd()->UnregisterDequeue();
   }
 
-  void ListDependencies(ModuleList* list) {
+  void ListDependencies(ModuleList* list) const {
     list->add<HciLayer>();
   }
 
@@ -390,14 +391,14 @@
     ASSERT_EQ(reset_sent_status, std::future_status::ready);
 
     // Verify that reset was received
-    ASSERT_EQ(1, hal->GetNumSentCommands());
+    ASSERT_EQ(1u, hal->GetNumSentCommands());
 
     auto sent_command = hal->GetSentCommand();
     auto reset_view = ResetView::Create(CommandView::Create(sent_command));
     ASSERT_TRUE(reset_view.IsValid());
 
     // Verify that only one was sent
-    ASSERT_EQ(0, hal->GetNumSentCommands());
+    ASSERT_EQ(0u, hal->GetNumSentCommands());
 
     // Send the response event
     uint8_t num_packets = 1;
@@ -457,7 +458,7 @@
   ASSERT_TRUE(LeConnectionCompleteView::Create(LeMetaEventView::Create(EventView::Create(event))).IsValid());
 }
 
-TEST_F(HciTest, hciTimeOut) {
+TEST_F(HciTest, DISABLED_hciTimeOut) {
   auto event_future = upper->GetReceivedEventFuture();
   auto reset_command_future = hal->GetSentCommandFuture();
   upper->SendHciCommandExpectingComplete(ResetBuilder::Create());
@@ -478,7 +479,7 @@
 }
 
 TEST_F(HciTest, noOpCredits) {
-  ASSERT_EQ(0, hal->GetNumSentCommands());
+  ASSERT_EQ(0u, hal->GetNumSentCommands());
 
   // Send 0 credits
   uint8_t num_packets = 0;
@@ -488,7 +489,7 @@
   upper->SendHciCommandExpectingComplete(ReadLocalVersionInformationBuilder::Create());
 
   // Verify that nothing was sent
-  ASSERT_EQ(0, hal->GetNumSentCommands());
+  ASSERT_EQ(0u, hal->GetNumSentCommands());
 
   num_packets = 1;
   hal->callbacks->hciEventReceived(GetPacketBytes(NoCommandCompleteBuilder::Create(num_packets)));
@@ -497,7 +498,7 @@
   ASSERT_EQ(command_sent_status, std::future_status::ready);
 
   // Verify that one was sent
-  ASSERT_EQ(1, hal->GetNumSentCommands());
+  ASSERT_EQ(1u, hal->GetNumSentCommands());
 
   auto event_future = upper->GetReceivedEventFuture();
 
@@ -522,7 +523,7 @@
 }
 
 TEST_F(HciTest, creditsTest) {
-  ASSERT_EQ(0, hal->GetNumSentCommands());
+  ASSERT_EQ(0u, hal->GetNumSentCommands());
 
   auto command_future = hal->GetSentCommandFuture();
 
@@ -535,14 +536,14 @@
   ASSERT_EQ(command_sent_status, std::future_status::ready);
 
   // Verify that the first one is sent
-  ASSERT_EQ(1, hal->GetNumSentCommands());
+  ASSERT_EQ(1u, hal->GetNumSentCommands());
 
   auto sent_command = hal->GetSentCommand();
   auto version_view = ReadLocalVersionInformationView::Create(CommandView::Create(sent_command));
   ASSERT_TRUE(version_view.IsValid());
 
   // Verify that only one was sent
-  ASSERT_EQ(0, hal->GetNumSentCommands());
+  ASSERT_EQ(0u, hal->GetNumSentCommands());
 
   // Get a new future
   auto event_future = upper->GetReceivedEventFuture();
@@ -570,14 +571,14 @@
   // Verify that the second one is sent
   command_sent_status = command_future.wait_for(kTimeout);
   ASSERT_EQ(command_sent_status, std::future_status::ready);
-  ASSERT_EQ(1, hal->GetNumSentCommands());
+  ASSERT_EQ(1u, hal->GetNumSentCommands());
 
   sent_command = hal->GetSentCommand();
   auto supported_commands_view = ReadLocalSupportedCommandsView::Create(CommandView::Create(sent_command));
   ASSERT_TRUE(supported_commands_view.IsValid());
 
   // Verify that only one was sent
-  ASSERT_EQ(0, hal->GetNumSentCommands());
+  ASSERT_EQ(0u, hal->GetNumSentCommands());
   event_future = upper->GetReceivedEventFuture();
   command_future = hal->GetSentCommandFuture();
 
@@ -598,14 +599,14 @@
   // Verify that the third one is sent
   command_sent_status = command_future.wait_for(kTimeout);
   ASSERT_EQ(command_sent_status, std::future_status::ready);
-  ASSERT_EQ(1, hal->GetNumSentCommands());
+  ASSERT_EQ(1u, hal->GetNumSentCommands());
 
   sent_command = hal->GetSentCommand();
   auto supported_features_view = ReadLocalSupportedFeaturesView::Create(CommandView::Create(sent_command));
   ASSERT_TRUE(supported_features_view.IsValid());
 
   // Verify that only one was sent
-  ASSERT_EQ(0, hal->GetNumSentCommands());
+  ASSERT_EQ(0u, hal->GetNumSentCommands());
   event_future = upper->GetReceivedEventFuture();
 
   // Send the response event
@@ -631,7 +632,7 @@
 
   // Check the command
   auto sent_command = hal->GetSentCommand();
-  ASSERT_LT(0, sent_command.size());
+  ASSERT_LT(0u, sent_command.size());
   LeRandView view = LeRandView::Create(LeSecurityCommandView::Create(CommandView::Create(sent_command)));
   ASSERT_TRUE(view.IsValid());
 
@@ -662,7 +663,7 @@
 
   // Check the command
   auto sent_command = hal->GetSentCommand();
-  ASSERT_LT(0, sent_command.size());
+  ASSERT_LT(0u, sent_command.size());
   auto view = WriteSimplePairingModeView::Create(SecurityCommandView::Create(CommandView::Create(sent_command)));
   ASSERT_TRUE(view.IsValid());
 
@@ -699,7 +700,7 @@
 
   // Check the command
   auto sent_command = hal->GetSentCommand();
-  ASSERT_LT(0, sent_command.size());
+  ASSERT_LT(0u, sent_command.size());
   CreateConnectionView view = CreateConnectionView::Create(
       ConnectionManagementCommandView::Create(AclCommandView::Create(CommandView::Create(sent_command))));
   ASSERT_TRUE(view.IsValid());
@@ -776,7 +777,7 @@
   auto sent_acl_status = sent_acl_future.wait_for(kAclTimeout);
   ASSERT_EQ(sent_acl_status, std::future_status::ready);
   auto sent_acl = hal->GetSentAcl();
-  ASSERT_LT(0, sent_acl.size());
+  ASSERT_LT(0u, sent_acl.size());
   AclView sent_acl_view = AclView::Create(sent_acl);
   ASSERT_TRUE(sent_acl_view.IsValid());
   ASSERT_EQ(bd_addr.length() + sizeof(handle), sent_acl_view.GetPayload().size());
@@ -904,5 +905,7 @@
   ASSERT_EQ(handle, itr.extract<uint16_t>());
   ASSERT_EQ(received_packets, itr.extract<uint16_t>());
 }
+
+}  // namespace
 }  // namespace hci
 }  // namespace bluetooth
diff --git a/system/gd/hci/hci_layer_unittest.cc b/system/gd/hci/hci_layer_unittest.cc
index 3d88f06..d4c2423 100644
--- a/system/gd/hci/hci_layer_unittest.cc
+++ b/system/gd/hci/hci_layer_unittest.cc
@@ -168,11 +168,13 @@
 
 TEST_F(HciLayerTest, setup_teardown) {}
 
-TEST_F(HciLayerTest, reset_command_sent_on_start) {
+// b/260915548
+TEST_F(HciLayerTest, DISABLED_reset_command_sent_on_start) {
   FailIfResetNotSent();
 }
 
-TEST_F(HciLayerTest, controller_debug_info_requested_on_hci_timeout) {
+// b/260915548
+TEST_F(HciLayerTest, DISABLED_controller_debug_info_requested_on_hci_timeout) {
   FailIfResetNotSent();
   FakeTimerAdvance(HciLayer::kHciTimeoutMs.count());
 
@@ -183,7 +185,8 @@
   ASSERT_TRUE(debug_info_view.IsValid());
 }
 
-TEST_F(HciLayerTest, abort_after_hci_restart_timeout) {
+// b/260915548
+TEST_F(HciLayerTest, DISABLED_abort_after_hci_restart_timeout) {
   FailIfResetNotSent();
   FakeTimerAdvance(HciLayer::kHciTimeoutMs.count());
 
@@ -202,7 +205,8 @@
       "");
 }
 
-TEST_F(HciLayerTest, abort_on_root_inflammation_event) {
+// b/260915548
+TEST_F(HciLayerTest, DISABLED_abort_on_root_inflammation_event) {
   FailIfResetNotSent();
 
   auto payload = CreatePayload({'0'});
diff --git a/system/gd/hci/le_address_manager_test.cc b/system/gd/hci/le_address_manager_test.cc
index 5008db5..7f698f0 100644
--- a/system/gd/hci/le_address_manager_test.cc
+++ b/system/gd/hci/le_address_manager_test.cc
@@ -26,22 +26,28 @@
 using ::bluetooth::os::Handler;
 using ::bluetooth::os::Thread;
 
-namespace bluetooth {
-namespace hci {
-
 namespace {
-using packet::kLittleEndian;
-using packet::PacketView;
-using packet::RawBuilder;
 
-PacketView<kLittleEndian> GetPacketView(std::unique_ptr<packet::BasePacketBuilder> packet) {
+using namespace bluetooth;
+
+packet::PacketView<packet::kLittleEndian> GetPacketView(std::unique_ptr<packet::BasePacketBuilder> packet) {
   auto bytes = std::make_shared<std::vector<uint8_t>>();
-  BitInserter i(*bytes);
+  packet::BitInserter i(*bytes);
   bytes->reserve(packet->size());
   packet->Serialize(i);
   return packet::PacketView<packet::kLittleEndian>(bytes);
 }
 
+}  // namespace
+
+namespace bluetooth {
+namespace hci {
+namespace {
+
+using packet::kLittleEndian;
+using packet::PacketView;
+using packet::RawBuilder;
+
 class TestHciLayer : public HciLayer {
  public:
   void EnqueueCommand(
@@ -51,13 +57,14 @@
     command_queue_.push(std::move(command));
     command_complete_callbacks.push_back(std::move(on_complete));
     if (command_promise_ != nullptr) {
-      command_promise_->set_value();
-      command_promise_.reset();
+      std::promise<void>* prom = command_promise_.release();
+      prom->set_value();
+      delete prom;
     }
   }
 
   void SetCommandFuture() {
-    ASSERT_LOG(command_promise_ == nullptr, "Promises, Promises, ... Only one at a time.");
+    ASSERT_EQ(command_promise_, nullptr) << "Promises, Promises, ... Only one at a time.";
     command_promise_ = std::make_unique<std::promise<void>>();
     command_future_ = std::make_unique<std::future<void>>(command_promise_->get_future());
   }
@@ -130,8 +137,9 @@
     paused = false;
     le_address_manager_->AckResume(this);
     if (resume_promise_ != nullptr) {
-      resume_promise_->set_value();
-      resume_promise_.reset();
+      std::promise<void>* prom = resume_promise_.release();
+      prom->set_value();
+      delete prom;
     }
   }
 
@@ -217,10 +225,11 @@
       LeAddressManager::AddressPolicy::USE_RESOLVABLE_ADDRESS,
       remote_address,
       irk,
+      false,
       minimum_rotation_time,
       maximum_rotation_time);
 
-  test_hci_layer_->SetCommandFuture();
+  ASSERT_NO_FATAL_FAILURE(test_hci_layer_->SetCommandFuture());
   le_address_manager_->Register(clients[0].get());
   sync_handler(handler_);
   test_hci_layer_->GetCommand(OpCode::LE_SET_RANDOM_ADDRESS);
@@ -239,10 +248,11 @@
       LeAddressManager::AddressPolicy::USE_NON_RESOLVABLE_ADDRESS,
       remote_address,
       irk,
+      false,
       minimum_rotation_time,
       maximum_rotation_time);
 
-  test_hci_layer_->SetCommandFuture();
+  ASSERT_NO_FATAL_FAILURE(test_hci_layer_->SetCommandFuture());
   le_address_manager_->Register(clients[0].get());
   sync_handler(handler_);
   test_hci_layer_->GetCommand(OpCode::LE_SET_RANDOM_ADDRESS);
@@ -263,6 +273,7 @@
       LeAddressManager::AddressPolicy::USE_RESOLVABLE_ADDRESS,
       remote_address,
       irk,
+      false,
       minimum_rotation_time,
       maximum_rotation_time);
   le_address_manager_->Register(clients[0].get());
@@ -300,10 +311,11 @@
         LeAddressManager::AddressPolicy::USE_RESOLVABLE_ADDRESS,
         remote_address,
         irk,
+        false,
         minimum_rotation_time,
         maximum_rotation_time);
 
-    test_hci_layer_->SetCommandFuture();
+    ASSERT_NO_FATAL_FAILURE(test_hci_layer_->SetCommandFuture());
     le_address_manager_->Register(clients[0].get());
     sync_handler(handler_);
     test_hci_layer_->GetCommand(OpCode::LE_SET_RANDOM_ADDRESS);
@@ -330,7 +342,7 @@
 TEST_F(LeAddressManagerWithSingleClientTest, add_device_to_connect_list) {
   Address address;
   Address::FromString("01:02:03:04:05:06", address);
-  test_hci_layer_->SetCommandFuture();
+  ASSERT_NO_FATAL_FAILURE(test_hci_layer_->SetCommandFuture());
   le_address_manager_->AddDeviceToFilterAcceptList(FilterAcceptListAddressType::RANDOM, address);
   auto packet = test_hci_layer_->GetCommand(OpCode::LE_ADD_DEVICE_TO_FILTER_ACCEPT_LIST);
   auto packet_view = LeAddDeviceToFilterAcceptListView::Create(
@@ -346,12 +358,12 @@
 TEST_F(LeAddressManagerWithSingleClientTest, remove_device_from_connect_list) {
   Address address;
   Address::FromString("01:02:03:04:05:06", address);
-  test_hci_layer_->SetCommandFuture();
+  ASSERT_NO_FATAL_FAILURE(test_hci_layer_->SetCommandFuture());
   le_address_manager_->AddDeviceToFilterAcceptList(FilterAcceptListAddressType::RANDOM, address);
   test_hci_layer_->GetCommand(OpCode::LE_ADD_DEVICE_TO_FILTER_ACCEPT_LIST);
   test_hci_layer_->IncomingEvent(LeAddDeviceToFilterAcceptListCompleteBuilder::Create(0x01, ErrorCode::SUCCESS));
 
-  test_hci_layer_->SetCommandFuture();
+  ASSERT_NO_FATAL_FAILURE(test_hci_layer_->SetCommandFuture());
   le_address_manager_->RemoveDeviceFromFilterAcceptList(FilterAcceptListAddressType::RANDOM, address);
   auto packet = test_hci_layer_->GetCommand(OpCode::LE_REMOVE_DEVICE_FROM_FILTER_ACCEPT_LIST);
   auto packet_view = LeRemoveDeviceFromFilterAcceptListView::Create(
@@ -366,84 +378,154 @@
 TEST_F(LeAddressManagerWithSingleClientTest, clear_connect_list) {
   Address address;
   Address::FromString("01:02:03:04:05:06", address);
-  test_hci_layer_->SetCommandFuture();
+  ASSERT_NO_FATAL_FAILURE(test_hci_layer_->SetCommandFuture());
   le_address_manager_->AddDeviceToFilterAcceptList(FilterAcceptListAddressType::RANDOM, address);
   test_hci_layer_->GetCommand(OpCode::LE_ADD_DEVICE_TO_FILTER_ACCEPT_LIST);
   test_hci_layer_->IncomingEvent(LeAddDeviceToFilterAcceptListCompleteBuilder::Create(0x01, ErrorCode::SUCCESS));
 
-  test_hci_layer_->SetCommandFuture();
+  ASSERT_NO_FATAL_FAILURE(test_hci_layer_->SetCommandFuture());
   le_address_manager_->ClearFilterAcceptList();
   test_hci_layer_->GetCommand(OpCode::LE_CLEAR_FILTER_ACCEPT_LIST);
   test_hci_layer_->IncomingEvent(LeClearFilterAcceptListCompleteBuilder::Create(0x01, ErrorCode::SUCCESS));
   clients[0].get()->WaitForResume();
 }
 
-TEST_F(LeAddressManagerWithSingleClientTest, add_device_to_resolving_list) {
+// b/260916288
+TEST_F(LeAddressManagerWithSingleClientTest, DISABLED_add_device_to_resolving_list) {
   Address address;
   Address::FromString("01:02:03:04:05:06", address);
   Octet16 peer_irk = {0xec, 0x02, 0x34, 0xa3, 0x57, 0xc8, 0xad, 0x05, 0x34, 0x10, 0x10, 0xa6, 0x0a, 0x39, 0x7d, 0x9b};
   Octet16 local_irk = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10};
-  test_hci_layer_->SetCommandFuture();
+
+  ASSERT_NO_FATAL_FAILURE(test_hci_layer_->SetCommandFuture());
   le_address_manager_->AddDeviceToResolvingList(
       PeerAddressType::RANDOM_DEVICE_OR_IDENTITY_ADDRESS, address, peer_irk, local_irk);
-  auto packet = test_hci_layer_->GetCommand(OpCode::LE_ADD_DEVICE_TO_RESOLVING_LIST);
-  auto packet_view = LeAddDeviceToResolvingListView::Create(LeSecurityCommandView::Create(packet));
-  ASSERT_TRUE(packet_view.IsValid());
-  ASSERT_EQ(PeerAddressType::RANDOM_DEVICE_OR_IDENTITY_ADDRESS, packet_view.GetPeerIdentityAddressType());
-  ASSERT_EQ(address, packet_view.GetPeerIdentityAddress());
-  ASSERT_EQ(peer_irk, packet_view.GetPeerIrk());
-  ASSERT_EQ(local_irk, packet_view.GetLocalIrk());
-
-  test_hci_layer_->IncomingEvent(LeAddDeviceToResolvingListCompleteBuilder::Create(0x01, ErrorCode::SUCCESS));
+  {
+    auto packet = test_hci_layer_->GetCommand(OpCode::LE_SET_ADDRESS_RESOLUTION_ENABLE);
+    auto packet_view = LeSetAddressResolutionEnableView::Create(LeSecurityCommandView::Create(packet));
+    ASSERT_TRUE(packet_view.IsValid());
+    ASSERT_EQ(Enable::DISABLED, packet_view.GetAddressResolutionEnable());
+    test_hci_layer_->IncomingEvent(LeSetAddressResolutionEnableCompleteBuilder::Create(0x01, ErrorCode::SUCCESS));
+  }
+  {
+    ASSERT_NO_FATAL_FAILURE(test_hci_layer_->SetCommandFuture());
+    auto packet = test_hci_layer_->GetCommand(OpCode::LE_ADD_DEVICE_TO_RESOLVING_LIST);
+    auto packet_view = LeAddDeviceToResolvingListView::Create(LeSecurityCommandView::Create(packet));
+    ASSERT_TRUE(packet_view.IsValid());
+    ASSERT_EQ(PeerAddressType::RANDOM_DEVICE_OR_IDENTITY_ADDRESS, packet_view.GetPeerIdentityAddressType());
+    ASSERT_EQ(address, packet_view.GetPeerIdentityAddress());
+    ASSERT_EQ(peer_irk, packet_view.GetPeerIrk());
+    ASSERT_EQ(local_irk, packet_view.GetLocalIrk());
+    test_hci_layer_->IncomingEvent(LeAddDeviceToResolvingListCompleteBuilder::Create(0x01, ErrorCode::SUCCESS));
+  }
+  {
+    ASSERT_NO_FATAL_FAILURE(test_hci_layer_->SetCommandFuture());
+    auto packet = test_hci_layer_->GetCommand(OpCode::LE_SET_ADDRESS_RESOLUTION_ENABLE);
+    auto packet_view = LeSetAddressResolutionEnableView::Create(LeSecurityCommandView::Create(packet));
+    ASSERT_TRUE(packet_view.IsValid());
+    ASSERT_EQ(Enable::ENABLED, packet_view.GetAddressResolutionEnable());
+    test_hci_layer_->IncomingEvent(LeSetAddressResolutionEnableCompleteBuilder::Create(0x01, ErrorCode::SUCCESS));
+  }
   clients[0].get()->WaitForResume();
 }
 
-TEST_F(LeAddressManagerWithSingleClientTest, remove_device_from_resolving_list) {
+// b/260916288
+TEST_F(LeAddressManagerWithSingleClientTest, DISABLED_remove_device_from_resolving_list) {
   Address address;
   Address::FromString("01:02:03:04:05:06", address);
   Octet16 peer_irk = {0xec, 0x02, 0x34, 0xa3, 0x57, 0xc8, 0xad, 0x05, 0x34, 0x10, 0x10, 0xa6, 0x0a, 0x39, 0x7d, 0x9b};
   Octet16 local_irk = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10};
-  test_hci_layer_->SetCommandFuture();
+  ASSERT_NO_FATAL_FAILURE(test_hci_layer_->SetCommandFuture());
   le_address_manager_->AddDeviceToResolvingList(
       PeerAddressType::RANDOM_DEVICE_OR_IDENTITY_ADDRESS, address, peer_irk, local_irk);
+  test_hci_layer_->GetCommand(OpCode::LE_SET_ADDRESS_RESOLUTION_ENABLE);
+  test_hci_layer_->IncomingEvent(LeSetAddressResolutionEnableCompleteBuilder::Create(0x01, ErrorCode::SUCCESS));
+  ASSERT_NO_FATAL_FAILURE(test_hci_layer_->SetCommandFuture());
   test_hci_layer_->GetCommand(OpCode::LE_ADD_DEVICE_TO_RESOLVING_LIST);
   test_hci_layer_->IncomingEvent(LeAddDeviceToResolvingListCompleteBuilder::Create(0x01, ErrorCode::SUCCESS));
+  ASSERT_NO_FATAL_FAILURE(test_hci_layer_->SetCommandFuture());
+  test_hci_layer_->GetCommand(OpCode::LE_SET_ADDRESS_RESOLUTION_ENABLE);
+  test_hci_layer_->IncomingEvent(LeSetAddressResolutionEnableCompleteBuilder::Create(0x01, ErrorCode::SUCCESS));
 
-  test_hci_layer_->SetCommandFuture();
+  ASSERT_NO_FATAL_FAILURE(test_hci_layer_->SetCommandFuture());
   le_address_manager_->RemoveDeviceFromResolvingList(PeerAddressType::RANDOM_DEVICE_OR_IDENTITY_ADDRESS, address);
-  auto packet = test_hci_layer_->GetCommand(OpCode::LE_REMOVE_DEVICE_FROM_RESOLVING_LIST);
-  auto packet_view = LeRemoveDeviceFromResolvingListView::Create(LeSecurityCommandView::Create(packet));
-  ASSERT_TRUE(packet_view.IsValid());
-  ASSERT_EQ(PeerAddressType::RANDOM_DEVICE_OR_IDENTITY_ADDRESS, packet_view.GetPeerIdentityAddressType());
-  ASSERT_EQ(address, packet_view.GetPeerIdentityAddress());
-  test_hci_layer_->IncomingEvent(LeRemoveDeviceFromResolvingListCompleteBuilder::Create(0x01, ErrorCode::SUCCESS));
+  {
+    auto packet = test_hci_layer_->GetCommand(OpCode::LE_SET_ADDRESS_RESOLUTION_ENABLE);
+    auto packet_view = LeSetAddressResolutionEnableView::Create(LeSecurityCommandView::Create(packet));
+    ASSERT_TRUE(packet_view.IsValid());
+    ASSERT_EQ(Enable::DISABLED, packet_view.GetAddressResolutionEnable());
+    test_hci_layer_->IncomingEvent(LeSetAddressResolutionEnableCompleteBuilder::Create(0x01, ErrorCode::SUCCESS));
+  }
+  {
+    ASSERT_NO_FATAL_FAILURE(test_hci_layer_->SetCommandFuture());
+    auto packet = test_hci_layer_->GetCommand(OpCode::LE_REMOVE_DEVICE_FROM_RESOLVING_LIST);
+    auto packet_view = LeRemoveDeviceFromResolvingListView::Create(LeSecurityCommandView::Create(packet));
+    ASSERT_TRUE(packet_view.IsValid());
+    ASSERT_EQ(PeerAddressType::RANDOM_DEVICE_OR_IDENTITY_ADDRESS, packet_view.GetPeerIdentityAddressType());
+    ASSERT_EQ(address, packet_view.GetPeerIdentityAddress());
+    test_hci_layer_->IncomingEvent(LeRemoveDeviceFromResolvingListCompleteBuilder::Create(0x01, ErrorCode::SUCCESS));
+  }
+  {
+    ASSERT_NO_FATAL_FAILURE(test_hci_layer_->SetCommandFuture());
+    auto packet = test_hci_layer_->GetCommand(OpCode::LE_SET_ADDRESS_RESOLUTION_ENABLE);
+    auto packet_view = LeSetAddressResolutionEnableView::Create(LeSecurityCommandView::Create(packet));
+    ASSERT_TRUE(packet_view.IsValid());
+    ASSERT_EQ(Enable::ENABLED, packet_view.GetAddressResolutionEnable());
+    test_hci_layer_->IncomingEvent(LeSetAddressResolutionEnableCompleteBuilder::Create(0x01, ErrorCode::SUCCESS));
+  }
   clients[0].get()->WaitForResume();
 }
 
-TEST_F(LeAddressManagerWithSingleClientTest, clear_resolving_list) {
+// b/260916288
+TEST_F(LeAddressManagerWithSingleClientTest, DISABLED_clear_resolving_list) {
   Address address;
   Address::FromString("01:02:03:04:05:06", address);
   Octet16 peer_irk = {0xec, 0x02, 0x34, 0xa3, 0x57, 0xc8, 0xad, 0x05, 0x34, 0x10, 0x10, 0xa6, 0x0a, 0x39, 0x7d, 0x9b};
   Octet16 local_irk = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10};
-  test_hci_layer_->SetCommandFuture();
+  ASSERT_NO_FATAL_FAILURE(test_hci_layer_->SetCommandFuture());
   le_address_manager_->AddDeviceToResolvingList(
       PeerAddressType::RANDOM_DEVICE_OR_IDENTITY_ADDRESS, address, peer_irk, local_irk);
+  test_hci_layer_->GetCommand(OpCode::LE_SET_ADDRESS_RESOLUTION_ENABLE);
+  test_hci_layer_->IncomingEvent(LeSetAddressResolutionEnableCompleteBuilder::Create(0x01, ErrorCode::SUCCESS));
+  ASSERT_NO_FATAL_FAILURE(test_hci_layer_->SetCommandFuture());
   test_hci_layer_->GetCommand(OpCode::LE_ADD_DEVICE_TO_RESOLVING_LIST);
   test_hci_layer_->IncomingEvent(LeAddDeviceToResolvingListCompleteBuilder::Create(0x01, ErrorCode::SUCCESS));
+  ASSERT_NO_FATAL_FAILURE(test_hci_layer_->SetCommandFuture());
+  test_hci_layer_->GetCommand(OpCode::LE_SET_ADDRESS_RESOLUTION_ENABLE);
+  test_hci_layer_->IncomingEvent(LeSetAddressResolutionEnableCompleteBuilder::Create(0x01, ErrorCode::SUCCESS));
 
-  test_hci_layer_->SetCommandFuture();
+  ASSERT_NO_FATAL_FAILURE(test_hci_layer_->SetCommandFuture());
   le_address_manager_->ClearResolvingList();
-  auto packet = test_hci_layer_->GetCommand(OpCode::LE_CLEAR_RESOLVING_LIST);
-  auto packet_view = LeClearResolvingListView::Create(LeSecurityCommandView::Create(packet));
-  ASSERT_TRUE(packet_view.IsValid());
-  test_hci_layer_->IncomingEvent(LeClearResolvingListCompleteBuilder::Create(0x01, ErrorCode::SUCCESS));
+  {
+    auto packet = test_hci_layer_->GetCommand(OpCode::LE_SET_ADDRESS_RESOLUTION_ENABLE);
+    auto packet_view = LeSetAddressResolutionEnableView::Create(LeSecurityCommandView::Create(packet));
+    ASSERT_TRUE(packet_view.IsValid());
+    ASSERT_EQ(Enable::DISABLED, packet_view.GetAddressResolutionEnable());
+    test_hci_layer_->IncomingEvent(LeSetAddressResolutionEnableCompleteBuilder::Create(0x01, ErrorCode::SUCCESS));
+  }
+  {
+    ASSERT_NO_FATAL_FAILURE(test_hci_layer_->SetCommandFuture());
+    auto packet = test_hci_layer_->GetCommand(OpCode::LE_CLEAR_RESOLVING_LIST);
+    auto packet_view = LeClearResolvingListView::Create(LeSecurityCommandView::Create(packet));
+    ASSERT_TRUE(packet_view.IsValid());
+    test_hci_layer_->IncomingEvent(LeClearResolvingListCompleteBuilder::Create(0x01, ErrorCode::SUCCESS));
+  }
+  {
+    ASSERT_NO_FATAL_FAILURE(test_hci_layer_->SetCommandFuture());
+    auto packet = test_hci_layer_->GetCommand(OpCode::LE_SET_ADDRESS_RESOLUTION_ENABLE);
+    auto packet_view = LeSetAddressResolutionEnableView::Create(LeSecurityCommandView::Create(packet));
+    ASSERT_TRUE(packet_view.IsValid());
+    ASSERT_EQ(Enable::ENABLED, packet_view.GetAddressResolutionEnable());
+    test_hci_layer_->IncomingEvent(LeSetAddressResolutionEnableCompleteBuilder::Create(0x01, ErrorCode::SUCCESS));
+  }
+
   clients[0].get()->WaitForResume();
 }
 
 TEST_F(LeAddressManagerWithSingleClientTest, register_during_command_complete) {
   Address address;
   Address::FromString("01:02:03:04:05:06", address);
-  test_hci_layer_->SetCommandFuture();
+  ASSERT_NO_FATAL_FAILURE(test_hci_layer_->SetCommandFuture());
   le_address_manager_->AddDeviceToFilterAcceptList(FilterAcceptListAddressType::RANDOM, address);
   auto packet = test_hci_layer_->GetCommand(OpCode::LE_ADD_DEVICE_TO_FILTER_ACCEPT_LIST);
   auto packet_view = LeAddDeviceToFilterAcceptListView::Create(
@@ -454,7 +536,7 @@
   test_hci_layer_->IncomingEvent(LeAddDeviceToFilterAcceptListCompleteBuilder::Create(0x01, ErrorCode::SUCCESS));
 
   AllocateClients(1);
-  test_hci_layer_->SetCommandFuture();
+  ASSERT_NO_FATAL_FAILURE(test_hci_layer_->SetCommandFuture());
   le_address_manager_->Register(clients[1].get());
   clients[0].get()->WaitForResume();
   clients[1].get()->WaitForResume();
diff --git a/system/gd/hci/le_periodic_sync_manager_test.cc b/system/gd/hci/le_periodic_sync_manager_test.cc
index 3bb9e42..da77d18 100644
--- a/system/gd/hci/le_periodic_sync_manager_test.cc
+++ b/system/gd/hci/le_periodic_sync_manager_test.cc
@@ -45,8 +45,9 @@
     command_queue_.push(std::move(command));
     command_complete_callbacks.push_back(std::move(on_complete));
     if (command_promise_ != nullptr) {
-      command_promise_->set_value();
-      command_promise_.reset();
+      std::promise<void>* prom = command_promise_.release();
+      prom->set_value();
+      delete prom;
     }
   }
 
@@ -56,13 +57,14 @@
     command_queue_.push(std::move(command));
     command_status_callbacks.push_back(std::move(on_status));
     if (command_promise_ != nullptr) {
-      command_promise_->set_value();
-      command_promise_.reset();
+      std::promise<void>* prom = command_promise_.release();
+      prom->set_value();
+      delete prom;
     }
   }
 
   void SetCommandFuture() {
-    ASSERT_LOG(command_promise_ == nullptr, "Promises, Promises, ... Only one at a time.");
+    ASSERT_EQ(command_promise_, nullptr) << "Promises, Promises, ... Only one at a time.";
     command_promise_ = std::make_unique<std::promise<void>>();
     command_future_ = std::make_unique<std::future<void>>(command_promise_->get_future());
   }
@@ -224,7 +226,7 @@
   };
   uint16_t skip = 0x04;
   uint16_t sync_timeout = 0x0A;
-  test_le_scanning_interface_->SetCommandFuture();
+  ASSERT_NO_FATAL_FAILURE(test_le_scanning_interface_->SetCommandFuture());
   periodic_sync_manager_->StartSync(request, skip, sync_timeout);
   auto packet = test_le_scanning_interface_->GetCommand(OpCode::LE_PERIODIC_ADVERTISING_CREATE_SYNC);
   auto packet_view = LePeriodicAdvertisingCreateSyncView::Create(LeScanningCommandView::Create(packet));
@@ -251,7 +253,7 @@
       .sync_handle = sync_handle,
       .sync_state = PeriodicSyncState::PERIODIC_SYNC_STATE_IDLE,
   };
-  test_le_scanning_interface_->SetCommandFuture();
+  ASSERT_NO_FATAL_FAILURE(test_le_scanning_interface_->SetCommandFuture());
   periodic_sync_manager_->StartSync(request, 0x04, 0x0A);
   auto packet = test_le_scanning_interface_->GetCommand(OpCode::LE_PERIODIC_ADVERTISING_CREATE_SYNC);
   auto temp_view = LePeriodicAdvertisingCreateSyncView::Create(LeScanningCommandView::Create(packet));
@@ -293,7 +295,7 @@
       .sync_handle = sync_handle,
       .sync_state = PeriodicSyncState::PERIODIC_SYNC_STATE_IDLE,
   };
-  test_le_scanning_interface_->SetCommandFuture();
+  ASSERT_NO_FATAL_FAILURE(test_le_scanning_interface_->SetCommandFuture());
   periodic_sync_manager_->StartSync(request, 0x04, 0x0A);
   auto packet = test_le_scanning_interface_->GetCommand(OpCode::LE_PERIODIC_ADVERTISING_CREATE_SYNC);
   auto temp_view = LePeriodicAdvertisingCreateSyncView::Create(LeScanningCommandView::Create(packet));
@@ -335,7 +337,7 @@
       .sync_handle = sync_handle,
       .sync_state = PeriodicSyncState::PERIODIC_SYNC_STATE_IDLE,
   };
-  test_le_scanning_interface_->SetCommandFuture();
+  ASSERT_NO_FATAL_FAILURE(test_le_scanning_interface_->SetCommandFuture());
   periodic_sync_manager_->StartSync(request, 0x04, 0x0A);
   auto packet = test_le_scanning_interface_->GetCommand(OpCode::LE_PERIODIC_ADVERTISING_CREATE_SYNC);
   auto temp_veiw = LePeriodicAdvertisingCreateSyncView::Create(LeScanningCommandView::Create(packet));
@@ -362,7 +364,7 @@
   periodic_sync_manager_->HandleLePeriodicAdvertisingSyncEstablished(event_view);
 
   // StopSync
-  test_le_scanning_interface_->SetCommandFuture();
+  ASSERT_NO_FATAL_FAILURE(test_le_scanning_interface_->SetCommandFuture());
   periodic_sync_manager_->StopSync(sync_handle);
   packet = test_le_scanning_interface_->GetCommand(OpCode::LE_PERIODIC_ADVERTISING_TERMINATE_SYNC);
   auto packet_view = LePeriodicAdvertisingTerminateSyncView::Create(LeScanningCommandView::Create(packet));
@@ -385,7 +387,7 @@
       .sync_handle = sync_handle,
       .sync_state = PeriodicSyncState::PERIODIC_SYNC_STATE_IDLE,
   };
-  test_le_scanning_interface_->SetCommandFuture();
+  ASSERT_NO_FATAL_FAILURE(test_le_scanning_interface_->SetCommandFuture());
   periodic_sync_manager_->StartSync(request, 0x04, 0x0A);
   auto packet = test_le_scanning_interface_->GetCommand(OpCode::LE_PERIODIC_ADVERTISING_CREATE_SYNC);
   auto temp_veiw = LePeriodicAdvertisingCreateSyncView::Create(LeScanningCommandView::Create(packet));
@@ -396,7 +398,7 @@
       LePeriodicAdvertisingCreateSyncStatusBuilder::Create(ErrorCode::SUCCESS, 0x00));
 
   // Cancel crate sync
-  test_le_scanning_interface_->SetCommandFuture();
+  ASSERT_NO_FATAL_FAILURE(test_le_scanning_interface_->SetCommandFuture());
   periodic_sync_manager_->CancelCreateSync(advertiser_sid, address_with_type.GetAddress());
   packet = test_le_scanning_interface_->GetCommand(OpCode::LE_PERIODIC_ADVERTISING_CREATE_SYNC_CANCEL);
   auto packet_view = LePeriodicAdvertisingCreateSyncCancelView::Create(LeScanningCommandView::Create(packet));
@@ -411,7 +413,7 @@
   uint16_t sync_handle = 0x11;
   uint16_t connection_handle = 0x12;
   int pa_source = 0x01;
-  test_le_scanning_interface_->SetCommandFuture();
+  ASSERT_NO_FATAL_FAILURE(test_le_scanning_interface_->SetCommandFuture());
   periodic_sync_manager_->TransferSync(address, service_data, sync_handle, pa_source, connection_handle);
   auto packet = test_le_scanning_interface_->GetCommand(OpCode::LE_PERIODIC_ADVERTISING_SYNC_TRANSFER);
   auto packet_view = LePeriodicAdvertisingSyncTransferView::Create(LeScanningCommandView::Create(packet));
@@ -436,7 +438,7 @@
   uint16_t advertising_handle = 0x11;
   uint16_t connection_handle = 0x12;
   int pa_source = 0x01;
-  test_le_scanning_interface_->SetCommandFuture();
+  ASSERT_NO_FATAL_FAILURE(test_le_scanning_interface_->SetCommandFuture());
   periodic_sync_manager_->SyncSetInfo(address, service_data, advertising_handle, pa_source, connection_handle);
   auto packet = test_le_scanning_interface_->GetCommand(OpCode::LE_PERIODIC_ADVERTISING_SET_INFO_TRANSFER);
   auto packet_view = LePeriodicAdvertisingSetInfoTransferView::Create(LeScanningCommandView::Create(packet));
@@ -461,7 +463,7 @@
   uint16_t skip = 0x11;
   uint16_t timout = 0x12;
   int reg_id = 0x01;
-  test_le_scanning_interface_->SetCommandFuture();
+  ASSERT_NO_FATAL_FAILURE(test_le_scanning_interface_->SetCommandFuture());
   periodic_sync_manager_->SyncTxParameters(address, mode, skip, timout, reg_id);
   auto packet =
       test_le_scanning_interface_->GetCommand(OpCode::LE_SET_DEFAULT_PERIODIC_ADVERTISING_SYNC_TRANSFER_PARAMETERS);
@@ -490,7 +492,7 @@
       .sync_handle = sync_handle,
       .sync_state = PeriodicSyncState::PERIODIC_SYNC_STATE_IDLE,
   };
-  test_le_scanning_interface_->SetCommandFuture();
+  ASSERT_NO_FATAL_FAILURE(test_le_scanning_interface_->SetCommandFuture());
   periodic_sync_manager_->StartSync(request, 0x04, 0x0A);
   auto packet = test_le_scanning_interface_->GetCommand(OpCode::LE_PERIODIC_ADVERTISING_CREATE_SYNC);
   auto temp_veiw = LePeriodicAdvertisingCreateSyncView::Create(LeScanningCommandView::Create(packet));
@@ -542,7 +544,7 @@
       .sync_handle = sync_handle,
       .sync_state = PeriodicSyncState::PERIODIC_SYNC_STATE_IDLE,
   };
-  test_le_scanning_interface_->SetCommandFuture();
+  ASSERT_NO_FATAL_FAILURE(test_le_scanning_interface_->SetCommandFuture());
   periodic_sync_manager_->StartSync(request, 0x04, 0x0A);
   auto packet = test_le_scanning_interface_->GetCommand(OpCode::LE_PERIODIC_ADVERTISING_CREATE_SYNC);
   auto temp_veiw = LePeriodicAdvertisingCreateSyncView::Create(LeScanningCommandView::Create(packet));
diff --git a/system/gd/hci/le_scanning_manager_test.cc b/system/gd/hci/le_scanning_manager_test.cc
index 88eb3be..e59c725 100644
--- a/system/gd/hci/le_scanning_manager_test.cc
+++ b/system/gd/hci/le_scanning_manager_test.cc
@@ -29,6 +29,7 @@
 #include <queue>
 #include <vector>
 
+#include "hci/hci_layer_fake.h"
 #include "common/bind.h"
 #include "hci/acl_manager.h"
 #include "hci/address.h"
@@ -109,14 +110,6 @@
 namespace hci {
 namespace {
 
-PacketView<kLittleEndian> GetPacketView(std::unique_ptr<packet::BasePacketBuilder> packet) {
-  auto bytes = std::make_shared<std::vector<uint8_t>>();
-  BitInserter i(*bytes);
-  bytes->reserve(packet->size());
-  packet->Serialize(i);
-  return packet::PacketView<packet::kLittleEndian>(bytes);
-}
-
 class TestController : public Controller {
  public:
   bool IsSupported(OpCode op_code) const override {
@@ -127,6 +120,14 @@
     supported_opcodes_.insert(op_code);
   }
 
+  bool SupportsBleExtendedAdvertising() const override {
+    return support_ble_extended_advertising_;
+  }
+
+  void SetBleExtendedAdvertisingSupport(bool support) {
+    support_ble_extended_advertising_ = support;
+  }
+
  protected:
   void Start() override {}
   void Stop() override {}
@@ -134,156 +135,7 @@
 
  private:
   std::set<OpCode> supported_opcodes_{};
-};
-
-class TestHciLayer : public HciLayer {
- public:
-  void EnqueueCommand(
-      std::unique_ptr<CommandBuilder> command,
-      common::ContextualOnceCallback<void(CommandStatusView)> on_status) override {
-    std::lock_guard<std::mutex> lock(mutex_);
-    command_queue_.push(std::move(command));
-    command_status_callbacks.push_back(std::move(on_status));
-    command_count_--;
-    if (command_promise_ != nullptr && command_count_ == 0) {
-      command_promise_->set_value();
-      command_promise_.reset();
-    }
-  }
-
-  void EnqueueCommand(
-      std::unique_ptr<CommandBuilder> command,
-      common::ContextualOnceCallback<void(CommandCompleteView)> on_complete) override {
-    std::lock_guard<std::mutex> lock(mutex_);
-    command_queue_.push(std::move(command));
-    command_complete_callbacks.push_back(std::move(on_complete));
-    command_count_--;
-    if (command_promise_ != nullptr && command_count_ == 0) {
-      command_promise_->set_value();
-      command_promise_.reset();
-    }
-  }
-
-  // Set command future for 'num_command' commands are expected
-  void SetCommandFuture(uint16_t num_command = 1) {
-    ASSERT_TRUE(command_promise_ == nullptr) << "Promises, Promises, ... Only one at a time.";
-    command_count_ = num_command;
-    command_promise_ = std::make_unique<std::promise<void>>();
-    command_future_ = command_promise_->get_future();
-  }
-
-  CommandView GetCommand() {
-    // Wait for EnqueueCommand if command_queue_ is empty
-    if (command_promise_ != nullptr) {
-      if (command_queue_.empty()) {
-        LOG_ERROR("Waiting for command queue to fill ");
-        command_future_.wait_for(1s);
-      }
-      command_promise_.reset();
-    }
-
-    std::lock_guard<std::mutex> lock(mutex_);
-    if (command_queue_.empty()) {
-      LOG_ERROR("Command queue is empty");
-      return empty_command_view_;
-    }
-
-    auto last = std::move(command_queue_.front());
-    command_queue_.pop();
-    CommandView command_packet_view = CommandView::Create(GetPacketView(std::move(last)));
-    if (!command_packet_view.IsValid()) {
-      LOG_ERROR("Got invalid command");
-      return empty_command_view_;
-    }
-    return command_packet_view;
-  }
-
-  void RegisterEventHandler(EventCode event_code, common::ContextualCallback<void(EventView)> event_handler) override {
-    registered_events_[event_code] = event_handler;
-  }
-
-  void UnregisterEventHandler(EventCode event_code) override {
-    registered_events_.erase(event_code);
-  }
-
-  void RegisterLeEventHandler(
-      SubeventCode subevent_code, common::ContextualCallback<void(LeMetaEventView)> event_handler) override {
-    registered_le_events_[subevent_code] = event_handler;
-  }
-
-  void UnregisterLeEventHandler(SubeventCode subevent_code) override {
-    registered_le_events_.erase(subevent_code);
-  }
-
-  void IncomingEvent(std::unique_ptr<EventBuilder> event_builder) {
-    auto packet = GetPacketView(std::move(event_builder));
-    EventView event = EventView::Create(packet);
-    ASSERT_TRUE(event.IsValid());
-    EventCode event_code = event.GetEventCode();
-    ASSERT_NE(registered_events_.find(event_code), registered_events_.end()) << EventCodeText(event_code);
-    registered_events_[event_code].Invoke(event);
-  }
-
-  void IncomingLeMetaEvent(std::unique_ptr<LeMetaEventBuilder> event_builder) {
-    auto packet = GetPacketView(std::move(event_builder));
-    EventView event = EventView::Create(packet);
-    LeMetaEventView meta_event_view = LeMetaEventView::Create(event);
-    ASSERT_TRUE(meta_event_view.IsValid());
-    SubeventCode subevent_code = meta_event_view.GetSubeventCode();
-    ASSERT_TRUE(registered_le_events_.find(subevent_code) != registered_le_events_.end());
-    registered_le_events_[subevent_code].Invoke(meta_event_view);
-  }
-
-  void CommandCompleteCallback(EventView event) {
-    CommandCompleteView complete_view = CommandCompleteView::Create(event);
-    ASSERT_TRUE(complete_view.IsValid());
-    ASSERT_TRUE(!command_complete_callbacks.empty());
-    std::move(command_complete_callbacks.front()).Invoke(complete_view);
-    command_complete_callbacks.pop_front();
-  }
-
-  void CommandStatusCallback(EventView event) {
-    CommandStatusView status_view = CommandStatusView::Create(event);
-    ASSERT_TRUE(status_view.IsValid());
-    std::move(command_status_callbacks.front()).Invoke(status_view);
-    command_status_callbacks.pop_front();
-  }
-
-  void InitEmptyCommand() {
-    auto payload = std::make_unique<bluetooth::packet::RawBuilder>();
-    auto command_builder = CommandBuilder::Create(OpCode::NONE, std::move(payload));
-    empty_command_view_ = CommandView::Create(GetPacketView(std::move(command_builder)));
-    ASSERT_TRUE(empty_command_view_.IsValid());
-  }
-
-  void ListDependencies(ModuleList* list) const {}
-  void Start() override {
-    InitEmptyCommand();
-    RegisterEventHandler(
-        EventCode::COMMAND_COMPLETE, GetHandler()->BindOn(this, &TestHciLayer::CommandCompleteCallback));
-    RegisterEventHandler(EventCode::COMMAND_STATUS, GetHandler()->BindOn(this, &TestHciLayer::CommandStatusCallback));
-  }
-  void Stop() override {
-    UnregisterEventHandler(EventCode::COMMAND_STATUS);
-    UnregisterEventHandler(EventCode::COMMAND_COMPLETE);
-  }
-
-  size_t CommandQueueSize() const {
-    return command_queue_.size();
-  }
-
- private:
-  std::map<EventCode, common::ContextualCallback<void(EventView)>> registered_events_;
-  std::map<SubeventCode, common::ContextualCallback<void(LeMetaEventView)>> registered_le_events_;
-  std::list<common::ContextualOnceCallback<void(CommandCompleteView)>> command_complete_callbacks;
-  std::list<common::ContextualOnceCallback<void(CommandStatusView)>> command_status_callbacks;
-  std::queue<std::unique_ptr<CommandBuilder>> command_queue_;
-  std::unique_ptr<std::promise<void>> command_promise_;
-  std::future<void> command_future_;
-  mutable std::mutex mutex_;
-  uint16_t command_count_ = 0;
-  CommandView empty_command_view_ =
-      CommandView::Create(PacketView<kLittleEndian>(std::make_shared<std::vector<uint8_t>>()));
+  bool support_ble_extended_advertising_ = false;
 };
 
 class TestLeAddressManager : public LeAddressManager {
@@ -438,11 +290,7 @@
   }
 
   void sync_client_handler() {
-    std::promise<void> promise;
-    auto future = promise.get_future();
-    client_handler_->Post(common::BindOnce(&std::promise<void>::set_value, common::Unretained(&promise)));
-    auto future_status = future.wait_for(std::chrono::seconds(1));
-    ASSERT_EQ(future_status, std::future_status::ready);
+    ASSERT(thread_.GetReactor()->WaitForIdle(std::chrono::seconds(2)));
   }
 
   TestModuleRegistry fake_registry_;
@@ -466,10 +314,13 @@
     start_le_scanning_manager();
     ASSERT_TRUE(fake_registry_.IsStarted(&HciLayer::Factory));
 
-    test_hci_layer_->SetCommandFuture();
     ASSERT_EQ(OpCode::LE_ADV_FILTER, test_hci_layer_->GetCommand().GetOpCode());
-    ASSERT_EQ(0UL, test_hci_layer_->CommandQueueSize());
     test_hci_layer_->IncomingEvent(LeAdvFilterReadExtendedFeaturesCompleteBuilder::Create(1, ErrorCode::SUCCESS, 0x01));
+
+    // Get the command a second time as the configure_scan is called twice in le_scanning_manager.cc
+    // Fixed on aosp/2242078 but not present on older branches
+    EXPECT_EQ(OpCode::LE_EXTENDED_SCAN_PARAMS, test_hci_layer_->GetCommand().GetOpCode());
+    test_hci_layer_->IncomingEvent(LeExtendedScanParamsCompleteBuilder::Create(uint8_t{1}, ErrorCode::SUCCESS));
   }
 
   void TearDown() override {
@@ -483,7 +334,12 @@
     LeScanningManagerTest::SetUp();
     test_controller_->AddSupported(OpCode::LE_SET_EXTENDED_SCAN_PARAMETERS);
     test_controller_->AddSupported(OpCode::LE_SET_EXTENDED_SCAN_ENABLE);
+    test_controller_->SetBleExtendedAdvertisingSupport(true);
     start_le_scanning_manager();
+    // Get the command a second time as the configure_scan is called twice in le_scanning_manager.cc
+    // Fixed on aosp/2242078 but not present on older branches
+    EXPECT_EQ(OpCode::LE_SET_EXTENDED_SCAN_PARAMETERS, test_hci_layer_->GetCommand().GetOpCode());
+    test_hci_layer_->IncomingEvent(LeSetExtendedScanParametersCompleteBuilder::Create(uint8_t{1}, ErrorCode::SUCCESS));
   }
 };
 
@@ -492,12 +348,17 @@
 TEST_F(LeScanningManagerTest, start_scan_test) {
   start_le_scanning_manager();
 
-  test_hci_layer_->SetCommandFuture(2);
+  // Get the command a second time as the configure_scan is called twice in le_scanning_manager.cc
+  // Fixed on aosp/2242078 but not present on older branches
+  EXPECT_EQ(OpCode::LE_SET_SCAN_PARAMETERS, test_hci_layer_->GetCommand().GetOpCode());
+  test_hci_layer_->IncomingEvent(LeSetScanParametersCompleteBuilder::Create(uint8_t{1}, ErrorCode::SUCCESS));
+
   // Enable scan
   le_scanning_manager->Scan(true);
-  ASSERT_EQ(OpCode::LE_SET_SCAN_PARAMETERS, test_hci_layer_->GetCommand().GetOpCode());
+  EXPECT_EQ(OpCode::LE_SET_SCAN_PARAMETERS, test_hci_layer_->GetCommand().GetOpCode());
   test_hci_layer_->IncomingEvent(LeSetScanParametersCompleteBuilder::Create(uint8_t{1}, ErrorCode::SUCCESS));
-  ASSERT_EQ(OpCode::LE_SET_SCAN_ENABLE, test_hci_layer_->GetCommand().GetOpCode());
+
+  EXPECT_EQ(OpCode::LE_SET_SCAN_ENABLE, test_hci_layer_->GetCommand().GetOpCode());
   test_hci_layer_->IncomingEvent(LeSetScanEnableCompleteBuilder::Create(uint8_t{1}, ErrorCode::SUCCESS));
 
   LeAdvertisingResponse report = make_advertising_report();
@@ -515,7 +376,7 @@
 TEST_F(LeScanningManagerTest, scan_filter_add_ad_type_not_supported_test) {
   start_le_scanning_manager();
   ASSERT_TRUE(fake_registry_.IsStarted(&HciLayer::Factory));
-  test_hci_layer_->SetCommandFuture();
+
   std::vector<AdvertisingPacketContentFilterCommand> filters = {};
   filters.push_back(make_filter(hci::ApcfFilterType::AD_TYPE));
   le_scanning_manager->ScanFilterAdd(0x01, filters);
@@ -524,7 +385,6 @@
 TEST_F(LeScanningManagerAndroidHciTest, startup_teardown) {}
 
 TEST_F(LeScanningManagerAndroidHciTest, start_scan_test) {
-  test_hci_layer_->SetCommandFuture(2);
   // Enable scan
   le_scanning_manager->Scan(true);
   ASSERT_EQ(OpCode::LE_EXTENDED_SCAN_PARAMS, test_hci_layer_->GetCommand().GetOpCode());
@@ -545,14 +405,16 @@
 
 TEST_F(LeScanningManagerAndroidHciTest, scan_filter_enable_test) {
   le_scanning_manager->ScanFilterEnable(true);
+  sync_client_handler();
 
   EXPECT_CALL(mock_callbacks_, OnFilterEnable);
   test_hci_layer_->IncomingEvent(
       LeAdvFilterEnableCompleteBuilder::Create(uint8_t{1}, ErrorCode::SUCCESS, Enable::ENABLED));
+  sync_client_handler();
 }
 
 TEST_F(LeScanningManagerAndroidHciTest, scan_filter_parameter_test) {
-  test_hci_layer_->SetCommandFuture();
+
   AdvertisingFilterParameter advertising_filter_parameter{};
   advertising_filter_parameter.delivery_mode = DeliveryMode::IMMEDIATE;
   le_scanning_manager->ScanFilterParameterSetup(ApcfAction::ADD, 0x01, advertising_filter_parameter);
@@ -566,10 +428,11 @@
   EXPECT_CALL(mock_callbacks_, OnFilterParamSetup);
   test_hci_layer_->IncomingEvent(
       LeAdvFilterSetFilteringParametersCompleteBuilder::Create(uint8_t{1}, ErrorCode::SUCCESS, ApcfAction::ADD, 0x0a));
+  sync_client_handler();
 }
 
 TEST_F(LeScanningManagerAndroidHciTest, scan_filter_add_broadcaster_address_test) {
-  test_hci_layer_->SetCommandFuture();
+
   std::vector<AdvertisingPacketContentFilterCommand> filters = {};
   filters.push_back(make_filter(ApcfFilterType::BROADCASTER_ADDRESS));
   le_scanning_manager->ScanFilterAdd(0x01, filters);
@@ -586,7 +449,7 @@
 }
 
 TEST_F(LeScanningManagerAndroidHciTest, scan_filter_add_service_uuid_test) {
-  test_hci_layer_->SetCommandFuture();
+
   std::vector<AdvertisingPacketContentFilterCommand> filters = {};
   filters.push_back(make_filter(ApcfFilterType::SERVICE_UUID));
   le_scanning_manager->ScanFilterAdd(0x01, filters);
@@ -603,7 +466,7 @@
 }
 
 TEST_F(LeScanningManagerAndroidHciTest, scan_filter_add_local_name_test) {
-  test_hci_layer_->SetCommandFuture();
+
   std::vector<AdvertisingPacketContentFilterCommand> filters = {};
   filters.push_back(make_filter(ApcfFilterType::LOCAL_NAME));
   le_scanning_manager->ScanFilterAdd(0x01, filters);
@@ -620,7 +483,7 @@
 }
 
 TEST_F(LeScanningManagerAndroidHciTest, scan_filter_add_manufacturer_data_test) {
-  test_hci_layer_->SetCommandFuture();
+
   std::vector<AdvertisingPacketContentFilterCommand> filters = {};
   filters.push_back(make_filter(ApcfFilterType::MANUFACTURER_DATA));
   le_scanning_manager->ScanFilterAdd(0x01, filters);
@@ -637,7 +500,7 @@
 }
 
 TEST_F(LeScanningManagerAndroidHciTest, scan_filter_add_service_data_test) {
-  test_hci_layer_->SetCommandFuture();
+
   std::vector<AdvertisingPacketContentFilterCommand> filters = {};
   filters.push_back(make_filter(hci::ApcfFilterType::SERVICE_DATA));
   le_scanning_manager->ScanFilterAdd(0x01, filters);
@@ -680,20 +543,20 @@
       LeBatchScanSetStorageParametersCompleteBuilder::Create(uint8_t{1}, ErrorCode::SUCCESS));
 
   // Enable batch scan
-  test_hci_layer_->SetCommandFuture();
+
   le_scanning_manager->BatchScanEnable(BatchScanMode::FULL, 2400, 2400, BatchScanDiscardRule::OLDEST);
   ASSERT_EQ(OpCode::LE_BATCH_SCAN, test_hci_layer_->GetCommand().GetOpCode());
   test_hci_layer_->IncomingEvent(LeBatchScanEnableCompleteBuilder::Create(uint8_t{1}, ErrorCode::SUCCESS));
 
   // Read batch scan data
-  test_hci_layer_->SetCommandFuture();
+
   le_scanning_manager->BatchScanReadReport(0x01, BatchScanMode::FULL);
   ASSERT_EQ(OpCode::LE_BATCH_SCAN, test_hci_layer_->GetCommand().GetOpCode());
 
   // We will send read command while num_of_record != 0
   std::vector<uint8_t> raw_data = {0x5c, 0x1f, 0xa2, 0xc3, 0x63, 0x5d, 0x01, 0xf5, 0xb3, 0x5e, 0x00, 0x0c, 0x02,
                                    0x01, 0x02, 0x05, 0x09, 0x6d, 0x76, 0x38, 0x76, 0x02, 0x0a, 0xf5, 0x00};
-  test_hci_layer_->SetCommandFuture();
+
   test_hci_layer_->IncomingEvent(LeBatchScanReadResultParametersCompleteRawBuilder::Create(
       uint8_t{1}, ErrorCode::SUCCESS, BatchScanDataRead::FULL_MODE_DATA, 1, raw_data));
   ASSERT_EQ(OpCode::LE_BATCH_SCAN, test_hci_layer_->GetCommand().GetOpCode());
@@ -708,7 +571,6 @@
 
 TEST_F(LeScanningManagerExtendedTest, start_scan_test) {
   // Enable scan
-  test_hci_layer_->SetCommandFuture(2);
   le_scanning_manager->Scan(true);
   ASSERT_EQ(OpCode::LE_SET_EXTENDED_SCAN_PARAMETERS, test_hci_layer_->GetCommand().GetOpCode());
   test_hci_layer_->IncomingEvent(LeSetExtendedScanParametersCompleteBuilder::Create(uint8_t{1}, ErrorCode::SUCCESS));
@@ -742,7 +604,6 @@
   test_le_address_manager->ignore_unregister_for_testing = true;
 
   // Register LeAddressManager
-  test_hci_layer_->SetCommandFuture(2);
   le_scanning_manager->Scan(true);
   ASSERT_EQ(OpCode::LE_SET_EXTENDED_SCAN_PARAMETERS, test_hci_layer_->GetCommand().GetOpCode());
   test_hci_layer_->IncomingEvent(LeSetExtendedScanParametersCompleteBuilder::Create(uint8_t{1}, ErrorCode::SUCCESS));
@@ -751,7 +612,6 @@
   sync_client_handler();
 
   // Unregister LeAddressManager
-  test_hci_layer_->SetCommandFuture(1);
   le_scanning_manager->Scan(false);
   ASSERT_EQ(OpCode::LE_SET_EXTENDED_SCAN_ENABLE, test_hci_layer_->GetCommand().GetOpCode());
   test_hci_layer_->IncomingEvent(LeSetExtendedScanEnableCompleteBuilder::Create(uint8_t{1}, ErrorCode::SUCCESS));
@@ -768,7 +628,6 @@
 
 TEST_F(LeScanningManagerExtendedTest, drop_insignificant_bytes_test) {
   // Enable scan
-  test_hci_layer_->SetCommandFuture(2);
   le_scanning_manager->Scan(true);
   ASSERT_EQ(OpCode::LE_SET_EXTENDED_SCAN_PARAMETERS, test_hci_layer_->GetCommand().GetOpCode());
   test_hci_layer_->IncomingEvent(LeSetExtendedScanParametersCompleteBuilder::Create(uint8_t{1}, ErrorCode::SUCCESS));
diff --git a/system/gd/os/Android.bp b/system/gd/os/Android.bp
index 7431f15..292da06 100644
--- a/system/gd/os/Android.bp
+++ b/system/gd/os/Android.bp
@@ -17,6 +17,7 @@
 filegroup {
     name: "BluetoothOsSources_android",
     srcs: [
+        "system_properties_common.cc",
         "android/metrics.cc",
         "android/parameter_provider.cc",
         "android/system_properties.cc",
@@ -35,6 +36,7 @@
 filegroup {
     name: "BluetoothOsSources_host",
     srcs: [
+        "system_properties_common.cc",
         "host/metrics.cc",
         "host/parameter_provider.cc",
         "host/system_properties.cc",
diff --git a/system/gd/os/BUILD.gn b/system/gd/os/BUILD.gn
index 48337e1..7204ccd 100644
--- a/system/gd/os/BUILD.gn
+++ b/system/gd/os/BUILD.gn
@@ -18,6 +18,7 @@
     "linux/parameter_provider.cc",
     "linux/system_properties.cc",
     "linux/wakelock_native.cc",
+    "system_properties_common.cc",
     "syslog.cc",
   ]
 
diff --git a/system/gd/os/android/metrics.cc b/system/gd/os/android/metrics.cc
index db42fc5..400e925 100644
--- a/system/gd/os/android/metrics.cc
+++ b/system/gd/os/android/metrics.cc
@@ -236,7 +236,7 @@
 }
 
 void LogMetricSmpPairingEvent(
-    const Address& address, uint8_t smp_cmd, android::bluetooth::DirectionEnum direction, uint8_t smp_fail_reason) {
+    const Address& address, uint16_t smp_cmd, android::bluetooth::DirectionEnum direction, uint16_t smp_fail_reason) {
   int metric_id = 0;
   if (!address.IsEmpty()) {
     metric_id = MetricIdManager::GetInstance().AllocateId(address);
@@ -443,7 +443,12 @@
     uint8_t hci_version,
     uint32_t hci_revision) {
   int ret = stats_write(
-      BLUETOOTH_LOCAL_VERSIONS_REPORTED, lmp_manufacturer_name, lmp_version, lmp_subversion, hci_version, hci_revision);
+      BLUETOOTH_LOCAL_VERSIONS_REPORTED,
+      static_cast<int32_t>(lmp_manufacturer_name),
+      static_cast<int32_t>(lmp_version),
+      static_cast<int32_t>(lmp_subversion),
+      static_cast<int32_t>(hci_version),
+      static_cast<int32_t>(hci_revision));
   if (ret < 0) {
     LOG_WARN(
         "Failed for LogMetricBluetoothLocalVersions, "
diff --git a/system/gd/os/android/wakelock_native_test.cc b/system/gd/os/android/wakelock_native_test.cc
index 66be722..36f8af8 100644
--- a/system/gd/os/android/wakelock_native_test.cc
+++ b/system/gd/os/android/wakelock_native_test.cc
@@ -53,8 +53,9 @@
   static void FulfilPromise(std::unique_ptr<std::promise<void>>& promise) {
     std::lock_guard<std::recursive_mutex> lock_guard(mutex);
     if (promise != nullptr) {
-      promise->set_value();
-      promise = nullptr;
+      std::promise<void>* prom = promise.release();
+      prom->set_value();
+      delete prom;
     }
   }
 
diff --git a/system/gd/os/handler_unittest.cc b/system/gd/os/handler_unittest.cc
index b8c580f..04f6347 100644
--- a/system/gd/os/handler_unittest.cc
+++ b/system/gd/os/handler_unittest.cc
@@ -70,19 +70,27 @@
   auto closure_started_future = closure_started.get_future();
   std::promise<void> closure_can_continue;
   auto can_continue_future = closure_can_continue.get_future();
+  std::promise<void> closure_finished;
+  auto closure_finished_future = closure_finished.get_future();
   handler_->Post(common::BindOnce(
-      [](int* val, std::promise<void> closure_started, std::future<void> can_continue_future) {
+      [](int* val,
+         std::promise<void> closure_started,
+         std::future<void> can_continue_future,
+         std::promise<void> closure_finished) {
         closure_started.set_value();
         *val = *val + 1;
         can_continue_future.wait();
+        closure_finished.set_value();
       },
       common::Unretained(&val),
       std::move(closure_started),
-      std::move(can_continue_future)));
+      std::move(can_continue_future),
+      std::move(closure_finished)));
   handler_->Post(common::BindOnce([]() { ASSERT_TRUE(false); }));
   closure_started_future.wait();
   handler_->Clear();
   closure_can_continue.set_value();
+  closure_finished_future.wait();
   ASSERT_EQ(val, 1);
 }
 
diff --git a/system/gd/os/host/metrics.cc b/system/gd/os/host/metrics.cc
index 955d22e..2e85e73 100644
--- a/system/gd/os/host/metrics.cc
+++ b/system/gd/os/host/metrics.cc
@@ -96,7 +96,7 @@
     const char* attribute_value) {}
 
 void LogMetricSmpPairingEvent(
-    const Address& address, uint8_t smp_cmd, android::bluetooth::DirectionEnum direction, uint8_t smp_fail_reason) {}
+    const Address& address, uint16_t smp_cmd, android::bluetooth::DirectionEnum direction, uint16_t smp_fail_reason) {}
 
 void LogMetricA2dpPlaybackEvent(const Address& address, int playback_state, int audio_coding_mode) {}
 
diff --git a/system/gd/os/linux/metrics.cc b/system/gd/os/linux/metrics.cc
index 65fb181..3941f77 100644
--- a/system/gd/os/linux/metrics.cc
+++ b/system/gd/os/linux/metrics.cc
@@ -96,7 +96,7 @@
     const char* attribute_value) {}
 
 void LogMetricSmpPairingEvent(
-    const Address& address, uint8_t smp_cmd, android::bluetooth::DirectionEnum direction, uint8_t smp_fail_reason) {}
+    const Address& address, uint16_t smp_cmd, android::bluetooth::DirectionEnum direction, uint16_t smp_fail_reason) {}
 
 void LogMetricA2dpPlaybackEvent(const Address& address, int playback_state, int audio_coding_mode) {}
 
diff --git a/system/gd/os/linux_generic/queue_unittest.cc b/system/gd/os/linux_generic/queue_unittest.cc
index 3739735..706fcd2 100644
--- a/system/gd/os/linux_generic/queue_unittest.cc
+++ b/system/gd/os/linux_generic/queue_unittest.cc
@@ -19,6 +19,7 @@
 #include <sys/eventfd.h>
 
 #include <atomic>
+#include <chrono>
 #include <future>
 #include <unordered_map>
 
@@ -26,6 +27,8 @@
 #include "gtest/gtest.h"
 #include "os/reactor.h"
 
+using namespace std::chrono_literals;
+
 namespace bluetooth {
 namespace os {
 namespace {
@@ -60,6 +63,11 @@
   Handler* enqueue_handler_;
   Thread* dequeue_thread_;
   Handler* dequeue_handler_;
+
+  void sync_enqueue_handler() {
+    ASSERT(enqueue_thread_ != nullptr);
+    ASSERT(enqueue_thread_->GetReactor()->WaitForIdle(2s));
+  }
 };
 
 class TestEnqueueEnd {
@@ -96,11 +104,12 @@
       queue_->UnregisterEnqueue();
     }
 
-    auto pair = promise_map_->find(buffer_.size());
-    if (pair != promise_map_->end()) {
-      pair->second.set_value(pair->first);
-      promise_map_->erase(pair->first);
+    auto key = buffer_.size();
+    auto node = promise_map_->extract(key);
+    if (node) {
+      node.mapped().set_value(key);
     }
+
     return data;
   }
 
@@ -161,10 +170,10 @@
       queue_->UnregisterDequeue();
     }
 
-    auto pair = promise_map_->find(buffer_.size());
-    if (pair != promise_map_->end()) {
-      pair->second.set_value(pair->first);
-      promise_map_->erase(pair->first);
+    auto key = buffer_.size();
+    auto node = promise_map_->extract(key);
+    if (node) {
+      node.mapped().set_value(key);
     }
   }
 
@@ -337,6 +346,7 @@
   test_enqueue_end.RegisterEnqueue(&enqueue_promise_map);
   enqueue_future.wait();
   EXPECT_EQ(enqueue_future.get(), 0);
+  sync_enqueue_handler();
 }
 
 // Enqueue end level : 1
diff --git a/system/gd/os/metrics.h b/system/gd/os/metrics.h
index 339a2cf..cb35d68 100644
--- a/system/gd/os/metrics.h
+++ b/system/gd/os/metrics.h
@@ -166,7 +166,10 @@
  * @param smp_fail_reason SMP pairing failure reason code from SMP spec
  */
 void LogMetricSmpPairingEvent(
-    const hci::Address& address, uint8_t smp_cmd, android::bluetooth::DirectionEnum direction, uint8_t smp_fail_reason);
+    const hci::Address& address,
+    uint16_t smp_cmd,
+    android::bluetooth::DirectionEnum direction,
+    uint16_t smp_fail_reason);
 
 /**
  * Logs there is an event related Bluetooth classic pairing
diff --git a/system/gd/os/system_properties.h b/system/gd/os/system_properties.h
index f5a82b8..42ea4e7 100644
--- a/system/gd/os/system_properties.h
+++ b/system/gd/os/system_properties.h
@@ -26,6 +26,21 @@
 // or if the platform does not support system property
 std::optional<std::string> GetSystemProperty(const std::string& property);
 
+// Get |property| keyed system property as uint32_t from supported platform, return |default_value| if the property
+// does not exist or if the platform does not support system property
+uint32_t GetSystemPropertyUint32(const std::string& property, uint32_t default_value);
+
+// Get |property| keyed system property as uint32_t from supported platform, return |default_value|
+// if the property does not exist or if the platform does not support system property if property is
+// found it will call stoul with |base|
+uint32_t GetSystemPropertyUint32Base(
+    const std::string& property, uint32_t default_value, int base = 0);
+
+// Get |property| keyed property as bool from supported platform, return
+// |default_value| if the property does not exist or if the platform
+// does not support system property
+bool GetSystemPropertyBool(const std::string& property, bool default_value);
+
 // Set |property| keyed system property to |value|, return true if the set was successful and false if the set failed
 // Replace existing value if property already exists
 bool SetSystemProperty(const std::string& property, const std::string& value);
diff --git a/system/gd/os/system_properties_common.cc b/system/gd/os/system_properties_common.cc
new file mode 100644
index 0000000..f59560c
--- /dev/null
+++ b/system/gd/os/system_properties_common.cc
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <string>
+
+#include "common/strings.h"
+#include "os/system_properties.h"
+
+namespace bluetooth {
+namespace os {
+
+uint32_t GetSystemPropertyUint32(const std::string& property, uint32_t default_value) {
+  return GetSystemPropertyUint32Base(property, default_value, 10);
+}
+
+uint32_t GetSystemPropertyUint32Base(
+    const std::string& property, uint32_t default_value, int base) {
+  std::optional<std::string> result = GetSystemProperty(property);
+  if (result.has_value()) {
+    return static_cast<uint32_t>(std::stoul(*result, nullptr, base));
+  }
+  return default_value;
+}
+
+bool GetSystemPropertyBool(const std::string& property, bool default_value) {
+  std::optional<std::string> result = GetSystemProperty(property);
+  if (result.has_value()) {
+    std::string trimmed_val = common::StringTrim(result.value());
+    if (trimmed_val == "true" || trimmed_val == "1") {
+      return true;
+    }
+    if (trimmed_val == "false" || trimmed_val == "0") {
+      return false;
+    }
+  }
+  return default_value;
+}
+
+}  // namespace os
+}  // namespace bluetooth
diff --git a/system/gd/rust/common/src/init_flags.rs b/system/gd/rust/common/src/init_flags.rs
index 21e0c41..2beb2b5 100644
--- a/system/gd/rust/common/src/init_flags.rs
+++ b/system/gd/rust/common/src/init_flags.rs
@@ -207,7 +207,10 @@
     flags: {
         asynchronously_start_l2cap_coc = true,
         btaa_hci = true,
+        bta_dm_clear_conn_id_on_client_close = true,
         btm_dm_flush_discovery_queue_on_search_cancel,
+        clear_hidd_interrupt_cid_on_disconnect = true,
+        delay_hidh_cleanup_until_hidh_ready_start = true,
         finite_att_timeout = true,
         gatt_robust_caching_client = true,
         gatt_robust_caching_server,
diff --git a/system/gd/rust/shim/src/init_flags.rs b/system/gd/rust/shim/src/init_flags.rs
index 071d658..5c1e29d 100644
--- a/system/gd/rust/shim/src/init_flags.rs
+++ b/system/gd/rust/shim/src/init_flags.rs
@@ -6,7 +6,10 @@
 
         fn asynchronously_start_l2cap_coc_is_enabled() -> bool;
         fn btaa_hci_is_enabled() -> bool;
+        fn bta_dm_clear_conn_id_on_client_close_is_enabled() -> bool;
         fn btm_dm_flush_discovery_queue_on_search_cancel_is_enabled() -> bool;
+        fn delay_hidh_cleanup_until_hidh_ready_start_is_enabled() -> bool;
+        fn clear_hidd_interrupt_cid_on_disconnect_is_enabled() -> bool;
         fn finite_att_timeout_is_enabled() -> bool;
         fn gatt_robust_caching_client_is_enabled() -> bool;
         fn gatt_robust_caching_server_is_enabled() -> bool;
diff --git a/system/gd/stack_manager_unittest.cc b/system/gd/stack_manager_unittest.cc
index 824675e..51645ea 100644
--- a/system/gd/stack_manager_unittest.cc
+++ b/system/gd/stack_manager_unittest.cc
@@ -22,7 +22,7 @@
 namespace bluetooth {
 namespace {
 
-TEST(StackManagerTest, start_and_shutdown_no_module) {
+TEST(StackManagerTest, DISABLED_start_and_shutdown_no_module) {
   StackManager stack_manager;
   ModuleList module_list;
   os::Thread thread{"test_thread", os::Thread::Priority::NORMAL};
@@ -45,7 +45,7 @@
 
 const ModuleFactory TestModuleNoDependency::Factory = ModuleFactory([]() { return new TestModuleNoDependency(); });
 
-TEST(StackManagerTest, get_module_instance) {
+TEST(StackManagerTest, DISABLED_get_module_instance) {
   StackManager stack_manager;
   ModuleList module_list;
   module_list.add<TestModuleNoDependency>();
diff --git a/system/hci/Android.bp b/system/hci/Android.bp
index d9f9542..1553c25 100644
--- a/system/hci/Android.bp
+++ b/system/hci/Android.bp
@@ -91,28 +91,6 @@
     ],
 }
 
-// HCI native unit tests for target
-cc_test {
-    name: "net_test_hci_native",
-    test_suites: ["device-tests"],
-    defaults: [
-        "fluoride_unit_test_defaults",
-        "mts_defaults",
-    ],
-    local_include_dirs: [
-        "include",
-    ],
-    include_dirs: [
-        "packages/modules/Bluetooth/system",
-        "packages/modules/Bluetooth/system/gd",
-        "packages/modules/Bluetooth/system/stack/include",
-    ],
-    srcs: [
-        "test/hci_layer_test.cc",
-        "test/other_stack_stub.cc",
-    ],
-}
-
 cc_test {
     name: "net_test_hci_fragmenter_native",
     test_suites: ["device-tests"],
diff --git a/system/hci/test/hci_layer_test.cc b/system/hci/test/hci_layer_test.cc
deleted file mode 100644
index e69de29..0000000
--- a/system/hci/test/hci_layer_test.cc
+++ /dev/null
diff --git a/system/hci/test/other_stack_stub.cc b/system/hci/test/other_stack_stub.cc
deleted file mode 100644
index e69de29..0000000
--- a/system/hci/test/other_stack_stub.cc
+++ /dev/null
diff --git a/system/include/hardware/bt_hf_client.h b/system/include/hardware/bt_hf_client.h
index 7263805..bf8b1bb 100644
--- a/system/include/hardware/bt_hf_client.h
+++ b/system/include/hardware/bt_hf_client.h
@@ -392,6 +392,9 @@
   /** Send AT Command. */
   bt_status_t (*send_at_cmd)(const RawAddress* bd_addr, int cmd, int val1,
                              int val2, const char* arg);
+
+  /** Send hfp audio policy to remote */
+  bt_status_t (*send_android_at)(const RawAddress* bd_addr, const char* arg);
 } bthf_client_interface_t;
 
 __END_DECLS
diff --git a/system/internal_include/bt_target.h b/system/internal_include/bt_target.h
index 98787a3..0aab570 100644
--- a/system/internal_include/bt_target.h
+++ b/system/internal_include/bt_target.h
@@ -75,10 +75,6 @@
 #define BTA_HH_ROLE BTA_CENTRAL_ROLE_PREF
 #endif
 
-#ifndef BTA_DISABLE_DELAY
-#define BTA_DISABLE_DELAY 200 /* in milliseconds */
-#endif
-
 #ifndef AVDT_VERSION
 #define AVDT_VERSION 0x0103
 #endif
@@ -818,10 +814,6 @@
 #define PAN_INCLUDED TRUE
 #endif
 
-#ifndef PAN_NAP_DISABLED
-#define PAN_NAP_DISABLED FALSE
-#endif
-
 #ifndef PANU_DISABLED
 #define PANU_DISABLED FALSE
 #endif
@@ -974,10 +966,6 @@
  *
  *****************************************************************************/
 
-#ifndef AVRC_ADV_CTRL_INCLUDED
-#define AVRC_ADV_CTRL_INCLUDED TRUE
-#endif
-
 #ifndef DUMP_PCM_DATA
 #define DUMP_PCM_DATA FALSE
 #endif
@@ -1026,12 +1014,4 @@
 
 #include "bt_trace.h"
 
-#ifndef BTM_DELAY_AUTH_MS
-#define BTM_DELAY_AUTH_MS 0
-#endif
-
-#ifndef BTM_DISABLE_CONCURRENT_PEER_AUTH
-#define BTM_DISABLE_CONCURRENT_PEER_AUTH FALSE
-#endif
-
 #endif /* BT_TARGET_H */
diff --git a/system/main/shim/metrics_api.cc b/system/main/shim/metrics_api.cc
index 99832be..82ad3c1 100644
--- a/system/main/shim/metrics_api.cc
+++ b/system/main/shim/metrics_api.cc
@@ -89,9 +89,9 @@
                                                  transmit_power_level);
 }
 
-void LogMetricSmpPairingEvent(const RawAddress& raw_address, uint8_t smp_cmd,
+void LogMetricSmpPairingEvent(const RawAddress& raw_address, uint16_t smp_cmd,
                               android::bluetooth::DirectionEnum direction,
-                              uint8_t smp_fail_reason) {
+                              uint16_t smp_fail_reason) {
   Address address = bluetooth::ToGdAddress(raw_address);
   bluetooth::os::LogMetricSmpPairingEvent(address, smp_cmd, direction,
                                           smp_fail_reason);
diff --git a/system/main/shim/metrics_api.h b/system/main/shim/metrics_api.h
index ce13876..f838908 100644
--- a/system/main/shim/metrics_api.h
+++ b/system/main/shim/metrics_api.h
@@ -134,9 +134,9 @@
  * @param direction direction of this SMP command
  * @param smp_fail_reason SMP pairing failure reason code from SMP spec
  */
-void LogMetricSmpPairingEvent(const RawAddress& address, uint8_t smp_cmd,
+void LogMetricSmpPairingEvent(const RawAddress& address, uint16_t smp_cmd,
                               android::bluetooth::DirectionEnum direction,
-                              uint8_t smp_fail_reason);
+                              uint16_t smp_fail_reason);
 
 /**
  * Logs there is an event related Bluetooth classic pairing
diff --git a/system/packet/avrcp/get_element_attributes_packet.cc b/system/packet/avrcp/get_element_attributes_packet.cc
index 8634ce0..f08a698 100644
--- a/system/packet/avrcp/get_element_attributes_packet.cc
+++ b/system/packet/avrcp/get_element_attributes_packet.cc
@@ -90,7 +90,7 @@
   return builder;
 }
 
-bool GetElementAttributesResponseBuilder::AddAttributeEntry(
+size_t GetElementAttributesResponseBuilder::AddAttributeEntry(
     AttributeEntry entry) {
   CHECK_LT(entries_.size(), size_t(0xFF))
       << __func__ << ": attribute entry overflow";
@@ -101,15 +101,15 @@
   }
 
   if (entry.empty()) {
-    return false;
+    return 0;
   }
 
   entries_.insert(entry);
-  return true;
+  return entry.size();
 }
 
-bool GetElementAttributesResponseBuilder::AddAttributeEntry(Attribute attribute,
-                                                            std::string value) {
+size_t GetElementAttributesResponseBuilder::AddAttributeEntry(
+    Attribute attribute, const std::string& value) {
   return AddAttributeEntry(AttributeEntry(attribute, value));
 }
 
@@ -120,7 +120,7 @@
     attr_list_size += attribute_entry.size();
   }
 
-  return VendorPacket::kMinSize() + 1 + attr_list_size;
+  return kHeaderSize() + attr_list_size;
 }
 
 bool GetElementAttributesResponseBuilder::Serialize(
diff --git a/system/packet/avrcp/get_element_attributes_packet.h b/system/packet/avrcp/get_element_attributes_packet.h
index e60844c..b1d4c61 100644
--- a/system/packet/avrcp/get_element_attributes_packet.h
+++ b/system/packet/avrcp/get_element_attributes_packet.h
@@ -58,15 +58,21 @@
   using VendorPacket::VendorPacket;
 };
 
+template <class Builder>
+class AttributesResponseBuilderTestUser;
+
 class GetElementAttributesResponseBuilder : public VendorPacketBuilder {
  public:
   virtual ~GetElementAttributesResponseBuilder() = default;
+  using Builder = std::unique_ptr<GetElementAttributesResponseBuilder>;
+  static Builder MakeBuilder(size_t mtu);
 
-  static std::unique_ptr<GetElementAttributesResponseBuilder> MakeBuilder(
-      size_t mtu);
+  size_t AddAttributeEntry(AttributeEntry entry);
+  size_t AddAttributeEntry(Attribute attribute, const std::string& value);
 
-  bool AddAttributeEntry(AttributeEntry entry);
-  bool AddAttributeEntry(Attribute attribute, std::string value);
+  virtual void clear() { entries_.clear(); }
+
+  static constexpr size_t kHeaderSize() { return VendorPacket::kMinSize() + 1; }
 
   virtual size_t size() const override;
   virtual bool Serialize(
@@ -75,6 +81,8 @@
  private:
   std::set<AttributeEntry> entries_;
   size_t mtu_;
+  friend class AttributesResponseBuilderTestUser<
+      GetElementAttributesResponseBuilder>;
 
   GetElementAttributesResponseBuilder(size_t mtu)
       : VendorPacketBuilder(CType::STABLE, CommandPdu::GET_ELEMENT_ATTRIBUTES,
@@ -83,4 +91,4 @@
 };
 
 }  // namespace avrcp
-}  // namespace bluetooth
\ No newline at end of file
+}  // namespace bluetooth
diff --git a/system/packet/avrcp/get_item_attributes.cc b/system/packet/avrcp/get_item_attributes.cc
index fb2ba87..0c7afe4 100644
--- a/system/packet/avrcp/get_item_attributes.cc
+++ b/system/packet/avrcp/get_item_attributes.cc
@@ -27,7 +27,8 @@
   return builder;
 }
 
-bool GetItemAttributesResponseBuilder::AddAttributeEntry(AttributeEntry entry) {
+size_t GetItemAttributesResponseBuilder::AddAttributeEntry(
+    AttributeEntry entry) {
   CHECK(entries_.size() < 0xFF);
 
   size_t remaining_space = mtu_ - size();
@@ -36,24 +37,22 @@
   }
 
   if (entry.empty()) {
-    return false;
+    return 0;
   }
 
   entries_.insert(entry);
-  return true;
+  return entry.size();
 }
 
-bool GetItemAttributesResponseBuilder::AddAttributeEntry(Attribute attribute,
-                                                         std::string value) {
+size_t GetItemAttributesResponseBuilder::AddAttributeEntry(
+    Attribute attribute, const std::string& value) {
   return AddAttributeEntry(AttributeEntry(attribute, value));
 }
 
 size_t GetItemAttributesResponseBuilder::size() const {
-  size_t len = BrowsePacket::kMinSize();
-  len += 1;  // Status
-  if (status_ != Status::NO_ERROR) return len;
+  size_t len = kHeaderSize();
+  if (status_ != Status::NO_ERROR) return kErrorHeaderSize();
 
-  len += 1;  // Number of attributes
   for (const auto& entry : entries_) {
     len += entry.size();
   }
diff --git a/system/packet/avrcp/get_item_attributes.h b/system/packet/avrcp/get_item_attributes.h
index aa1db71..7811909 100644
--- a/system/packet/avrcp/get_item_attributes.h
+++ b/system/packet/avrcp/get_item_attributes.h
@@ -23,15 +23,32 @@
 namespace bluetooth {
 namespace avrcp {
 
+template <class Builder>
+class AttributesResponseBuilderTestUser;
+
 class GetItemAttributesResponseBuilder : public BrowsePacketBuilder {
  public:
   virtual ~GetItemAttributesResponseBuilder() = default;
+  using Builder = std::unique_ptr<GetItemAttributesResponseBuilder>;
+  static Builder MakeBuilder(Status status, size_t mtu);
 
-  static std::unique_ptr<GetItemAttributesResponseBuilder> MakeBuilder(
-      Status status, size_t mtu);
+  size_t AddAttributeEntry(AttributeEntry entry);
+  size_t AddAttributeEntry(Attribute, const std::string&);
 
-  bool AddAttributeEntry(AttributeEntry entry);
-  bool AddAttributeEntry(Attribute, std::string);
+  virtual void clear() { entries_.clear(); }
+
+  static constexpr size_t kHeaderSize() {
+    size_t len = BrowsePacket::kMinSize();
+    len += 1;  // Status
+    len += 1;  // Number of attributes
+    return len;
+  }
+
+  static constexpr size_t kErrorHeaderSize() {
+    size_t len = BrowsePacket::kMinSize();
+    len += 1;  // Status
+    return len;
+  }
 
   virtual size_t size() const override;
   virtual bool Serialize(
@@ -41,6 +58,8 @@
   Status status_;
   size_t mtu_;
   std::set<AttributeEntry> entries_;
+  friend class AttributesResponseBuilderTestUser<
+      GetItemAttributesResponseBuilder>;
 
   GetItemAttributesResponseBuilder(Status status, size_t mtu)
       : BrowsePacketBuilder(BrowsePdu::GET_ITEM_ATTRIBUTES),
@@ -81,4 +100,4 @@
 };
 
 }  // namespace avrcp
-}  // namespace bluetooth
\ No newline at end of file
+}  // namespace bluetooth
diff --git a/system/packet/tests/avrcp/get_element_attributes_packet_test.cc b/system/packet/tests/avrcp/get_element_attributes_packet_test.cc
index 8a05487..96ae1fa 100644
--- a/system/packet/tests/avrcp/get_element_attributes_packet_test.cc
+++ b/system/packet/tests/avrcp/get_element_attributes_packet_test.cc
@@ -103,6 +103,47 @@
   ASSERT_EQ(test_packet->GetData(), get_elements_attributes_response_full);
 }
 
+TEST(GetElementAttributesResponseBuilderTest, builderMtuTest) {
+  std::vector<AttributeEntry> test_data = {
+      {Attribute::TITLE, "Test Song 1"},
+      {Attribute::ARTIST_NAME, "Test Artist"},
+      {Attribute::ALBUM_NAME, "Test Album"},
+      {Attribute::TRACK_NUMBER, "1"},
+      {Attribute::TOTAL_NUMBER_OF_TRACKS, "2"},
+      {Attribute::GENRE, "Test Genre"},
+      {Attribute::PLAYING_TIME, "10 200"},
+      {Attribute::TITLE, "Test Song 2"},
+      {Attribute::ARTIST_NAME, "Test Artist"},
+      {Attribute::ALBUM_NAME, "Test Album"},
+      {Attribute::TRACK_NUMBER, "2"},
+      {Attribute::TOTAL_NUMBER_OF_TRACKS, "2"},
+      {Attribute::GENRE, "Test Genre"},
+      {Attribute::PLAYING_TIME, "1500"},
+  };
+
+  using Builder = GetElementAttributesResponseBuilder;
+  using Helper = FragmentationBuilderHelper<Builder>;
+  size_t mtu = size_t(-1);
+  Helper helper(mtu, [](size_t mtu) { return Builder::MakeBuilder(mtu); });
+
+  EXPECT_NO_FATAL_FAILURE(helper.runTest(test_data, mtu, false, false));
+
+  mtu = test_data[0].size() + Builder::kHeaderSize();
+  EXPECT_NO_FATAL_FAILURE(helper.runTest(test_data, mtu));
+
+  mtu = test_data[0].size() + test_data[1].size() + Builder::kHeaderSize();
+  EXPECT_NO_FATAL_FAILURE(helper.runTest(test_data, mtu));
+
+  mtu = test_data[0].size() + (Builder::kHeaderSize() * 2) + 1;
+  EXPECT_NO_FATAL_FAILURE(helper.runTest(test_data, mtu, true, false));
+
+  mtu = Builder::kHeaderSize() + AttributeEntry::kHeaderSize() + 1;
+  EXPECT_NO_FATAL_FAILURE(helper.runTest(test_data, mtu));
+
+  mtu = Builder::kHeaderSize() + AttributeEntry::kHeaderSize();
+  EXPECT_NO_FATAL_FAILURE(helper.runTest(test_data, mtu, false, false));
+}
+
 TEST(GetElementAttributesResponseBuilderTest, truncateBuilderTest) {
   auto attribute = AttributeEntry(Attribute::TITLE, "1234");
   size_t truncated_size = VendorPacket::kMinSize();
@@ -130,4 +171,4 @@
 }
 
 }  // namespace avrcp
-}  // namespace bluetooth
\ No newline at end of file
+}  // namespace bluetooth
diff --git a/system/packet/tests/avrcp/get_item_attributes_packet_test.cc b/system/packet/tests/avrcp/get_item_attributes_packet_test.cc
index 3d6f691..4f1da7b 100644
--- a/system/packet/tests/avrcp/get_item_attributes_packet_test.cc
+++ b/system/packet/tests/avrcp/get_item_attributes_packet_test.cc
@@ -126,5 +126,48 @@
   ASSERT_FALSE(test_packet->IsValid());
 }
 
+TEST(GetItemAttributesRequestTest, builderMtuTest) {
+  std::vector<AttributeEntry> test_data = {
+      {Attribute::TITLE, "Test Song 1"},
+      {Attribute::ARTIST_NAME, "Test Artist"},
+      {Attribute::ALBUM_NAME, "Test Album"},
+      {Attribute::TRACK_NUMBER, "1"},
+      {Attribute::TOTAL_NUMBER_OF_TRACKS, "2"},
+      {Attribute::GENRE, "Test Genre"},
+      {Attribute::PLAYING_TIME, "10 200"},
+      {Attribute::TITLE, "Test Song 2"},
+      {Attribute::ARTIST_NAME, "Test Artist"},
+      {Attribute::ALBUM_NAME, "Test Album"},
+      {Attribute::TRACK_NUMBER, "2"},
+      {Attribute::TOTAL_NUMBER_OF_TRACKS, "2"},
+      {Attribute::GENRE, "Test Genre"},
+      {Attribute::PLAYING_TIME, "1500"},
+  };
+
+  using Builder = GetItemAttributesResponseBuilder;
+  using Helper = FragmentationBuilderHelper<Builder>;
+  size_t mtu = size_t(-1);
+  Helper helper(mtu, [](size_t mtu) {
+    return Builder::MakeBuilder(Status::NO_ERROR, mtu);
+  });
+
+  EXPECT_NO_FATAL_FAILURE(helper.runTest(test_data, mtu, false, false));
+
+  mtu = test_data[0].size() + Builder::kHeaderSize();
+  EXPECT_NO_FATAL_FAILURE(helper.runTest(test_data, mtu));
+
+  mtu = test_data[0].size() + test_data[1].size() + Builder::kHeaderSize();
+  EXPECT_NO_FATAL_FAILURE(helper.runTest(test_data, mtu));
+
+  mtu = test_data[0].size() + (Builder::kHeaderSize() * 2) + 1;
+  EXPECT_NO_FATAL_FAILURE(helper.runTest(test_data, mtu, true, false));
+
+  mtu = Builder::kHeaderSize() + AttributeEntry::kHeaderSize() + 1;
+  EXPECT_NO_FATAL_FAILURE(helper.runTest(test_data, mtu));
+
+  mtu = Builder::kHeaderSize() + AttributeEntry::kHeaderSize();
+  EXPECT_NO_FATAL_FAILURE(helper.runTest(test_data, mtu, false, false));
+}
+
 }  // namespace avrcp
-}  // namespace bluetooth
\ No newline at end of file
+}  // namespace bluetooth
diff --git a/system/packet/tests/packet_test_helper.h b/system/packet/tests/packet_test_helper.h
index ad3db2c..e03280c 100644
--- a/system/packet/tests/packet_test_helper.h
+++ b/system/packet/tests/packet_test_helper.h
@@ -16,10 +16,11 @@
 
 #pragma once
 
+#include <list>
 #include <memory>
 
+#include "avrcp_common.h"
 #include "packet.h"
-
 namespace bluetooth {
 
 // A helper templated class to access the protected members of Packet to make
@@ -63,4 +64,229 @@
   }
 };
 
-}  // namespace bluetooth
\ No newline at end of file
+namespace avrcp {
+
+inline std::string to_string(const Attribute& a) {
+  switch (a) {
+    case Attribute::TITLE:
+      return "TITLE";
+    case Attribute::ARTIST_NAME:
+      return "ARTIST_NAME";
+    case Attribute::ALBUM_NAME:
+      return "ALBUM_NAME";
+    case Attribute::TRACK_NUMBER:
+      return "TRACK_NUMBER";
+    case Attribute::TOTAL_NUMBER_OF_TRACKS:
+      return "TOTAL_NUMBER_OF_TRACKS";
+    case Attribute::GENRE:
+      return "GENRE";
+    case Attribute::PLAYING_TIME:
+      return "PLAYING_TIME";
+    case Attribute::DEFAULT_COVER_ART:
+      return "DEFAULT_COVER_ART";
+    default:
+      return "UNKNOWN ATTRIBUTE";
+  };
+}
+
+inline std::string to_string(const AttributeEntry& entry) {
+  std::stringstream ss;
+  ss << to_string(entry.attribute()) << ": " << entry.value();
+  return ss.str();
+}
+
+template <class Container>
+std::string to_string(const Container& entries) {
+  std::stringstream ss;
+  for (const auto& el : entries) {
+    ss << to_string(el) << std::endl;
+  }
+  return ss.str();
+}
+
+inline bool operator==(const AttributeEntry& a, const AttributeEntry& b) {
+  return (a.attribute() == b.attribute()) && (a.value() == b.value());
+}
+
+inline bool operator!=(const AttributeEntry& a, const AttributeEntry& b) {
+  return !(a == b);
+}
+
+template <class AttributesResponseBuilder>
+class AttributesResponseBuilderTestUser {
+ public:
+  using Builder = AttributesResponseBuilder;
+  using Maker = std::function<typename Builder::Builder(size_t)>;
+
+ private:
+  Maker maker;
+  typename Builder::Builder _builder;
+  size_t _mtu;
+  size_t _current_size = 0;
+  size_t _entry_counter = 0;
+  std::set<AttributeEntry> _control_set;
+  std::list<AttributeEntry> _order_control;
+  std::list<AttributeEntry> _sended_order;
+  std::stringstream _report;
+  bool _test_result = true;
+  bool _order_test_result = true;
+
+  void reset() {
+    for (const auto& en : _builder->entries_) {
+      _sended_order.push_back(en);
+    }
+    _current_size = 0, _entry_counter = 0;
+    _control_set.clear();
+    _builder->clear();
+  }
+
+  size_t expected_size() { return Builder::kHeaderSize() + _current_size; }
+
+ public:
+  std::string getReport() const { return _report.str(); }
+
+  AttributesResponseBuilderTestUser(size_t m_size, Maker maker)
+      : maker(maker), _builder(maker(m_size)), _mtu(m_size) {
+    _report << __func__ << ": mtu \"" << _mtu << "\"\n";
+  }
+
+  void startTest(size_t m_size) {
+    _builder = maker(m_size);
+    _mtu = m_size;
+    reset();
+    _report.str("");
+    _report.clear();
+    _order_control.clear();
+    _sended_order.clear();
+    _report << __func__ << ": mtu \"" << _mtu << "\"\n";
+    _order_test_result = true;
+    _test_result = true;
+  }
+
+  bool testResult() const { return _test_result; }
+
+  bool testOrder() { return _order_test_result; }
+
+  void finishTest() {
+    reset();
+    if (_order_control.size() != _sended_order.size()) {
+      _report << __func__ << ": testOrder FAIL: "
+              << "the count of entries which should send ("
+              << _order_control.size() << ") is not equal to sended entries("
+              << _sended_order.size() << ")) \n input:\n "
+              << to_string(_order_control) << "\n sended:\n"
+              << to_string(_sended_order) << "\n";
+      _order_test_result = false;
+      return;
+    }
+    auto e = _order_control.begin();
+    auto s = _sended_order.begin();
+    for (; e != _order_control.end(); ++e, ++s) {
+      if (*e != *s) {
+        _report << __func__ << "testOrder FAIL: order of entries was changed\n";
+        _order_test_result = false;
+        break;
+      }
+    }
+    _report << __func__ << ": mtu \"" << _mtu << "\"\n";
+  }
+
+  void AddAttributeEntry(AttributeEntry entry) {
+    auto f = _builder->AddAttributeEntry(entry);
+    if (f != 0) {
+      _current_size += f;
+      ++_entry_counter;
+    }
+    if (f == entry.size()) {
+      wholeEntry(f, std::move(entry));
+    } else {
+      fractionEntry(f, std::move(entry));
+    }
+  }
+
+ private:
+  void wholeEntry(size_t f, AttributeEntry&& entry) {
+    _control_set.insert(entry);
+    _order_control.push_back(entry);
+    if (_builder->size() != expected_size()) {
+      _report << __func__ << "FAIL for \"" << to_string(entry)
+              << "\": not allowed to add.\n";
+      _test_result = false;
+    }
+  }
+
+  void fractionEntry(size_t f, AttributeEntry&& entry) {
+    auto l_value = entry.value().size() - (entry.size() - f);
+    if (f != 0) {
+      auto pushed_entry = AttributeEntry(
+          entry.attribute(), std::string(entry.value(), 0, l_value));
+      _control_set.insert(pushed_entry);
+      _order_control.push_back(pushed_entry);
+    }
+
+    if (expected_size() != _builder->size()) {
+      _test_result = false;
+      _report << __func__ << "FAIL for \"" << to_string(entry)
+              << "\": not allowed to add.\n";
+    }
+
+    if (_builder->size() != expected_size() ||
+        _builder->entries_.size() != _entry_counter) {
+      _report << __func__ << "FAIL for \"" << to_string(entry)
+              << "\": unexpected size of packet\n";
+      _test_result = false;
+    }
+    for (auto dat = _builder->entries_.begin(), ex = _control_set.begin();
+         ex != _control_set.end(); ++dat, ++ex) {
+      if (*dat != *ex) {
+        _report << __func__ << "FAIL for \"" << to_string(entry)
+                << "\": unexpected entry order\n";
+        _test_result = false;
+      }
+    }
+    auto tail = (f == 0) ? entry
+                         : AttributeEntry(entry.attribute(),
+                                          std::string(entry.value(), l_value));
+    if (_builder->entries_.size() != 0) {
+      reset();
+      AddAttributeEntry(tail);
+    }
+    if (_builder->entries_.size() == 0) {
+      _report << __func__ << "FAIL: MTU " << _mtu << " too small\n";
+      _test_result = false;
+      _order_control.push_back(entry);
+      reset();
+    }
+  }
+};
+
+template <class AttributesBuilder>
+class FragmentationBuilderHelper {
+ public:
+  using Builder = AttributesBuilder;
+  using Helper = AttributesResponseBuilderTestUser<Builder>;
+  using Maker = typename Helper::Maker;
+
+  FragmentationBuilderHelper(size_t mtu, Maker m) : _helper(mtu, m) {}
+
+  template <class TestCollection>
+  void runTest(const TestCollection& test_data, size_t mtu,
+               bool expect_fragmentation = true, bool expect_ordering = true) {
+    _helper.startTest(mtu);
+
+    for (auto& i : test_data) {
+      _helper.AddAttributeEntry(i);
+    }
+    _helper.finishTest();
+
+    EXPECT_EQ(expect_fragmentation, _helper.testResult())
+        << "Report: " << _helper.getReport();
+    EXPECT_EQ(expect_ordering, _helper.testOrder())
+        << "Report: " << _helper.getReport();
+  }
+
+ private:
+  Helper _helper;
+};
+}  // namespace avrcp
+}  // namespace bluetooth
diff --git a/system/stack/Android.bp b/system/stack/Android.bp
index 73b8a53..3db56b9 100644
--- a/system/stack/Android.bp
+++ b/system/stack/Android.bp
@@ -207,6 +207,7 @@
         "libbt-hci",
     ],
     whole_static_libs: [
+        "libcom.android.sysprop.bluetooth",
         "libldacBT_abr",
         "libldacBT_enc",
         "libaptx_enc",
@@ -235,6 +236,7 @@
     srcs: [
         "test/stack_a2dp_test.cc",
         "test/stack_avrcp_test.cc",
+        "test/gatt/gatt_api_test.cc",
     ],
     shared_libs: [
         "android.hardware.bluetooth@1.0",
@@ -481,8 +483,12 @@
 // Bluetooth stack connection multiplexing
 cc_test {
     name: "net_test_gatt_conn_multiplexing",
-    defaults: ["fluoride_defaults"],
+    defaults: [
+        "fluoride_defaults",
+        "mts_defaults",
+    ],
     host_supported: true,
+    test_suites: ["general-tests"],
     local_include_dirs: [
         "include",
         "btm",
@@ -937,6 +943,7 @@
         ":BluetoothHalSources_hci_host",
         ":BluetoothOsSources_host",
         ":TestCommonMainHandler",
+        ":TestCommonMockFunctions",
         ":TestMockBta",
         ":TestMockBtif",
         ":TestMockDevice",
@@ -978,6 +985,7 @@
         "btm/btm_sec.cc",
         "metrics/stack_metrics_logging.cc",
         "test/btm/stack_btm_test.cc",
+        "test/btm/stack_btm_regression_tests.cc",
         "test/btm/peer_packet_types_test.cc",
         "test/common/mock_eatt.cc",
     ],
@@ -997,9 +1005,14 @@
         "libprotobuf-cpp-lite",
     ],
     sanitize: {
-      hwaddress: true,
-      integer_overflow: true,
-      scs: true,
+        address: true,
+        all_undefined: true,
+        cfi: true,
+        integer_overflow: true,
+        scs: true,
+        diag: {
+            undefined : true
+        },
     },
 }
 
@@ -1289,6 +1302,7 @@
         "libbt-common",
         "libbt-protos-lite",
         "libbtdevice",
+        "libflatbuffers-cpp",
         "libgmock",
         "liblog",
         "libosi",
@@ -1296,7 +1310,6 @@
     shared_libs: [
         "libbinder_ndk",
         "libcrypto",
-        "libflatbuffers-cpp",
         "libprotobuf-cpp-lite",
     ],
     sanitize: {
@@ -1359,6 +1372,7 @@
     static_libs: [
         "libbt-common",
         "libbt-protos-lite",
+        "libflatbuffers-cpp",
         "libgmock",
         "liblog",
         "libosi",
@@ -1366,7 +1380,6 @@
     shared_libs: [
         "libbinder_ndk",
         "libcrypto",
-        "libflatbuffers-cpp",
         "libprotobuf-cpp-lite",
     ],
     sanitize: {
diff --git a/system/stack/a2dp/a2dp_sbc.cc b/system/stack/a2dp/a2dp_sbc.cc
index c5d5a15..5a95099 100644
--- a/system/stack/a2dp/a2dp_sbc.cc
+++ b/system/stack/a2dp/a2dp_sbc.cc
@@ -696,6 +696,11 @@
     return false;
   }
 
+  // there is an 4-byte timestamp right following p_buf
+  if (p_buf->offset < 4 + A2DP_SBC_MPL_HDR_LEN) {
+    return false;
+  }
+
   p_buf->offset -= A2DP_SBC_MPL_HDR_LEN;
   uint8_t* p = (uint8_t*)(p_buf + 1) + p_buf->offset;
   p_buf->len += A2DP_SBC_MPL_HDR_LEN;
diff --git a/system/stack/a2dp/a2dp_vendor_ldac.cc b/system/stack/a2dp/a2dp_vendor_ldac.cc
index b6aaee5..e8430ff 100644
--- a/system/stack/a2dp/a2dp_vendor_ldac.cc
+++ b/system/stack/a2dp/a2dp_vendor_ldac.cc
@@ -518,6 +518,11 @@
                                      uint16_t frames_per_packet) {
   uint8_t* p;
 
+  // there is a 4 byte timestamp right following p_buf
+  if (p_buf->offset < 4 + A2DP_LDAC_MPL_HDR_LEN) {
+    return false;
+  }
+
   p_buf->offset -= A2DP_LDAC_MPL_HDR_LEN;
   p = (uint8_t*)(p_buf + 1) + p_buf->offset;
   p_buf->len += A2DP_LDAC_MPL_HDR_LEN;
diff --git a/system/stack/acl/btm_acl.cc b/system/stack/acl/btm_acl.cc
index cda4d85..eb74e90 100644
--- a/system/stack/acl/btm_acl.cc
+++ b/system/stack/acl/btm_acl.cc
@@ -50,6 +50,7 @@
 #include "main/shim/dumpsys.h"
 #include "main/shim/l2c_api.h"
 #include "main/shim/shim.h"
+#include "os/parameter_provider.h"
 #include "osi/include/allocator.h"
 #include "osi/include/log.h"
 #include "osi/include/osi.h"  // UNUSED_ATTR
@@ -339,6 +340,11 @@
   uint8_t sca;
   uint8_t status;
 
+  if (len < 4) {
+    LOG_WARN("Malformatted packet, not containing enough data");
+    return;
+  }
+
   STREAM_TO_UINT8(status, data);
 
   if (status != HCI_SUCCESS) {
@@ -630,6 +636,23 @@
     return;
   }
 
+  /* Common Criteria mode only: if we are trying to drop encryption on an
+   * encrypted connection, drop the connection */
+  if (bluetooth::os::ParameterProvider::IsCommonCriteriaMode()) {
+    if (p->is_encrypted && !encr_enable) {
+      LOG(ERROR)
+          << __func__
+          << " attempting to decrypt encrypted connection, disconnecting. "
+             "handle: "
+          << loghex(handle);
+
+      acl_disconnect_from_handle(handle, HCI_ERR_HOST_REJECT_SECURITY,
+                                 "stack::btu::btu_hcif::read_drop_encryption "
+                                 "Connection Already Encrypted");
+      return;
+    }
+  }
+
   p->is_encrypted = encr_enable;
 
   /* Process Role Switch if active */
@@ -1754,7 +1777,7 @@
  * Returns          void
  *
  ******************************************************************************/
-void btm_read_tx_power_complete(uint8_t* p, bool is_ble) {
+void btm_read_tx_power_complete(uint8_t* p, uint16_t evt_len, bool is_ble) {
   tBTM_CMPL_CB* p_cb = btm_cb.devcb.p_tx_power_cmpl_cb;
   tBTM_TX_POWER_RESULT result;
 
@@ -1763,6 +1786,10 @@
 
   /* If there was a registered callback, call it */
   if (p_cb) {
+    if (evt_len < 1) {
+      goto err_out;
+    }
+
     STREAM_TO_UINT8(result.hci_status, p);
 
     if (result.hci_status == HCI_SUCCESS) {
@@ -1770,6 +1797,11 @@
 
       if (!is_ble) {
         uint16_t handle;
+
+        if (evt_len < 4) {
+          goto err_out;
+        }
+
         STREAM_TO_UINT16(handle, p);
         STREAM_TO_UINT8(result.tx_power, p);
 
@@ -1778,6 +1810,10 @@
           result.rem_bda = p_acl_cb->remote_addr;
         }
       } else {
+        if (evt_len < 2) {
+          goto err_out;
+        }
+
         STREAM_TO_UINT8(result.tx_power, p);
         result.rem_bda = btm_cb.devcb.read_tx_pwr_addr;
       }
@@ -1791,6 +1827,11 @@
 
     (*p_cb)(&result);
   }
+
+  return;
+
+ err_out:
+  LOG_ERROR("Bogus event packet, too short");
 }
 
 /*******************************************************************************
@@ -1820,7 +1861,7 @@
  * Returns          void
  *
  ******************************************************************************/
-void btm_read_rssi_complete(uint8_t* p) {
+void btm_read_rssi_complete(uint8_t* p, uint16_t evt_len) {
   tBTM_CMPL_CB* p_cb = btm_cb.devcb.p_rssi_cmpl_cb;
   tBTM_RSSI_RESULT result;
 
@@ -1829,11 +1870,19 @@
 
   /* If there was a registered callback, call it */
   if (p_cb) {
+    if (evt_len < 1) {
+      goto err_out;
+    }
+
     STREAM_TO_UINT8(result.hci_status, p);
     result.status = BTM_ERR_PROCESSING;
 
     if (result.hci_status == HCI_SUCCESS) {
       uint16_t handle;
+
+      if (evt_len < 4) {
+        goto err_out;
+      }
       STREAM_TO_UINT16(handle, p);
 
       STREAM_TO_UINT8(result.rssi, p);
@@ -1849,6 +1898,11 @@
     }
     (*p_cb)(&result);
   }
+
+  return;
+
+err_out:
+  LOG_ERROR("Bogus event packet, too short");
 }
 
 /*******************************************************************************
@@ -1983,7 +2037,7 @@
  * Returns          void
  *
  ******************************************************************************/
-void btm_read_link_quality_complete(uint8_t* p) {
+void btm_read_link_quality_complete(uint8_t* p, uint16_t evt_len) {
   tBTM_CMPL_CB* p_cb = btm_cb.devcb.p_link_qual_cmpl_cb;
   tBTM_LINK_QUALITY_RESULT result;
 
@@ -1992,12 +2046,20 @@
 
   /* If there was a registered callback, call it */
   if (p_cb) {
+    if (evt_len < 1) {
+      goto err_out;
+    }
+
     STREAM_TO_UINT8(result.hci_status, p);
 
     if (result.hci_status == HCI_SUCCESS) {
       uint16_t handle;
       result.status = BTM_SUCCESS;
 
+      if (evt_len < 4) {
+        goto err_out;
+      }
+
       STREAM_TO_UINT16(handle, p);
 
       STREAM_TO_UINT8(result.link_quality, p);
@@ -2016,6 +2078,11 @@
 
     (*p_cb)(&result);
   }
+
+  return;
+
+err_out:
+  LOG_ERROR("Bogus Link Quality event packet, size: %d", evt_len);
 }
 
 /*******************************************************************************
diff --git a/system/stack/avdt/avdt_scb.cc b/system/stack/avdt/avdt_scb.cc
index bcb8a1a..f67b396 100644
--- a/system/stack/avdt/avdt_scb.cc
+++ b/system/stack/avdt/avdt_scb.cc
@@ -228,7 +228,7 @@
     /* TC_DATA_EVT */
     {AVDT_SCB_DROP_PKT, AVDT_SCB_IGNORE, AVDT_SCB_IDLE_ST},
     /* CC_CLOSE_EVT */
-    {AVDT_SCB_CLR_VARS, AVDT_SCB_IGNORE, AVDT_SCB_IDLE_ST}};
+    {AVDT_SCB_HDL_TC_CLOSE, AVDT_SCB_CLR_VARS, AVDT_SCB_IDLE_ST}};
 
 /* state table for configured state */
 const uint8_t avdt_scb_st_conf[][AVDT_SCB_NUM_COLS] = {
diff --git a/system/stack/avrc/avrc_api.cc b/system/stack/avrc/avrc_api.cc
index 9cbb2fe..dab1d24 100644
--- a/system/stack/avrc/avrc_api.cc
+++ b/system/stack/avrc/avrc_api.cc
@@ -23,6 +23,9 @@
  ******************************************************************************/
 #include "avrc_api.h"
 
+#ifdef OS_ANDROID
+#include <avrcp.sysprop.h>
+#endif
 #include <base/logging.h>
 #include <string.h>
 
@@ -77,6 +80,25 @@
 
 /******************************************************************************
  *
+ * Function         avrcp_absolute_volume_is_enabled
+ *
+ * Description      Check if config support advance control (absolute volume)
+ *
+ * Returns          return true if absolute_volume is enabled
+ *
+ *****************************************************************************/
+bool avrcp_absolute_volume_is_enabled() {
+#ifdef OS_ANDROID
+  static const bool absolute_volume =
+      android::sysprop::bluetooth::Avrcp::absolute_volume().value_or(true);
+  return absolute_volume;
+#else
+  return true;
+#endif
+}
+
+/******************************************************************************
+ *
  * Function         avrc_ctrl_cback
  *
  * Description      This is the callback function used by AVCTP to report
diff --git a/system/stack/avrc/avrc_bld_ct.cc b/system/stack/avrc/avrc_bld_ct.cc
index deadd29..5eb5d3b 100644
--- a/system/stack/avrc/avrc_bld_ct.cc
+++ b/system/stack/avrc/avrc_bld_ct.cc
@@ -58,7 +58,6 @@
  *  the following commands are introduced in AVRCP 1.4
  ****************************************************************************/
 
-#if (AVRC_ADV_CTRL_INCLUDED == TRUE)
 /*******************************************************************************
  *
  * Function         avrc_bld_set_abs_volume_cmd
@@ -110,7 +109,6 @@
   p_pkt->len = (p_data - p_start);
   return AVRC_STS_NO_ERROR;
 }
-#endif
 
 /*******************************************************************************
  *
@@ -607,16 +605,18 @@
     case AVRC_PDU_ABORT_CONTINUATION_RSP: /*          0x41 */
       status = avrc_bld_next_cmd(&p_cmd->abort, p_pkt);
       break;
-#if (AVRC_ADV_CTRL_INCLUDED == TRUE)
     case AVRC_PDU_SET_ABSOLUTE_VOLUME: /* 0x50 */
+      if (!avrcp_absolute_volume_is_enabled()) {
+        break;
+      }
       status = avrc_bld_set_abs_volume_cmd(&p_cmd->volume, p_pkt);
       break;
-#endif
     case AVRC_PDU_REGISTER_NOTIFICATION: /* 0x31 */
-#if (AVRC_ADV_CTRL_INCLUDED == TRUE)
+      if (!avrcp_absolute_volume_is_enabled()) {
+        break;
+      }
       status = avrc_bld_register_notifn(p_pkt, p_cmd->reg_notif.event_id,
                                         p_cmd->reg_notif.param);
-#endif
       break;
     case AVRC_PDU_GET_CAPABILITIES:
       status =
diff --git a/system/stack/avrc/avrc_pars_ct.cc b/system/stack/avrc/avrc_pars_ct.cc
index cee8ea4..9dc8a77 100644
--- a/system/stack/avrc/avrc_pars_ct.cc
+++ b/system/stack/avrc/avrc_pars_ct.cc
@@ -48,9 +48,7 @@
   tAVRC_STS status = AVRC_STS_NO_ERROR;
   uint8_t* p;
   uint16_t len;
-#if (AVRC_ADV_CTRL_INCLUDED == TRUE)
   uint8_t eventid = 0;
-#endif
 
   /* Check the vendor data */
   if (p_msg->vendor_len == 0) return AVRC_STS_NO_ERROR;
@@ -88,18 +86,21 @@
 /* case AVRC_PDU_REQUEST_CONTINUATION_RSP: 0x40 */
 /* case AVRC_PDU_ABORT_CONTINUATION_RSP:   0x41 */
 
-#if (AVRC_ADV_CTRL_INCLUDED == TRUE)
     case AVRC_PDU_SET_ABSOLUTE_VOLUME: /* 0x50 */
+      if (!avrcp_absolute_volume_is_enabled()) {
+        break;
+      }
       if (len != 1)
         status = AVRC_STS_INTERNAL_ERR;
       else {
         BE_STREAM_TO_UINT8(p_result->volume.volume, p);
       }
       break;
-#endif /* (AVRC_ADV_CTRL_INCLUDED == TRUE) */
 
     case AVRC_PDU_REGISTER_NOTIFICATION: /* 0x31 */
-#if (AVRC_ADV_CTRL_INCLUDED == TRUE)
+      if (!avrcp_absolute_volume_is_enabled()) {
+        break;
+      }
       if (len < 1) {
         AVRC_TRACE_WARNING(
             "%s: invalid parameter length %d: must be at least 1", __func__,
@@ -124,7 +125,6 @@
       }
       AVRC_TRACE_DEBUG("%s PDU reg notif response:event %x, volume %x",
                        __func__, eventid, p_result->reg_notif.param.volume);
-#endif /* (AVRC_ADV_CTRL_INCLUDED == TRUE) */
       break;
     default:
       status = AVRC_STS_BAD_CMD;
diff --git a/system/stack/btm/ble_scanner_hci_interface.cc b/system/stack/btm/ble_scanner_hci_interface.cc
index 5d3686b..2e521f2 100644
--- a/system/stack/btm/ble_scanner_hci_interface.cc
+++ b/system/stack/btm/ble_scanner_hci_interface.cc
@@ -418,6 +418,11 @@
                                                 uint8_t* data) {
   uint16_t sync_handle;
 
+  if (data_len < 2) {
+    LOG(ERROR) << "Bogus event packet, too short";
+    return;
+  }
+
   STREAM_TO_UINT16(sync_handle, data);
 
   if (BleScannerHciInterface::Get()) {
diff --git a/system/stack/btm/btm_ble.cc b/system/stack/btm/btm_ble.cc
index 0e59a1f..1227f99 100644
--- a/system/stack/btm/btm_ble.cc
+++ b/system/stack/btm/btm_ble.cc
@@ -62,6 +62,22 @@
 #define PROPERTY_BLE_PRIVACY_ENABLED "bluetooth.core.gap.le.privacy.enabled"
 #endif
 
+// Pairing parameters defined in Vol 3, Part H, Chapter 3.5.1 - 3.5.2
+// All present in the exact decimal values, not hex
+// Ex: bluetooth.core.smp.le.ctkd.initiator_key_distribution 15(0x0f)
+static const char kPropertyCtkdAuthRequest[] =
+    "bluetooth.core.smp.le.ctkd.auth_request";
+static const char kPropertyCtkdIoCapabilities[] =
+    "bluetooth.core.smp.le.ctkd.io_capabilities";
+// Vol 3, Part H, Chapter 3.6.1, Figure 3.11
+// |EncKey(1)|IdKey(1)|SignKey(1)|LinkKey(1)|Reserved(4)|
+static const char kPropertyCtkdInitiatorKeyDistribution[] =
+    "bluetooth.core.smp.le.ctkd.initiator_key_distribution";
+static const char kPropertyCtkdResponderKeyDistribution[] =
+    "bluetooth.core.smp.le.ctkd.responder_key_distribution";
+static const char kPropertyCtkdMaxKeySize[] =
+    "bluetooth.core.smp.le.ctkd.max_key_size";
+
 /******************************************************************************/
 /* External Function to be called by other modules                            */
 /******************************************************************************/
@@ -488,9 +504,9 @@
     }
   } else /* there is a security device record exisitng */
   {
-    /* new inquiry result, overwrite device type in security device record */
+    /* new inquiry result, merge device type in security device record */
     if (p_inq_info) {
-      p_dev_rec->device_type = p_inq_info->results.device_type;
+      p_dev_rec->device_type |= p_inq_info->results.device_type;
       if (is_ble_addr_type_known(p_inq_info->results.ble_addr_type))
         p_dev_rec->ble.SetAddressType(p_inq_info->results.ble_addr_type);
       else
@@ -1019,7 +1035,8 @@
  * Returns          void
  *
  ******************************************************************************/
-void btm_ble_rand_enc_complete(uint8_t* p, uint16_t op_code,
+void btm_ble_rand_enc_complete(uint8_t* p, uint16_t evt_len,
+                               uint16_t op_code,
                                tBTM_RAND_ENC_CB* p_enc_cplt_cback) {
   tBTM_RAND_ENC params;
   uint8_t* p_dest = params.param_buf;
@@ -1030,6 +1047,11 @@
 
   /* If there was a callback address for vcs complete, call it */
   if (p_enc_cplt_cback && p) {
+
+    if (evt_len < 1) {
+      goto err_out;
+    }
+
     /* Pass paramters to the callback function */
     STREAM_TO_UINT8(params.status, p); /* command status */
 
@@ -1041,12 +1063,21 @@
       else
         params.param_len = OCTET16_LEN;
 
+      if (evt_len < 1 + params.param_len) {
+        goto err_out;
+      }
+
       /* Fetch return info from HCI event message */
       memcpy(p_dest, p, params.param_len);
     }
     if (p_enc_cplt_cback) /* Call the Encryption complete callback function */
       (*p_enc_cplt_cback)(&params);
   }
+
+  return;
+
+err_out:
+  BTM_TRACE_ERROR("%s malformatted event packet, too short", __func__);
 }
 
 /*******************************************************************************
@@ -1592,13 +1623,30 @@
   BTM_TRACE_ERROR("key size = %d", p_rec->ble.keys.key_size);
   if (use_stk) {
     btsnd_hcic_ble_ltk_req_reply(btm_cb.enc_handle, stk);
-  } else /* calculate LTK using peer device  */
-  {
-    if (p_rec->ble.key_type & BTM_LE_KEY_LENC)
-      btsnd_hcic_ble_ltk_req_reply(btm_cb.enc_handle, p_rec->ble.keys.lltk);
-    else
-      btsnd_hcic_ble_ltk_req_neg_reply(btm_cb.enc_handle);
+    return;
   }
+  /* calculate LTK using peer device  */
+  if (p_rec->ble.key_type & BTM_LE_KEY_LENC) {
+    btsnd_hcic_ble_ltk_req_reply(btm_cb.enc_handle, p_rec->ble.keys.lltk);
+    return;
+  }
+
+  p_rec = btm_find_dev_with_lenc(bda);
+  if (!p_rec) {
+    btsnd_hcic_ble_ltk_req_neg_reply(btm_cb.enc_handle);
+    return;
+  }
+
+  LOG_INFO("Found second sec_dev_rec for device that have LTK");
+  /* This can happen when remote established LE connection using RPA to this
+   * device, but then pair with us using Classing transport while still keeping
+   * LE connection. If remote attempts to encrypt the LE connection, we might
+   * end up here. We will eventually consolidate both entries, this is to avoid
+   * race conditions. */
+
+  LOG_ASSERT(p_rec->ble.key_type & BTM_LE_KEY_LENC);
+  p_cb->key_size = p_rec->ble.keys.key_size;
+  btsnd_hcic_ble_ltk_req_reply(btm_cb.enc_handle, p_rec->ble.keys.lltk);
 }
 
 /*******************************************************************************
@@ -1699,11 +1747,18 @@
                             tBTM_LE_IO_REQ* p_data) {
   uint8_t callback_rc = BTM_SUCCESS;
   BTM_TRACE_DEBUG("%s", __func__);
-  if (btm_cb.api.p_le_callback) {
-    /* the callback function implementation may change the IO capability... */
-    callback_rc = (*btm_cb.api.p_le_callback)(
-        BTM_LE_IO_REQ_EVT, p_dev_rec->bd_addr, (tBTM_LE_EVT_DATA*)p_data);
-  }
+  p_data->io_cap =
+      osi_property_get_int32(kPropertyCtkdIoCapabilities, BTM_IO_CAP_UNKNOWN);
+  p_data->auth_req = osi_property_get_int32(kPropertyCtkdAuthRequest,
+                                            BTM_LE_AUTH_REQ_SC_MITM_BOND);
+  p_data->init_keys = osi_property_get_int32(
+      kPropertyCtkdInitiatorKeyDistribution, SMP_BR_SEC_DEFAULT_KEY);
+  p_data->resp_keys = osi_property_get_int32(
+      kPropertyCtkdResponderKeyDistribution, SMP_BR_SEC_DEFAULT_KEY);
+  p_data->max_key_size =
+      osi_property_get_int32(kPropertyCtkdMaxKeySize, BTM_BLE_MAX_KEY_SIZE);
+  // No OOB data for BR/EDR
+  p_data->oob_data = false;
 
   return callback_rc;
 }
diff --git a/system/stack/btm/btm_ble_batchscan.cc b/system/stack/btm/btm_ble_batchscan.cc
index 2278880..bdce367 100644
--- a/system/stack/btm/btm_ble_batchscan.cc
+++ b/system/stack/btm/btm_ble_batchscan.cc
@@ -60,12 +60,15 @@
 /* VSE callback for batch scan, filter, and tracking events */
 void btm_ble_batchscan_filter_track_adv_vse_cback(uint8_t len,
                                                   const uint8_t* p) {
-  tBTM_BLE_TRACK_ADV_DATA adv_data;
-
+  tBTM_BLE_TRACK_ADV_DATA adv_data{};
   uint8_t sub_event = 0;
   tBTM_BLE_VSC_CB cmn_ble_vsc_cb;
-  if (len == 0) return;
+
+  if (len < 1)
+    goto err_out;
+
   STREAM_TO_UINT8(sub_event, p);
+  len -= 1;
 
   BTM_TRACE_EVENT(
       "btm_ble_batchscan_filter_track_adv_vse_cback called with event:%x",
@@ -78,30 +81,45 @@
 
   if (HCI_VSE_SUBCODE_BLE_TRACKING_SUB_EVT == sub_event &&
       NULL != ble_advtrack_cb.p_track_cback) {
-    if (len < 10) return;
 
-    memset(&adv_data, 0, sizeof(tBTM_BLE_TRACK_ADV_DATA));
     BTM_BleGetVendorCapabilities(&cmn_ble_vsc_cb);
     adv_data.client_if = (uint8_t)ble_advtrack_cb.ref_value;
     if (cmn_ble_vsc_cb.version_supported > BTM_VSC_CHIP_CAPABILITY_L_VERSION) {
+      if (len < 10) {
+        goto err_out;
+      }
+
       STREAM_TO_UINT8(adv_data.filt_index, p);
       STREAM_TO_UINT8(adv_data.advertiser_state, p);
       STREAM_TO_UINT8(adv_data.advertiser_info_present, p);
       STREAM_TO_BDADDR(adv_data.bd_addr, p);
       STREAM_TO_UINT8(adv_data.addr_type, p);
 
+      len -= 10;
+
       /* Extract the adv info details */
       if (ADV_INFO_PRESENT == adv_data.advertiser_info_present) {
-        if (len < 15) return;
+
+        if (len < 5) {
+          goto err_out;
+        }
+
         STREAM_TO_UINT8(adv_data.tx_power, p);
         STREAM_TO_UINT8(adv_data.rssi_value, p);
         STREAM_TO_UINT16(adv_data.time_stamp, p);
-
         STREAM_TO_UINT8(adv_data.adv_pkt_len, p);
+
+        len -= 5;
+
         if (adv_data.adv_pkt_len > 0) {
           adv_data.p_adv_pkt_data =
               static_cast<uint8_t*>(osi_malloc(adv_data.adv_pkt_len));
+          if (adv_data.p_adv_pkt_data == nullptr || \
+            len < adv_data.adv_pkt_len) {
+            goto err_out;
+          }
           memcpy(adv_data.p_adv_pkt_data, p, adv_data.adv_pkt_len);
+          len -= adv_data.adv_pkt_len;
           p += adv_data.adv_pkt_len;
         }
 
@@ -109,11 +127,19 @@
         if (adv_data.scan_rsp_len > 0) {
           adv_data.p_scan_rsp_data =
               static_cast<uint8_t*>(osi_malloc(adv_data.scan_rsp_len));
+
+          if (adv_data.p_scan_rsp_data == nullptr || len < adv_data.scan_rsp_len) {
+            goto err_out;
+          }
           memcpy(adv_data.p_scan_rsp_data, p, adv_data.scan_rsp_len);
         }
       }
     } else {
       /* Based on L-release version */
+      if (len < 9) {
+        goto err_out;
+      }
+
       STREAM_TO_UINT8(adv_data.filt_index, p);
       STREAM_TO_UINT8(adv_data.addr_type, p);
       STREAM_TO_BDADDR(adv_data.bd_addr, p);
@@ -129,8 +155,15 @@
                         to_ble_addr_type(adv_data.addr_type));
 
     ble_advtrack_cb.p_track_cback(&adv_data);
-    return;
   }
+
+  return;
+
+err_out:
+  BTM_TRACE_ERROR("malformatted packet detected");
+
+  osi_free_and_reset((void **) &adv_data.p_adv_pkt_data);
+  osi_free_and_reset((void **) &adv_data.p_scan_rsp_data);
 }
 
 void feat_enable_cb(uint8_t* p, uint16_t len) {
diff --git a/system/stack/btm/btm_ble_gap.cc b/system/stack/btm/btm_ble_gap.cc
index f8a8940..ff28c2c 100644
--- a/system/stack/btm/btm_ble_gap.cc
+++ b/system/stack/btm/btm_ble_gap.cc
@@ -1436,7 +1436,7 @@
  ******************************************************************************/
 void btm_ble_periodic_adv_sync_tx_rcvd(uint8_t* p, uint16_t param_len) {
   LOG_DEBUG("[PAST]: PAST received, param_len=%u", param_len);
-  if (param_len == 0) {
+  if (param_len < 19) {
     LOG_ERROR("%s", "Insufficient data");
     return;
   }
@@ -2431,15 +2431,18 @@
     }
 
     const uint8_t* p_service_data = data.data();
-    uint16_t remaining_data_len = data.size();
     uint8_t service_data_len = 0;
 
     while ((p_service_data = AdvertiseDataParser::GetFieldByType(
                 p_service_data + service_data_len,
-                (remaining_data_len -= service_data_len),
+                data.size() - (p_service_data - data.data()) - service_data_len,
                 BTM_BLE_AD_TYPE_SERVICE_DATA_TYPE, &service_data_len))) {
       uint16_t uuid;
-      STREAM_TO_UINT16(uuid, p_service_data);
+      const uint8_t* p_uuid = p_service_data;
+      if (service_data_len < 2) {
+        continue;
+      }
+      STREAM_TO_UINT16(uuid, p_uuid);
 
       if (uuid == 0x184E /* Audio Stream Control service */ ||
           uuid == 0x184F /* Broadcast Audio Scan service */ ||
@@ -3197,9 +3200,14 @@
  * Returns          void
  *
  ******************************************************************************/
-void btm_ble_read_remote_features_complete(uint8_t* p) {
+void btm_ble_read_remote_features_complete(uint8_t* p, uint8_t length) {
   uint16_t handle;
   uint8_t status;
+
+  if (length < 3) {
+    goto err_out;
+  }
+
   STREAM_TO_UINT8(status, p);
   STREAM_TO_UINT16(handle, p);
   handle = handle & 0x0FFF;  // only 12 bits meaningful
@@ -3214,6 +3222,12 @@
   }
 
   if (status == HCI_SUCCESS) {
+    // BD_FEATURES_LEN additional bytes are read
+    // in acl_set_peer_le_features_from_handle
+    if (length < 3 + BD_FEATURES_LEN) {
+      goto err_out;
+    }
+
     if (!acl_set_peer_le_features_from_handle(handle, p)) {
       LOG_ERROR(
           "Unable to find existing connection after read remote features");
@@ -3222,6 +3236,10 @@
   }
 
   btsnd_hcic_rmt_ver_req(handle);
+  return;
+
+err_out:
+  LOG_ERROR("bogus event packet, too short");
 }
 
 /*******************************************************************************
@@ -3233,11 +3251,11 @@
  * Returns          void
  *
  ******************************************************************************/
-void btm_ble_write_adv_enable_complete(uint8_t* p) {
+void btm_ble_write_adv_enable_complete(uint8_t* p, uint16_t evt_len) {
   tBTM_BLE_INQ_CB* p_cb = &btm_cb.ble_ctr_cb.inq_var;
 
   /* if write adv enable/disbale not succeed */
-  if (*p != HCI_SUCCESS) {
+  if (evt_len < 1 || *p != HCI_SUCCESS) {
     /* toggle back the adv mode */
     p_cb->adv_mode = !p_cb->adv_mode;
   }
diff --git a/system/stack/btm/btm_ble_privacy.cc b/system/stack/btm/btm_ble_privacy.cc
index b2f26fa..6dafc4a 100644
--- a/system/stack/btm/btm_ble_privacy.cc
+++ b/system/stack/btm/btm_ble_privacy.cc
@@ -224,6 +224,12 @@
  ******************************************************************************/
 void btm_ble_clear_resolving_list_complete(uint8_t* p, uint16_t evt_len) {
   uint8_t status = 0;
+
+  if (evt_len < 1) {
+    BTM_TRACE_ERROR("malformatted event packet: containing zero bytes");
+    return;
+  }
+
   STREAM_TO_UINT8(status, p);
 
   BTM_TRACE_DEBUG("%s status=%d", __func__, status);
@@ -268,6 +274,12 @@
  ******************************************************************************/
 void btm_ble_add_resolving_list_entry_complete(uint8_t* p, uint16_t evt_len) {
   uint8_t status;
+
+  if (evt_len < 1) {
+    BTM_TRACE_ERROR("malformatted event packet: containing zero bytes");
+    return;
+  }
+
   STREAM_TO_UINT8(status, p);
 
   BTM_TRACE_DEBUG("%s status = %d", __func__, status);
diff --git a/system/stack/btm/btm_dev.cc b/system/stack/btm/btm_dev.cc
index 4067e3d..10c19d4 100644
--- a/system/stack/btm/btm_dev.cc
+++ b/system/stack/btm/btm_dev.cc
@@ -30,6 +30,7 @@
 #include <string.h>
 
 #include "btm_api.h"
+#include "btm_ble_int.h"
 #include "device/include/controller.h"
 #include "l2c_api.h"
 #include "main/shim/btm_api.h"
@@ -372,6 +373,32 @@
   return NULL;
 }
 
+static bool has_lenc_and_address_is_equal(void* data, void* context) {
+  tBTM_SEC_DEV_REC* p_dev_rec = static_cast<tBTM_SEC_DEV_REC*>(data);
+  if (!(p_dev_rec->ble.key_type & BTM_LE_KEY_LENC)) return true;
+
+  return is_address_equal(data, context);
+}
+
+/*******************************************************************************
+ *
+ * Function         btm_find_dev_with_lenc
+ *
+ * Description      Look for the record in the device database with LTK and
+ *                  specified BD address
+ *
+ * Returns          Pointer to the record or NULL
+ *
+ ******************************************************************************/
+tBTM_SEC_DEV_REC* btm_find_dev_with_lenc(const RawAddress& bd_addr) {
+  if (btm_cb.sec_dev_rec == nullptr) return nullptr;
+
+  list_node_t* n = list_foreach(btm_cb.sec_dev_rec, has_lenc_and_address_is_equal,
+                                (void*)&bd_addr);
+  if (n) return static_cast<tBTM_SEC_DEV_REC*>(list_node(n));
+
+  return NULL;
+}
 /*******************************************************************************
  *
  * Function         btm_consolidate_dev
@@ -429,6 +456,63 @@
   }
 }
 
+/* combine security records of established LE connections after Classic pairing
+ * succeeded. */
+void btm_dev_consolidate_existing_connections(const RawAddress& bd_addr) {
+  tBTM_SEC_DEV_REC* p_target_rec = btm_find_dev(bd_addr);
+  if (!p_target_rec) {
+    LOG_ERROR("No security record for just bonded device!?!?");
+    return;
+  }
+
+  if (p_target_rec->ble_hci_handle != HCI_INVALID_HANDLE) {
+    LOG_INFO("Not consolidating - already have LE connection");
+    return;
+  }
+
+  LOG_INFO("%s", PRIVATE_ADDRESS(bd_addr));
+
+  list_node_t* end = list_end(btm_cb.sec_dev_rec);
+  list_node_t* node = list_begin(btm_cb.sec_dev_rec);
+  while (node != end) {
+    tBTM_SEC_DEV_REC* p_dev_rec =
+        static_cast<tBTM_SEC_DEV_REC*>(list_node(node));
+
+    // we do list_remove in some cases, must grab next before removing
+    node = list_next(node);
+
+    if (p_target_rec == p_dev_rec) continue;
+
+    /* an RPA device entry is a duplicate of the target record */
+    if (btm_ble_addr_resolvable(p_dev_rec->bd_addr, p_target_rec)) {
+      if (p_dev_rec->ble_hci_handle == HCI_INVALID_HANDLE) {
+        LOG_INFO("already disconnected - erasing entry %s",
+                 PRIVATE_ADDRESS(p_dev_rec->bd_addr));
+        wipe_secrets_and_remove(p_dev_rec);
+        continue;
+      }
+
+      LOG_INFO(
+          "Found existing LE connection to just bonded device on %s handle 0x%04x",
+          PRIVATE_ADDRESS(p_dev_rec->bd_addr), p_dev_rec->ble_hci_handle);
+
+      RawAddress ble_conn_addr = p_dev_rec->bd_addr;
+      p_target_rec->ble_hci_handle = p_dev_rec->ble_hci_handle;
+
+      /* remove the old LE record */
+      wipe_secrets_and_remove(p_dev_rec);
+
+      /* To avoid race conditions between central/peripheral starting encryption
+       * at same time, initiate it just from central. */
+      if (L2CA_GetBleConnRole(ble_conn_addr) == HCI_ROLE_CENTRAL) {
+        LOG_INFO("Will encrypt existing connection");
+        BTM_SetEncryption(ble_conn_addr, BT_TRANSPORT_LE, nullptr, nullptr,
+                          BTM_BLE_SEC_ENCRYPT);
+      }
+    }
+  }
+}
+
 /*******************************************************************************
  *
  * Function         btm_find_or_alloc_dev
@@ -566,3 +650,25 @@
   p_dev_rec->bond_type = bond_type;
   return true;
 }
+
+/*******************************************************************************
+ *
+ * Function         btm_get_sec_dev_rec
+ *
+ * Description      Get security device records satisfying given filter
+ *
+ * Returns          A vector containing pointers of security device records
+ *
+ ******************************************************************************/
+std::vector<tBTM_SEC_DEV_REC*> btm_get_sec_dev_rec() {
+  std::vector<tBTM_SEC_DEV_REC*> result{};
+
+  list_node_t* end = list_end(btm_cb.sec_dev_rec);
+  for (list_node_t* node = list_begin(btm_cb.sec_dev_rec); node != end;
+       node = list_next(node)) {
+    tBTM_SEC_DEV_REC* p_dev_rec =
+        static_cast<tBTM_SEC_DEV_REC*>(list_node(node));
+    result.push_back(p_dev_rec);
+  }
+  return result;
+}
diff --git a/system/stack/btm/btm_dev.h b/system/stack/btm/btm_dev.h
index 9da2e89..84d73e7 100644
--- a/system/stack/btm/btm_dev.h
+++ b/system/stack/btm/btm_dev.h
@@ -14,6 +14,8 @@
  * limitations under the License.
  */
 
+#include <functional>
+
 #include "osi/include/log.h"
 #include "stack/btm/btm_ble_int.h"
 #include "stack/btm/security_device_record.h"
@@ -111,8 +113,20 @@
 
 /*******************************************************************************
  *
+ * Function         btm_find_dev_with_lenc
+ *
+ * Description      Look for the record in the device database with LTK and
+ *                  specified BD address
+ *
+ * Returns          Pointer to the record or NULL
+ *
+ ******************************************************************************/
+tBTM_SEC_DEV_REC* btm_find_dev_with_lenc(const RawAddress& bd_addr);
+
+/*******************************************************************************
+ *
  * Function         btm_consolidate_dev
-5**
+ *
  * Description      combine security records if identified as same peer
  *
  * Returns          none
@@ -122,6 +136,20 @@
 
 /*******************************************************************************
  *
+ * Function         btm_consolidate_dev
+ *
+ * Description      When pairing is finished (i.e. on BR/EDR), this function
+ *                  checks if there are existing LE connections to same device
+ *                  that can now be encrypted and used for profiles requiring
+ *                  encryption.
+ *
+ * Returns          none
+ *
+ ******************************************************************************/
+void btm_dev_consolidate_existing_connections(const RawAddress& bd_addr);
+
+/*******************************************************************************
+ *
  * Function         btm_find_or_alloc_dev
  *
  * Description      Look for the record in the device database for the record
@@ -171,3 +199,14 @@
  ******************************************************************************/
 bool btm_set_bond_type_dev(const RawAddress& bd_addr,
                            tBTM_SEC_DEV_REC::tBTM_BOND_TYPE bond_type);
+
+/*******************************************************************************
+ *
+ * Function         btm_get_sec_dev_rec
+ *
+ * Description      Get security device records satisfying given filter
+ *
+ * Returns          A vector containing pointers of security device records
+ *
+ ******************************************************************************/
+std::vector<tBTM_SEC_DEV_REC*> btm_get_sec_dev_rec();
diff --git a/system/stack/btm/btm_devctl.cc b/system/stack/btm/btm_devctl.cc
index b9fb846..30f2dda 100644
--- a/system/stack/btm/btm_devctl.cc
+++ b/system/stack/btm/btm_devctl.cc
@@ -559,18 +559,20 @@
   const uint8_t* bqr_ptr = p;
   uint8_t event_code;
   uint8_t len;
-  STREAM_TO_UINT8(event_code, bqr_ptr);
-  STREAM_TO_UINT8(len, bqr_ptr);
-  // Check if there's at least a subevent code
-  if (len > 1 && evt_len > 1 && event_code == HCI_VENDOR_SPECIFIC_EVT) {
-    uint8_t sub_event_code;
-    STREAM_TO_UINT8(sub_event_code, bqr_ptr);
-    if (sub_event_code == HCI_VSE_SUBCODE_BQR_SUB_EVT) {
-      // Excluding the HCI Event packet header and 1 octet sub-event code
-      int16_t bqr_parameter_length = evt_len - HCIE_PREAMBLE_SIZE - 1;
-      const uint8_t* p_bqr_event = bqr_ptr;
-      // The stream currently points to the BQR sub-event parameters
-      switch (sub_event_code) {
+
+  if (evt_len >= 2) {
+    STREAM_TO_UINT8(event_code, bqr_ptr);
+    STREAM_TO_UINT8(len, bqr_ptr);
+    // Check if there's at least a subevent code
+    if (len > 1 && evt_len >= 2 + 1 && event_code == HCI_VENDOR_SPECIFIC_EVT) {
+      uint8_t sub_event_code;
+      STREAM_TO_UINT8(sub_event_code, bqr_ptr);
+      if (sub_event_code == HCI_VSE_SUBCODE_BQR_SUB_EVT) {
+        // Excluding the HCI Event packet header and 1 octet sub-event code
+        int16_t bqr_parameter_length = evt_len - HCIE_PREAMBLE_SIZE - 1;
+        const uint8_t* p_bqr_event = bqr_ptr;
+        // The stream currently points to the BQR sub-event parameters
+        switch (sub_event_code) {
         case bluetooth::bqr::QUALITY_REPORT_ID_LMP_LL_MESSAGE_TRACE:
           if (bqr_parameter_length >= bluetooth::bqr::kLogDumpParamTotalLen) {
             bluetooth::bqr::DumpLmpLlMessage(bqr_parameter_length, p_bqr_event);
@@ -591,6 +593,7 @@
 
         default:
           LOG_INFO("Unhandled BQR subevent 0x%02hxx", sub_event_code);
+        }
       }
     }
   }
@@ -721,7 +724,7 @@
  * Returns          void
  *
  ******************************************************************************/
-void btm_delete_stored_link_key_complete(uint8_t* p) {
+void btm_delete_stored_link_key_complete(uint8_t* p, uint16_t evt_len) {
   tBTM_CMPL_CB* p_cb = btm_cb.devcb.p_stored_link_key_cmpl_cb;
   tBTM_DELETE_STORED_LINK_KEY_COMPLETE result;
 
@@ -732,6 +735,11 @@
     /* Set the call back event to indicate command complete */
     result.event = BTM_CB_EVT_DELETE_STORED_LINK_KEYS;
 
+    if (evt_len < 3) {
+      LOG(ERROR) << __func__ << "Malformatted event packet, too short";
+      return;
+    }
+
     /* Extract the result fields from the HCI event */
     STREAM_TO_UINT8(result.status, p);
     STREAM_TO_UINT16(result.num_keys, p);
diff --git a/system/stack/btm/btm_iso_impl.h b/system/stack/btm/btm_iso_impl.h
index 358296e..9559e1d 100644
--- a/system/stack/btm/btm_iso_impl.h
+++ b/system/stack/btm/btm_iso_impl.h
@@ -25,6 +25,7 @@
 #include "base/callback.h"
 #include "base/logging.h"
 #include "bind_helpers.h"
+#include "btm_dev.h"
 #include "btm_iso_api.h"
 #include "common/time_util.h"
 #include "device/include/controller.h"
@@ -33,6 +34,7 @@
 #include "osi/include/allocator.h"
 #include "osi/include/log.h"
 #include "stack/include/bt_hdr.h"
+#include "stack/include/btm_log_history.h"
 #include "stack/include/hci_error_code.h"
 #include "stack/include/hcidefs.h"
 
@@ -49,6 +51,8 @@
 static constexpr uint8_t kStateFlagHasDataPathSet = 0x04;
 static constexpr uint8_t kStateFlagIsBroadcast = 0x10;
 
+constexpr char kBtmLogTag[] = "ISO";
+
 struct iso_sync_info {
   uint32_t first_sync_ts;
   uint16_t seq_nb;
@@ -118,6 +122,12 @@
     uint8_t evt_code = IsCigKnown(cig_id) ? kIsoEventCigOnReconfigureCmpl
                                           : kIsoEventCigOnCreateCmpl;
 
+    BTM_LogHistory(
+        kBtmLogTag, RawAddress::kEmpty, "CIG Create complete",
+        base::StringPrintf(
+            "cig_id:0x%02x, status: %s", evt.cig_id,
+            hci_status_code_text((tHCI_STATUS)(evt.status)).c_str()));
+
     if (evt.status == HCI_SUCCESS) {
       LOG_ASSERT(len >= (3) + (cis_cnt * sizeof(uint16_t)))
           << "Invalid CIS count: " << +cis_cnt;
@@ -164,6 +174,11 @@
         cig_params.cis_cfgs.size(), cig_params.cis_cfgs.data(),
         base::BindOnce(&iso_impl::on_set_cig_params, base::Unretained(this),
                        cig_id, cig_params.sdu_itv_mtos));
+
+    BTM_LogHistory(
+        kBtmLogTag, RawAddress::kEmpty, "CIG Create",
+        base::StringPrintf("cig_id:0x%02x, size: %d", cig_id,
+                           static_cast<int>(cig_params.cis_cfgs.size())));
   }
 
   void reconfigure_cig(uint8_t cig_id,
@@ -188,6 +203,12 @@
     STREAM_TO_UINT8(evt.status, stream);
     STREAM_TO_UINT8(evt.cig_id, stream);
 
+    BTM_LogHistory(
+        kBtmLogTag, RawAddress::kEmpty, "CIG Remove complete",
+        base::StringPrintf(
+            "cig_id:0x%02x, status: %s", evt.cig_id,
+            hci_status_code_text((tHCI_STATUS)(evt.status)).c_str()));
+
     if (evt.status == HCI_SUCCESS) {
       auto cis_it = conn_hdl_to_cis_map_.cbegin();
       while (cis_it != conn_hdl_to_cis_map_.cend()) {
@@ -210,6 +231,8 @@
 
     btsnd_hcic_remove_cig(cig_id, base::BindOnce(&iso_impl::on_remove_cig,
                                                  base::Unretained(this)));
+    BTM_LogHistory(kBtmLogTag, RawAddress::kEmpty, "CIG Remove",
+                   base::StringPrintf("cig_id:0x%02x (f:%d)", cig_id, force));
   }
 
   void on_status_establish_cis(
@@ -234,6 +257,14 @@
         evt.cig_id = 0xFF;
         cis->state_flags &= ~kStateFlagIsConnecting;
         cig_callbacks_->OnCisEvent(kIsoEventCisEstablishCmpl, &evt);
+
+        BTM_LogHistory(
+            kBtmLogTag, cis_hdl_to_addr[evt.cis_conn_hdl],
+            "Establish CIS failed ",
+            base::StringPrintf(
+                "handle:0x%04x, status: %s", evt.cis_conn_hdl,
+                hci_status_code_text((tHCI_STATUS)(status)).c_str()));
+        cis_hdl_to_addr.erase(evt.cis_conn_hdl);
       }
     }
   }
@@ -246,6 +277,13 @@
                    (kStateFlagIsConnected | kStateFlagIsConnecting)))
           << "Already connected or connecting";
       cis->state_flags |= kStateFlagIsConnecting;
+
+      tBTM_SEC_DEV_REC* p_rec = btm_find_dev_by_handle(el.acl_conn_handle);
+      if (p_rec) {
+        cis_hdl_to_addr[el.cis_conn_handle] = p_rec->ble.pseudo_addr;
+        BTM_LogHistory(kBtmLogTag, p_rec->ble.pseudo_addr, "Establish CIS",
+                       base::StringPrintf("handle:0x%04x", el.acl_conn_handle));
+      }
     }
     btsnd_hcic_create_cis(conn_params.conn_pairs.size(),
                           conn_params.conn_pairs.data(),
@@ -261,6 +299,11 @@
         << "Not connected";
     bluetooth::legacy::hci::GetInterface().Disconnect(
         cis_handle, static_cast<tHCI_STATUS>(reason));
+
+    BTM_LogHistory(kBtmLogTag, cis_hdl_to_addr[cis_handle], "Disconnect CIS ",
+                   base::StringPrintf(
+                       "handle:0x%04x, reason:%s", cis_handle,
+                       hci_reason_code_text((tHCI_REASON)(reason)).c_str()));
   }
 
   void on_setup_iso_data_path(uint8_t* stream, uint16_t len) {
@@ -278,6 +321,12 @@
       return;
     }
 
+    BTM_LogHistory(kBtmLogTag, cis_hdl_to_addr[conn_handle],
+                   "Setup data path complete",
+                   base::StringPrintf(
+                       "handle:0x%04x, status:%s", conn_handle,
+                       hci_status_code_text((tHCI_STATUS)(status)).c_str()));
+
     if (status == HCI_SUCCESS) iso->state_flags |= kStateFlagHasDataPathSet;
     if (iso->state_flags & kStateFlagIsBroadcast) {
       LOG_ASSERT(big_callbacks_ != nullptr) << "Invalid BIG callbacks";
@@ -306,12 +355,22 @@
         std::move(path_params.codec_conf),
         base::BindOnce(&iso_impl::on_setup_iso_data_path,
                        base::Unretained(this)));
+    BTM_LogHistory(
+        kBtmLogTag, cis_hdl_to_addr[conn_handle], "Setup data path",
+        base::StringPrintf(
+            "handle:0x%04x, dir:0x%02x, path_id:0x%02x, codec_id:0x%02x",
+            conn_handle, path_params.data_path_dir, path_params.data_path_id,
+            path_params.codec_id_format));
   }
 
   void on_remove_iso_data_path(uint8_t* stream, uint16_t len) {
     uint8_t status;
     uint16_t conn_handle;
 
+    if (len < 3) {
+      LOG(WARNING) << __func__ << "Malformatted packet received";
+      return;
+    }
     STREAM_TO_UINT8(status, stream);
     STREAM_TO_UINT16(conn_handle, stream);
 
@@ -323,6 +382,12 @@
       return;
     }
 
+    BTM_LogHistory(kBtmLogTag, cis_hdl_to_addr[conn_handle],
+                   "Remove data path complete",
+                   base::StringPrintf(
+                       "handle:0x%04x, status:%s", conn_handle,
+                       hci_status_code_text((tHCI_STATUS)(status)).c_str()));
+
     if (status == HCI_SUCCESS) iso->state_flags &= ~kStateFlagHasDataPathSet;
 
     if (iso->state_flags & kStateFlagIsBroadcast) {
@@ -345,6 +410,9 @@
         iso_handle, data_path_dir,
         base::BindOnce(&iso_impl::on_remove_iso_data_path,
                        base::Unretained(this)));
+    BTM_LogHistory(kBtmLogTag, cis_hdl_to_addr[iso_handle], "Remove data path",
+                   base::StringPrintf("handle:0x%04x, dir:0x%02x", iso_handle,
+                                      data_path_dir));
   }
 
   void on_iso_link_quality_read(uint8_t* stream, uint16_t len) {
@@ -358,6 +426,13 @@
     uint32_t rxUnreceivedPackets;
     uint32_t duplicatePackets;
 
+    // 1 + 2 + 4 * 7
+#define ISO_LINK_QUALITY_SIZE 31
+    if (len < ISO_LINK_QUALITY_SIZE) {
+      LOG(ERROR) << "Malformated link quality format, len=" << len;
+      return;
+    }
+
     STREAM_TO_UINT8(status, stream);
     if (status != HCI_SUCCESS) {
       LOG(ERROR) << "Failed to Read ISO Link Quality, status: "
@@ -491,6 +566,12 @@
     auto cis = GetCisIfKnown(evt.cis_conn_hdl);
     LOG_ASSERT(cis != nullptr) << "No such cis: " << +evt.cis_conn_hdl;
 
+    BTM_LogHistory(kBtmLogTag, cis_hdl_to_addr[evt.cis_conn_hdl],
+                   "CIS established event",
+                   base::StringPrintf(
+                       "cis_handle:0x%04x status:%s", evt.cis_conn_hdl,
+                       hci_error_code_text((tHCI_STATUS)(evt.status)).c_str()));
+
     cis->sync_info.first_sync_ts = bluetooth::common::time_get_os_boottime_us();
 
     STREAM_TO_UINT24(evt.cig_sync_delay, data);
@@ -508,7 +589,11 @@
     STREAM_TO_UINT16(evt.max_pdu_stom, data);
     STREAM_TO_UINT16(evt.iso_itv, data);
 
-    if (evt.status == HCI_SUCCESS) cis->state_flags |= kStateFlagIsConnected;
+    if (evt.status == HCI_SUCCESS) {
+      cis->state_flags |= kStateFlagIsConnected;
+    } else {
+      cis_hdl_to_addr.erase(evt.cis_conn_hdl);
+    }
 
     cis->state_flags &= ~kStateFlagIsConnecting;
 
@@ -524,6 +609,13 @@
     LOG_ASSERT(cig_callbacks_ != nullptr) << "Invalid CIG callbacks";
 
     LOG_INFO("%s flags: %d", __func__, +cis->state_flags);
+
+    BTM_LogHistory(
+        kBtmLogTag, cis_hdl_to_addr[handle], "CIS disconnected",
+        base::StringPrintf("cis_handle:0x%04x, reason:%s", handle,
+                           hci_error_code_text((tHCI_REASON)(reason)).c_str()));
+    cis_hdl_to_addr.erase(handle);
+
     if (cis->state_flags & kStateFlagIsConnected) {
       cis_disconnected_evt evt = {
           .reason = reason,
@@ -870,6 +962,7 @@
 
   std::map<uint16_t, std::unique_ptr<iso_cis>> conn_hdl_to_cis_map_;
   std::map<uint16_t, std::unique_ptr<iso_bis>> conn_hdl_to_bis_map_;
+  std::map<uint16_t, RawAddress> cis_hdl_to_addr;
 
   std::atomic_uint16_t iso_credits_;
   uint16_t iso_buffer_size_;
diff --git a/system/stack/btm/btm_sec.cc b/system/stack/btm/btm_sec.cc
index 333adbf..df9b6cc 100644
--- a/system/stack/btm/btm_sec.cc
+++ b/system/stack/btm/btm_sec.cc
@@ -44,6 +44,7 @@
 #include "osi/include/compat.h"
 #include "osi/include/log.h"
 #include "osi/include/osi.h"
+#include "osi/include/properties.h"
 #include "stack/btm/btm_dev.h"
 #include "stack/btm/security_device_record.h"
 #include "stack/eatt/eatt.h"
@@ -153,6 +154,14 @@
   }
 }
 
+static bool concurrentPeerAuthIsEnabled() {
+  // Was previously named BTM_DISABLE_CONCURRENT_PEER_AUTH.
+  // Renamed to ENABLED for homogeneity with system properties
+  static const bool sCONCURRENT_PEER_AUTH_IS_ENABLED = osi_property_get_bool(
+      "bluetooth.btm.sec.concurrent_peer_auth.enabled", true);
+  return sCONCURRENT_PEER_AUTH_IS_ENABLED;
+}
+
 void NotifyBondingCanceled(tBTM_STATUS btm_status) {
   if (btm_cb.api.p_bond_cancel_cmpl_callback) {
     btm_cb.api.p_bond_cancel_cmpl_callback(BTM_SUCCESS);
@@ -2046,8 +2055,14 @@
  * Returns          void
  *
  ******************************************************************************/
-void btm_create_conn_cancel_complete(const uint8_t* p) {
+void btm_create_conn_cancel_complete(const uint8_t* p, uint16_t evt_len) {
   uint8_t status;
+
+  if (evt_len < 1 + BD_ADDR_LEN) {
+     BTM_TRACE_ERROR("%s malformatted event packet, too short", __func__);
+     return;
+  }
+
   STREAM_TO_UINT8(status, p);
   RawAddress bd_addr;
   STREAM_TO_BDADDR(bd_addr, p);
@@ -2986,13 +3001,23 @@
  * Returns          void
  *
  ******************************************************************************/
-void btm_read_local_oob_complete(uint8_t* p) {
+void btm_read_local_oob_complete(uint8_t* p, uint16_t evt_len) {
   tBTM_SP_LOC_OOB evt_data;
-  uint8_t status = *p++;
+  uint8_t status;
+  if (evt_len < 1) {
+    goto err_out;
+  }
+
+  STREAM_TO_UINT8(status, p);
 
   BTM_TRACE_EVENT("btm_read_local_oob_complete:%d", status);
   if (status == HCI_SUCCESS) {
     evt_data.status = BTM_SUCCESS;
+
+    if (evt_len < 1 + 32) {
+      goto err_out;
+    }
+
     STREAM_TO_ARRAY16(evt_data.c.data(), p);
     STREAM_TO_ARRAY16(evt_data.r.data(), p);
   } else
@@ -3003,6 +3028,11 @@
     btm_sp_evt_data.loc_oob = evt_data;
     (*btm_cb.api.p_sp_callback)(BTM_SP_LOC_OOB_EVT, &btm_sp_evt_data);
   }
+
+  return;
+
+err_out:
+  BTM_TRACE_ERROR("%s malformatted event packet, too short", __func__);
 }
 
 /*******************************************************************************
@@ -3385,11 +3415,9 @@
                       __func__, p_dev_rec, p_dev_rec->p_callback);
       p_dev_rec->p_callback = NULL;
       l2cu_resubmit_pending_sec_req(&p_dev_rec->bd_addr);
-#ifdef BTM_DISABLE_CONCURRENT_PEER_AUTH
-    } else if (BTM_DISABLE_CONCURRENT_PEER_AUTH &&
+    } else if (!concurrentPeerAuthIsEnabled() &&
                p_dev_rec->sec_state == BTM_SEC_STATE_AUTHENTICATING) {
       p_dev_rec->sec_state = BTM_SEC_STATE_IDLE;
-#endif
     }
     return;
   }
@@ -4014,10 +4042,9 @@
   tBTM_SEC_DEV_REC* p_dev_rec = btm_find_or_alloc_dev(bda);
 
   VLOG(2) << __func__ << " bda: " << bda;
-#if (defined(BTM_DISABLE_CONCURRENT_PEER_AUTH) && \
-     (BTM_DISABLE_CONCURRENT_PEER_AUTH == TRUE))
-  p_dev_rec->sec_state = BTM_SEC_STATE_AUTHENTICATING;
-#endif
+  if (!concurrentPeerAuthIsEnabled()) {
+    p_dev_rec->sec_state = BTM_SEC_STATE_AUTHENTICATING;
+  }
 
   if ((btm_cb.pairing_state == BTM_PAIR_STATE_WAIT_PIN_REQ) &&
       (btm_cb.collision_start_time != 0) &&
@@ -4477,12 +4504,16 @@
 static void btm_sec_wait_and_start_authentication(tBTM_SEC_DEV_REC* p_dev_rec) {
   p_dev_rec->sec_state = BTM_SEC_STATE_AUTHENTICATING;
   auto addr = new RawAddress(p_dev_rec->bd_addr);
+
+  static const int32_t delay_auth =
+      osi_property_get_int32("bluetooth.btm.sec.delay_auth_ms.value", 0);
+
   bt_status_t status = do_in_main_thread_delayed(
       FROM_HERE, base::Bind(&btm_sec_auth_timer_timeout, addr),
 #if BASE_VER < 931007
-      base::TimeDelta::FromMilliseconds(BTM_DELAY_AUTH_MS));
+      base::TimeDelta::FromMilliseconds(delay_auth));
 #else
-      base::Milliseconds(BTM_DELAY_AUTH_MS));
+      base::Milliseconds(delay_auth));
 #endif
   if (status != BT_STATUS_SUCCESS) {
     LOG(ERROR) << __func__
diff --git a/system/stack/btm/btm_sec.h b/system/stack/btm/btm_sec.h
index 8b92d5d..0acb6ca 100644
--- a/system/stack/btm/btm_sec.h
+++ b/system/stack/btm/btm_sec.h
@@ -454,7 +454,7 @@
  * Returns          void
  *
  ******************************************************************************/
-void btm_create_conn_cancel_complete(const uint8_t* p);
+void btm_create_conn_cancel_complete(const uint8_t* p, uint16_t evt_len);
 
 /*******************************************************************************
  *
@@ -582,7 +582,7 @@
  * Returns          void
  *
  ******************************************************************************/
-void btm_read_local_oob_complete(uint8_t* p);
+void btm_read_local_oob_complete(uint8_t* p, uint16_t evt_len);
 
 /*******************************************************************************
  *
diff --git a/system/stack/btu/btu_hcif.cc b/system/stack/btu/btu_hcif.cc
index 3c0f5d0..ec05df2 100644
--- a/system/stack/btu/btu_hcif.cc
+++ b/system/stack/btu/btu_hcif.cc
@@ -93,10 +93,10 @@
 static void btu_hcif_io_cap_request_evt(const uint8_t* p);
 
 static void btu_ble_ll_conn_param_upd_evt(uint8_t* p, uint16_t evt_len);
-static void btu_ble_proc_ltk_req(uint8_t* p);
+static void btu_ble_proc_ltk_req(uint8_t* p, uint16_t evt_len);
 static void btu_hcif_encryption_key_refresh_cmpl_evt(uint8_t* p);
 static void btu_ble_data_length_change_evt(uint8_t* p, uint16_t evt_len);
-static void btu_ble_rc_param_req_evt(uint8_t* p);
+static void btu_ble_rc_param_req_evt(uint8_t* p, uint8_t len);
 
 /**
  * Log HCI event metrics that are not handled in special functions
@@ -340,16 +340,16 @@
           btm_ble_process_adv_pkt(ble_evt_len, p);
           break;
         case HCI_BLE_LL_CONN_PARAM_UPD_EVT:
-          btu_ble_ll_conn_param_upd_evt(p, hci_evt_len);
+          btu_ble_ll_conn_param_upd_evt(p, ble_evt_len);
           break;
         case HCI_BLE_READ_REMOTE_FEAT_CMPL_EVT:
-          btm_ble_read_remote_features_complete(p);
+          btm_ble_read_remote_features_complete(p, ble_evt_len);
           break;
         case HCI_BLE_LTK_REQ_EVT: /* received only at peripheral device */
-          btu_ble_proc_ltk_req(p);
+          btu_ble_proc_ltk_req(p, ble_evt_len);
           break;
         case HCI_BLE_RC_PARAM_REQ_EVT:
-          btu_ble_rc_param_req_evt(p);
+          btu_ble_rc_param_req_evt(p, ble_evt_len);
           break;
         case HCI_BLE_DATA_LENGTH_CHANGE_EVT:
           btu_ble_data_length_change_evt(p, hci_evt_len);
@@ -1185,7 +1185,7 @@
       break;
 
     case HCI_DELETE_STORED_LINK_KEY:
-      btm_delete_stored_link_key_complete(p);
+      btm_delete_stored_link_key_complete(p, evt_len);
       break;
 
     case HCI_READ_LOCAL_NAME:
@@ -1193,11 +1193,11 @@
       break;
 
     case HCI_GET_LINK_QUALITY:
-      btm_read_link_quality_complete(p);
+      btm_read_link_quality_complete(p, evt_len);
       break;
 
     case HCI_READ_RSSI:
-      btm_read_rssi_complete(p);
+      btm_read_rssi_complete(p, evt_len);
       break;
 
     case HCI_READ_FAILED_CONTACT_COUNTER:
@@ -1209,15 +1209,15 @@
       break;
 
     case HCI_READ_TRANSMIT_POWER_LEVEL:
-      btm_read_tx_power_complete(p, false);
+      btm_read_tx_power_complete(p, evt_len, false);
       break;
 
     case HCI_CREATE_CONNECTION_CANCEL:
-      btm_create_conn_cancel_complete(p);
+      btm_create_conn_cancel_complete(p, evt_len);
       break;
 
     case HCI_READ_LOCAL_OOB_DATA:
-      btm_read_local_oob_complete(p);
+      btm_read_local_oob_complete(p, evt_len);
       break;
 
     case HCI_READ_INQ_TX_POWER_LEVEL:
@@ -1226,15 +1226,15 @@
     /* BLE Commands sComplete*/
     case HCI_BLE_RAND:
     case HCI_BLE_ENCRYPT:
-      btm_ble_rand_enc_complete(p, opcode, (tBTM_RAND_ENC_CB*)p_cplt_cback);
+      btm_ble_rand_enc_complete(p, evt_len, opcode, (tBTM_RAND_ENC_CB*)p_cplt_cback);
       break;
 
     case HCI_BLE_READ_ADV_CHNL_TX_POWER:
-      btm_read_tx_power_complete(p, true);
+      btm_read_tx_power_complete(p, evt_len, true);
       break;
 
     case HCI_BLE_WRITE_ADV_ENABLE:
-      btm_ble_write_adv_enable_complete(p);
+      btm_ble_write_adv_enable_complete(p, evt_len);
       break;
 
     case HCI_BLE_CREATE_LL_CONN:
@@ -1645,6 +1645,11 @@
   uint16_t latency;
   uint16_t timeout;
 
+  if (evt_len < 9) {
+     LOG_ERROR("Bogus event packet, too short");
+     return;
+  }
+
   STREAM_TO_UINT8(status, p);
   STREAM_TO_UINT16(handle, p);
   STREAM_TO_UINT16(interval, p);
@@ -1655,10 +1660,22 @@
                                 interval, latency, timeout);
 }
 
-static void btu_ble_proc_ltk_req(uint8_t* p) {
+static void btu_ble_proc_ltk_req(uint8_t* p, uint16_t evt_len) {
   uint16_t ediv, handle;
   uint8_t* pp;
 
+  // following the spec in Core_v5.3/Vol 4/Part E
+  // / 7.7.65.5 LE Long Term Key Request event
+  // A BLE Long Term Key Request event contains:
+  // - 1-byte subevent (already consumed in btu_hcif_process_event)
+  // - 2-byte connection handler
+  // - 8-byte random number
+  // - 2 byte Encrypted_Diversifier
+  if (evt_len < 2 + 8 + 2) {
+    LOG_ERROR("Event packet too short");
+    return;
+  }
+
   STREAM_TO_UINT16(handle, p);
   pp = p + 8;
   STREAM_TO_UINT16(ediv, pp);
@@ -1687,10 +1704,15 @@
 /**********************************************
  * End of BLE Events Handler
  **********************************************/
-static void btu_ble_rc_param_req_evt(uint8_t* p) {
+static void btu_ble_rc_param_req_evt(uint8_t* p, uint8_t len) {
   uint16_t handle;
   uint16_t int_min, int_max, latency, timeout;
 
+  if (len < 10) {
+    LOG(ERROR) << __func__ << "bogus event packet, too short";
+    return;
+  }
+
   STREAM_TO_UINT16(handle, p);
   STREAM_TO_UINT16(int_min, p);
   STREAM_TO_UINT16(int_max, p);
diff --git a/system/stack/eatt/eatt_impl.h b/system/stack/eatt/eatt_impl.h
index 8293bb6..998fc10 100644
--- a/system/stack/eatt/eatt_impl.h
+++ b/system/stack/eatt/eatt_impl.h
@@ -153,7 +153,8 @@
     tL2CAP_LE_CFG_INFO local_coc_cfg = {
         .mtu = eatt_dev->rx_mtu_,
         .mps = eatt_dev->rx_mps_ < max_mps ? eatt_dev->rx_mps_ : max_mps,
-        .credits = L2CAP_LE_CREDIT_DEFAULT};
+        .credits = L2CA_LeCreditDefault(),
+    };
 
     if (!L2CA_ConnectCreditBasedRsp(bda, identifier, lcids, L2CAP_CONN_OK,
                                     &local_coc_cfg))
@@ -584,7 +585,7 @@
     tL2CAP_LE_CFG_INFO local_coc_cfg = {
         .mtu = eatt_dev->rx_mtu_,
         .mps = eatt_dev->rx_mps_,
-        .credits = L2CAP_LE_CREDIT_DEFAULT,
+        .credits = L2CA_LeCreditDefault(),
         .number_of_channels = num_of_channels,
     };
 
@@ -874,7 +875,12 @@
               << " is_eatt_supported = " << int(is_eatt_supported);
     if (!is_eatt_supported) return;
 
-    eatt_device* eatt_dev = add_eatt_device(bd_addr);
+    eatt_device* eatt_dev = this->find_device_by_address(bd_addr);
+    if (!eatt_dev) {
+      LOG(INFO) << __func__ << " Adding device: " << bd_addr
+                << " on supported features callback.";
+      eatt_dev = add_eatt_device(bd_addr);
+    }
 
     if (role != HCI_ROLE_CENTRAL) {
       /* TODO For now do nothing, we could run a timer here and start EATT if
diff --git a/system/stack/gap/gap_conn.cc b/system/stack/gap/gap_conn.cc
index d1b8219..23d9589 100644
--- a/system/stack/gap/gap_conn.cc
+++ b/system/stack/gap/gap_conn.cc
@@ -207,7 +207,7 @@
 
   /* Configure L2CAP COC, if transport is LE */
   if (transport == BT_TRANSPORT_LE) {
-    p_ccb->local_coc_cfg.credits = L2CAP_LE_CREDIT_DEFAULT;
+    p_ccb->local_coc_cfg.credits = L2CA_LeCreditDefault();
     p_ccb->local_coc_cfg.mtu = p_cfg->mtu;
 
     uint16_t max_mps = controller_get_interface()->get_acl_data_size_ble();
diff --git a/system/stack/gatt/connection_manager.cc b/system/stack/gatt/connection_manager.cc
index d832369..b2f48ff 100644
--- a/system/stack/gatt/connection_manager.cc
+++ b/system/stack/gatt/connection_manager.cc
@@ -29,6 +29,7 @@
 
 #include "bind_helpers.h"
 #include "internal_include/bt_trace.h"
+#include "main/shim/dumpsys.h"
 #include "main/shim/le_scanning_manager.h"
 #include "main/shim/shim.h"
 #include "osi/include/alarm.h"
@@ -121,12 +122,11 @@
 
 bool IsTargetedAnnouncement(const uint8_t* p_eir, uint16_t eir_len) {
   const uint8_t* p_service_data = p_eir;
-  uint16_t remaining_data_len = eir_len;
   uint8_t service_data_len = 0;
 
   while ((p_service_data = AdvertiseDataParser::GetFieldByType(
               p_service_data + service_data_len,
-              (remaining_data_len -= service_data_len),
+              eir_len - (p_service_data - p_eir) - service_data_len,
               BTM_BLE_AD_TYPE_SERVICE_DATA_TYPE, &service_data_len))) {
     uint16_t uuid;
     uint8_t announcement_type;
@@ -174,8 +174,6 @@
   LOG_INFO("Found targeted announcement for device %s",
            addr.ToString().c_str());
 
-  BTM_LogHistory(kBtmLogTag, addr, "Found TA from");
-
   if (it->second.is_in_accept_list) {
     LOG_INFO("Device %s is already connecting", addr.ToString().c_str());
     return;
@@ -186,6 +184,8 @@
     return;
   }
 
+  BTM_LogHistory(kBtmLogTag, addr, "Found TA from");
+
   /* Take fist app_id and use it for direct_connect */
   auto app_id = *(it->second.doing_targeted_announcements_conn.begin());
 
@@ -397,6 +397,10 @@
   return true;
 }
 
+bool is_background_connection(const RawAddress& address) {
+  return bgconn_dev.find(address) != bgconn_dev.end();
+}
+
 /** deregister all related background connetion device. */
 void on_app_deregistered(uint8_t app_id) {
   LOG_DEBUG("app_id=%d", static_cast<int>(app_id));
@@ -527,13 +531,15 @@
             address.ToString().c_str());
   auto it = bgconn_dev.find(address);
   if (it == bgconn_dev.end()) {
-    LOG_WARN("Unable to find background connection to remove");
+    LOG_WARN("Unable to find background connection to remove peer:%s",
+             PRIVATE_ADDRESS(address));
     return false;
   }
 
   auto app_it = it->second.doing_direct_conn.find(app_id);
   if (app_it == it->second.doing_direct_conn.end()) {
-    LOG_WARN("Unable to find direct connection to remove");
+    LOG_WARN("Unable to find direct connection to remove peer:%s",
+             PRIVATE_ADDRESS(address));
     return false;
   }
 
diff --git a/system/stack/gatt/connection_manager.h b/system/stack/gatt/connection_manager.h
index 0dff252..0c407c8 100644
--- a/system/stack/gatt/connection_manager.h
+++ b/system/stack/gatt/connection_manager.h
@@ -61,4 +61,6 @@
 extern void on_connection_timed_out(uint8_t app_id, const RawAddress& address);
 extern void on_connection_timed_out_from_shim(const RawAddress& address);
 
+extern bool is_background_connection(const RawAddress& address);
+
 }  // namespace connection_manager
diff --git a/system/stack/gatt/gatt_api.cc b/system/stack/gatt/gatt_api.cc
index 3861016..9a1152b 100644
--- a/system/stack/gatt/gatt_api.cc
+++ b/system/stack/gatt/gatt_api.cc
@@ -21,7 +21,7 @@
  *  this file contains GATT interface functions
  *
  ******************************************************************************/
-#include "gatt_api.h"
+#include "stack/include/gatt_api.h"
 
 #include <base/logging.h>
 #include <base/strings/string_number_conversions.h>
@@ -31,13 +31,16 @@
 
 #include "bt_target.h"
 #include "device/include/controller.h"
-#include "gatt_int.h"
+#include "gd/os/system_properties.h"
 #include "internal_include/stack_config.h"
 #include "l2c_api.h"
 #include "main/shim/dumpsys.h"
 #include "osi/include/allocator.h"
+#include "osi/include/list.h"
 #include "osi/include/log.h"
+#include "stack/btm/btm_dev.h"
 #include "stack/gatt/connection_manager.h"
+#include "stack/gatt/gatt_int.h"
 #include "stack/include/bt_hdr.h"
 #include "types/bluetooth/uuid.h"
 #include "types/bt_transport.h"
@@ -304,7 +307,7 @@
   elem.type = list.asgn_range.is_primary ? GATT_UUID_PRI_SERVICE
                                          : GATT_UUID_SEC_SERVICE;
 
-  if (elem.type == GATT_UUID_PRI_SERVICE) {
+  if (elem.type == GATT_UUID_PRI_SERVICE && gatt_cb.over_br_enabled) {
     Uuid* p_uuid = gatts_get_service_uuid(elem.p_db);
     if (*p_uuid != Uuid::From16Bit(UUID_SERVCLASS_GMCS_SERVER) &&
         *p_uuid != Uuid::From16Bit(UUID_SERVCLASS_GTBS_SERVER)) {
@@ -1479,3 +1482,48 @@
   LOG_DEBUG("status=%d", status);
   return status;
 }
+
+static void gatt_bonded_check_add_address(const RawAddress& bda) {
+  if (!gatt_is_bda_in_the_srv_chg_clt_list(bda)) {
+    gatt_add_a_bonded_dev_for_srv_chg(bda);
+  }
+}
+
+std::optional<bool> OVERRIDE_GATT_LOAD_BONDED = std::nullopt;
+
+static bool gatt_load_bonded_is_enabled() {
+  static const bool sGATT_LOAD_BONDED = bluetooth::os::GetSystemPropertyBool(
+      "bluetooth.gatt.load_bonded.enabled", false);
+  if (OVERRIDE_GATT_LOAD_BONDED.has_value()) {
+    return OVERRIDE_GATT_LOAD_BONDED.value();
+  }
+  return sGATT_LOAD_BONDED;
+}
+
+/* Initialize GATTS list of bonded device service change updates.
+ *
+ * Addresses for bonded devices (publict for BR/EDR or pseudo for BLE) are added
+ * to GATTS service change control list so that updates are sent to bonded
+ * devices on next connect after any handles for GATTS services change due to
+ * services added/removed.
+ */
+void gatt_load_bonded(void) {
+  const bool load_bonded = gatt_load_bonded_is_enabled();
+  LOG_INFO("load bonded: %s", load_bonded ? "True" : "False");
+  if (!load_bonded) {
+    return;
+  }
+  for (tBTM_SEC_DEV_REC* p_dev_rec : btm_get_sec_dev_rec()) {
+    if (p_dev_rec->is_link_key_known()) {
+      LOG_VERBOSE("Add bonded BR/EDR transport %s",
+                  PRIVATE_ADDRESS(p_dev_rec->bd_addr));
+      gatt_bonded_check_add_address(p_dev_rec->bd_addr);
+    }
+    if (p_dev_rec->is_le_link_key_known()) {
+      VLOG(1) << " add bonded BLE " << p_dev_rec->ble.pseudo_addr;
+      LOG_VERBOSE("Add bonded BLE %s",
+                  PRIVATE_ADDRESS(p_dev_rec->ble.pseudo_addr));
+      gatt_bonded_check_add_address(p_dev_rec->ble.pseudo_addr);
+    }
+  }
+}
diff --git a/system/stack/gatt/gatt_auth.cc b/system/stack/gatt/gatt_auth.cc
index 600829a..2329bc5 100644
--- a/system/stack/gatt/gatt_auth.cc
+++ b/system/stack/gatt/gatt_auth.cc
@@ -176,23 +176,26 @@
   tGATT_CLCB* p_clcb = p_tcb->pending_enc_clcb.front();
   p_tcb->pending_enc_clcb.pop_front();
 
-  bool status = false;
-  if (result == BTM_SUCCESS) {
-    if (gatt_get_sec_act(p_tcb) == GATT_SEC_ENCRYPT_MITM) {
-      status = BTM_IsLinkKeyAuthed(*bd_addr, transport);
-    } else {
-      status = true;
+  if (p_clcb != NULL) {
+    bool status = false;
+    if (result == BTM_SUCCESS) {
+      if (gatt_get_sec_act(p_tcb) == GATT_SEC_ENCRYPT_MITM) {
+        status = BTM_IsLinkKeyAuthed(*bd_addr, transport);
+      } else {
+        status = true;
+      }
     }
-  }
 
-  gatt_sec_check_complete(status, p_clcb, p_tcb->sec_act);
+    gatt_sec_check_complete(status, p_clcb, p_tcb->sec_act);
+  }
 
   /* start all other pending operation in queue */
   std::deque<tGATT_CLCB*> new_pending_clcbs;
   while (!p_tcb->pending_enc_clcb.empty()) {
     tGATT_CLCB* p_clcb = p_tcb->pending_enc_clcb.front();
     p_tcb->pending_enc_clcb.pop_front();
-    if (gatt_security_check_start(p_clcb)) new_pending_clcbs.push_back(p_clcb);
+    if (p_clcb != NULL && gatt_security_check_start(p_clcb))
+      new_pending_clcbs.push_back(p_clcb);
   }
   p_tcb->pending_enc_clcb = new_pending_clcbs;
 }
@@ -229,7 +232,7 @@
     while (!p_tcb->pending_enc_clcb.empty()) {
       tGATT_CLCB* p_clcb = p_tcb->pending_enc_clcb.front();
       p_tcb->pending_enc_clcb.pop_front();
-      if (gatt_security_check_start(p_clcb))
+      if (p_clcb != NULL && gatt_security_check_start(p_clcb))
         new_pending_clcbs.push_back(p_clcb);
     }
     p_tcb->pending_enc_clcb = new_pending_clcbs;
diff --git a/system/stack/gatt/gatt_cl.cc b/system/stack/gatt/gatt_cl.cc
index e2f8808..d026633 100644
--- a/system/stack/gatt/gatt_cl.cc
+++ b/system/stack/gatt/gatt_cl.cc
@@ -595,7 +595,8 @@
   VLOG(1) << StringPrintf("value resp op_code = %s len = %d",
                           gatt_dbg_op_name(op_code), len);
 
-  if (len < GATT_PREP_WRITE_RSP_MIN_LEN) {
+  if (len < GATT_PREP_WRITE_RSP_MIN_LEN ||
+      len > GATT_PREP_WRITE_RSP_MIN_LEN + sizeof(value.value)) {
     LOG(ERROR) << "illegal prepare write response length, discard";
     gatt_end_operation(p_clcb, GATT_INVALID_PDU, &value);
     return;
@@ -604,7 +605,7 @@
   STREAM_TO_UINT16(value.handle, p);
   STREAM_TO_UINT16(value.offset, p);
 
-  value.len = len - 4;
+  value.len = len - GATT_PREP_WRITE_RSP_MIN_LEN;
 
   memcpy(value.value, p, value.len);
 
diff --git a/system/stack/gatt/gatt_int.h b/system/stack/gatt/gatt_int.h
index a05c27c..fa173f5 100644
--- a/system/stack/gatt/gatt_int.h
+++ b/system/stack/gatt/gatt_int.h
@@ -259,6 +259,8 @@
 }
 #undef CASE_RETURN_TEXT
 
+// If you change these values make sure to look at b/262219144 before.
+// Some platform rely on this to never changes
 #define GATT_GATT_START_HANDLE 1
 #define GATT_GAP_START_HANDLE 20
 #define GATT_GMCS_START_HANDLE 40
@@ -266,14 +268,6 @@
 #define GATT_TMAS_START_HANDLE 130
 #define GATT_APP_START_HANDLE 134
 
-#ifndef GATT_DEFAULT_START_HANDLE
-#define GATT_DEFAULT_START_HANDLE GATT_GATT_START_HANDLE
-#endif
-
-#ifndef GATT_LAST_HANDLE
-#define GATT_LAST_HANDLE 0xFFFF
-#endif
-
 typedef struct hdl_cfg {
   uint16_t gatt_start_hdl;
   uint16_t gap_start_hdl;
@@ -452,6 +446,7 @@
   tGATT_APPL_INFO cb_info;
 
   tGATT_HDL_CFG hdl_cfg;
+  bool over_br_enabled;
 } tGATT_CB;
 
 #define GATT_SIZE_OF_SRV_CHG_HNDL_RANGE 4
diff --git a/system/stack/gatt/gatt_main.cc b/system/stack/gatt/gatt_main.cc
index 09eccb6..b9d1120 100644
--- a/system/stack/gatt/gatt_main.cc
+++ b/system/stack/gatt/gatt_main.cc
@@ -123,10 +123,10 @@
 
   L2CA_RegisterFixedChannel(L2CAP_ATT_CID, &fixed_reg);
 
-  bool gatt_over_br_is_disabled =
-      osi_property_get_bool("bluetooth.gatt_over_bredr.disabled", false);
+  gatt_cb.over_br_enabled =
+      osi_property_get_bool("bluetooth.gatt.over_bredr.enabled", true);
   /* Now, register with L2CAP for ATT PSM over BR/EDR */
-  if (!gatt_over_br_is_disabled &&
+  if (gatt_cb.over_br_enabled &&
       !L2CA_Register2(BT_PSM_ATT, dyn_info, false /* enable_snoop */, nullptr,
                       GATT_MAX_MTU_SIZE, 0, BTM_SEC_NONE)) {
     LOG(ERROR) << "ATT Dynamic Registration failed";
@@ -909,6 +909,13 @@
 /** This function is called to send a service chnaged indication to the
  * specified bd address */
 void gatt_send_srv_chg_ind(const RawAddress& peer_bda) {
+  static const uint16_t sGATT_DEFAULT_START_HANDLE =
+      (uint16_t)osi_property_get_int32(
+          "bluetooth.gatt.default_start_handle_for_srvc_change.value",
+          GATT_GATT_START_HANDLE);
+  static const uint16_t sGATT_LAST_HANDLE = (uint16_t)osi_property_get_int32(
+      "bluetooth.gatt.last_handle_for_srvc_change.value", 0xFFFF);
+
   VLOG(1) << __func__;
 
   if (!gatt_cb.handle_of_h_r) return;
@@ -921,8 +928,8 @@
 
   uint8_t handle_range[GATT_SIZE_OF_SRV_CHG_HNDL_RANGE];
   uint8_t* p = handle_range;
-  UINT16_TO_STREAM(p, GATT_DEFAULT_START_HANDLE);
-  UINT16_TO_STREAM(p, GATT_LAST_HANDLE);
+  UINT16_TO_STREAM(p, sGATT_DEFAULT_START_HANDLE);
+  UINT16_TO_STREAM(p, sGATT_LAST_HANDLE);
   GATTS_HandleValueIndication(conn_id, gatt_cb.handle_of_h_r,
                               GATT_SIZE_OF_SRV_CHG_HNDL_RANGE, handle_range);
 }
diff --git a/system/stack/gatt/gatt_utils.cc b/system/stack/gatt/gatt_utils.cc
index ec19948..d65b6d6 100644
--- a/system/stack/gatt/gatt_utils.cc
+++ b/system/stack/gatt/gatt_utils.cc
@@ -1163,13 +1163,13 @@
   uint16_t cid = p_clcb->cid;
 
   if (!p_tcb->pending_enc_clcb.empty()) {
-    auto iter = std::find_if(p_tcb->pending_enc_clcb.begin(),
-                             p_tcb->pending_enc_clcb.end(),
-                             [p_clcb](auto& el) { return el == p_clcb; });
-    if (iter != p_tcb->pending_enc_clcb.end()) {
-      p_tcb->pending_enc_clcb.erase(iter);
-      LOG_WARN("Removing clcb (%p) for conn id=0x%04x from pending_enc_clcb",
-               p_clcb, p_clcb->conn_id);
+    for (size_t i = 0; i < p_tcb->pending_enc_clcb.size(); i++) {
+      if (p_tcb->pending_enc_clcb.at(i) == p_clcb) {
+        LOG_WARN("Removing clcb (%p) for conn id=0x%04x from pending_enc_clcb",
+                 p_clcb, p_clcb->conn_id);
+        p_tcb->pending_enc_clcb.at(i) = NULL;
+        break;
+      }
     }
   }
 
@@ -1466,11 +1466,18 @@
   }
 
   if (!connection_manager::direct_connect_remove(gatt_if, bda)) {
-    BTM_AcceptlistRemove(bda);
-    LOG_INFO(
-        "GATT connection manager has no record but removed filter acceptlist "
-        "gatt_if:%hhu peer:%s",
-        gatt_if, PRIVATE_ADDRESS(bda));
+    if (!connection_manager::is_background_connection(bda)) {
+      BTM_AcceptlistRemove(bda);
+      LOG_INFO(
+          "Gatt connection manager has no background record but "
+          " removed filter acceptlist gatt_if:%hhu peer:%s",
+          gatt_if, PRIVATE_ADDRESS(bda));
+    } else {
+      LOG_INFO(
+          "Gatt connection manager maintains a background record"
+          " preserving filter acceptlist gatt_if:%hhu peer:%s",
+          gatt_if, PRIVATE_ADDRESS(bda));
+    }
   }
   return true;
 }
@@ -1708,10 +1715,12 @@
     pseduo_op_code_idx = 0x15; /* just an index to op_code_name */
   }
 
-  if (pseduo_op_code_idx <= GATT_OP_CODE_MAX)
+  #define ARR_SIZE(a) (sizeof(a)/sizeof(a[0]))
+  if (pseduo_op_code_idx < ARR_SIZE(op_code_name))
     return (uint8_t*)op_code_name[pseduo_op_code_idx];
   else
     return (uint8_t*)"Op Code Exceed Max";
+  #undef ARR_SIZE
 }
 
 /** Remove the application interface for the specified background device */
diff --git a/system/stack/hid/hidd_conn.cc b/system/stack/hid/hidd_conn.cc
index da78d40..fc0a245 100644
--- a/system/stack/hid/hidd_conn.cc
+++ b/system/stack/hid/hidd_conn.cc
@@ -27,6 +27,7 @@
 
 #include "bta/include/bta_api.h"
 #include "btif/include/btif_hd.h"
+#include "gd/common/init_flags.h"
 #include "osi/include/allocator.h"
 #include "stack/hid/hidd_int.h"
 #include "stack/include/bt_hdr.h"
@@ -347,7 +348,6 @@
 static void hidd_l2cif_disconnect(uint16_t cid) {
   L2CA_DisconnectReq(cid);
 
-
   HIDD_TRACE_EVENT("%s: cid=%04x", __func__, cid);
 
   tHID_CONN* p_hcon = &hd_cb.device.conn;
@@ -365,6 +365,10 @@
 
     // now disconnect CTRL
     L2CA_DisconnectReq(p_hcon->ctrl_cid);
+    if (bluetooth::common::init_flags::
+            clear_hidd_interrupt_cid_on_disconnect_is_enabled()) {
+      p_hcon->ctrl_cid = 0;
+    }
   }
 
   if ((p_hcon->ctrl_cid == 0) && (p_hcon->intr_cid == 0)) {
diff --git a/system/stack/include/acl_hci_link_interface.h b/system/stack/include/acl_hci_link_interface.h
index f109b56..5ce61b7 100644
--- a/system/stack/include/acl_hci_link_interface.h
+++ b/system/stack/include/acl_hci_link_interface.h
@@ -52,7 +52,7 @@
 void btm_pm_proc_ssr_evt(uint8_t* p, uint16_t evt_len);
 void btm_read_automatic_flush_timeout_complete(uint8_t* p);
 void btm_read_failed_contact_counter_complete(uint8_t* p);
-void btm_read_link_quality_complete(uint8_t* p);
+void btm_read_link_quality_complete(uint8_t* p, uint16_t evt_len);
 void btm_read_remote_ext_features_complete_raw(uint8_t* p, uint8_t evt_len);
 void btm_read_remote_ext_features_complete(uint16_t handle, uint8_t page_num,
                                            uint8_t max_page, uint8_t* features);
@@ -62,8 +62,8 @@
                                       uint8_t lmp_version,
                                       uint16_t manufacturer,
                                       uint16_t lmp_subversion);
-void btm_read_rssi_complete(uint8_t* p);
-void btm_read_tx_power_complete(uint8_t* p, bool is_ble);
+void btm_read_rssi_complete(uint8_t* p, uint16_t evt_len);
+void btm_read_tx_power_complete(uint8_t* p, uint16_t evt_len, bool is_ble);
 
 void acl_rcv_acl_data(BT_HDR* p_msg);
 void acl_link_segments_xmitted(BT_HDR* p_msg);
diff --git a/system/stack/include/avrc_api.h b/system/stack/include/avrc_api.h
index 877dafe..10a34d1 100644
--- a/system/stack/include/avrc_api.h
+++ b/system/stack/include/avrc_api.h
@@ -236,6 +236,16 @@
 /*****************************************************************************
  *  external function declarations
  ****************************************************************************/
+/******************************************************************************
+ *
+ * Function         avrcp_absolute_volume_is_enabled
+ *
+ * Description      Check if config support advance control (absolute volume)
+ *
+ * Returns          return true if absolute_volume is enabled
+ *
+ *****************************************************************************/
+bool avrcp_absolute_volume_is_enabled();
 
 /******************************************************************************
  *
diff --git a/system/stack/include/ble_hci_link_interface.h b/system/stack/include/ble_hci_link_interface.h
index d4f6d35..ab60b48 100644
--- a/system/stack/include/ble_hci_link_interface.h
+++ b/system/stack/include/ble_hci_link_interface.h
@@ -27,14 +27,15 @@
 void btm_ble_process_adv_pkt(uint8_t len, const uint8_t* p);
 void btm_ble_process_ext_adv_pkt(uint8_t len, const uint8_t* p);
 void btm_ble_process_phy_update_pkt(uint8_t len, uint8_t* p);
-void btm_ble_read_remote_features_complete(uint8_t* p);
+void btm_ble_read_remote_features_complete(uint8_t* p, uint8_t length);
 void btm_le_on_advertising_set_terminated(uint8_t* p, uint16_t length);
-extern void btm_ble_write_adv_enable_complete(uint8_t* p);
+extern void btm_ble_write_adv_enable_complete(uint8_t* p, uint16_t evt_len);
 extern void btm_ble_create_ll_conn_complete(tHCI_STATUS status);
 extern void btm_ble_ltk_request(uint16_t handle, uint8_t rand[8],
                                 uint16_t ediv);
 extern void btm_ble_test_command_complete(uint8_t* p);
-extern void btm_ble_rand_enc_complete(uint8_t* p, uint16_t op_code,
+extern void btm_ble_rand_enc_complete(uint8_t* p, uint16_t evt_len,
+                                      uint16_t op_code,
                                       tBTM_RAND_ENC_CB* p_enc_cplt_cback);
 extern bool btm_identity_addr_to_random_pseudo(RawAddress* bd_addr,
                                                tBLE_ADDR_TYPE* p_addr_type,
diff --git a/system/stack/include/dev_hci_link_interface.h b/system/stack/include/dev_hci_link_interface.h
index 9a6a767..b6e3d71 100644
--- a/system/stack/include/dev_hci_link_interface.h
+++ b/system/stack/include/dev_hci_link_interface.h
@@ -23,7 +23,7 @@
 
 #include "types/raw_address.h"
 
-extern void btm_delete_stored_link_key_complete(uint8_t* p);
+extern void btm_delete_stored_link_key_complete(uint8_t* p, uint16_t evt_len);
 extern void btm_vendor_specific_evt(const uint8_t* p, uint8_t evt_len);
 extern void btm_vsc_complete(uint8_t* p, uint16_t cc_opcode, uint16_t evt_len,
                              tBTM_VSC_CMPL_CB* p_vsc_cplt_cback);
diff --git a/system/stack/include/gatt_api.h b/system/stack/include/gatt_api.h
index 05d4115..eb37910 100644
--- a/system/stack/include/gatt_api.h
+++ b/system/stack/include/gatt_api.h
@@ -1190,4 +1190,7 @@
  * true, as there is no need to wipe controller acceptlist in this case. */
 extern void gatt_reset_bgdev_list(bool after_reset);
 
+// Initialize GATTS list of bonded device service change updates.
+extern void gatt_load_bonded(void);
+
 #endif /* GATT_API_H */
diff --git a/system/stack/include/l2c_api.h b/system/stack/include/l2c_api.h
index d62da3c..c4e452f 100644
--- a/system/stack/include/l2c_api.h
+++ b/system/stack/include/l2c_api.h
@@ -180,14 +180,11 @@
 
 // This is initial amout of credits we send, and amount to which we increase
 // credits once they fall below threshold
-constexpr uint16_t L2CAP_LE_CREDIT_DEFAULT = 0xffff;
+uint16_t L2CA_LeCreditDefault();
 
 // If credit count on remote fall below this value, we send back credits to
 // reach default value.
-constexpr uint16_t L2CAP_LE_CREDIT_THRESHOLD = 0x0040;
-
-static_assert(L2CAP_LE_CREDIT_THRESHOLD < L2CAP_LE_CREDIT_DEFAULT,
-              "Threshold must be smaller than default credits");
+uint16_t L2CA_LeCreditThreshold();
 
 // Max number of CIDs in the L2CAP CREDIT BASED CONNECTION REQUEST
 constexpr uint16_t L2CAP_CREDIT_BASED_MAX_CIDS = 5;
@@ -199,7 +196,7 @@
   uint16_t result; /* Only used in confirm messages */
   uint16_t mtu = 100;
   uint16_t mps = 100;
-  uint16_t credits = L2CAP_LE_CREDIT_DEFAULT;
+  uint16_t credits = L2CA_LeCreditDefault();
   uint8_t number_of_channels = L2CAP_CREDIT_BASED_MAX_CIDS;
 };
 
diff --git a/system/stack/include/sec_hci_link_interface.h b/system/stack/include/sec_hci_link_interface.h
index b5dda7f..dce3c47 100644
--- a/system/stack/include/sec_hci_link_interface.h
+++ b/system/stack/include/sec_hci_link_interface.h
@@ -26,12 +26,12 @@
 // This header contains functions for HCIF-Security Management to invoke
 //
 
-void btm_create_conn_cancel_complete(const uint8_t* p);
+void btm_create_conn_cancel_complete(const uint8_t* p, uint16_t evt_len);
 void btm_io_capabilities_req(const RawAddress& p);
 void btm_io_capabilities_rsp(const uint8_t* p);
 void btm_proc_sp_req_evt(tBTM_SP_EVT event, const uint8_t* p);
 void btm_read_inq_tx_power_complete(uint8_t* p);
-void btm_read_local_oob_complete(uint8_t* p);
+void btm_read_local_oob_complete(uint8_t* p, uint16_t evt_len);
 void btm_rem_oob_req(const uint8_t* p);
 void btm_sec_auth_complete(uint16_t handle, tHCI_STATUS status);
 void btm_sec_disconnected(uint16_t handle, tHCI_STATUS reason, std::string);
diff --git a/system/stack/include/smp_api.h b/system/stack/include/smp_api.h
index 8124211..cf23914 100644
--- a/system/stack/include/smp_api.h
+++ b/system/stack/include/smp_api.h
@@ -187,8 +187,11 @@
  * Description      This function is called to generate a public key to be
  *                  passed to a remote device via an Out of Band transport
  *
+ * Returns          true if the request is successfully sent and executed by the
+ *                  state machine, false otherwise
+ *
  ******************************************************************************/
-extern void SMP_CrLocScOobData();
+extern bool SMP_CrLocScOobData();
 
 /*******************************************************************************
  *
diff --git a/system/stack/include/stack_metrics_logging.h b/system/stack/include/stack_metrics_logging.h
index 158c98f..58d869f 100644
--- a/system/stack/include/stack_metrics_logging.h
+++ b/system/stack/include/stack_metrics_logging.h
@@ -34,9 +34,9 @@
     uint32_t hci_cmd, uint16_t hci_event, uint16_t hci_ble_event,
     uint16_t cmd_status, uint16_t reason_code);
 
-void log_smp_pairing_event(const RawAddress& address, uint8_t smp_cmd,
+void log_smp_pairing_event(const RawAddress& address, uint16_t smp_cmd,
                            android::bluetooth::DirectionEnum direction,
-                           uint8_t smp_fail_reason);
+                           uint16_t smp_fail_reason);
 
 void log_sdp_attribute(const RawAddress& address, uint16_t protocol_uuid,
                        uint16_t attribute_id, size_t attribute_size,
diff --git a/system/stack/l2cap/l2c_api.cc b/system/stack/l2cap/l2c_api.cc
index 5464cb4..2a7efd8 100644
--- a/system/stack/l2cap/l2c_api.cc
+++ b/system/stack/l2cap/l2c_api.cc
@@ -34,6 +34,7 @@
 
 #include "device/include/controller.h"  // TODO Remove
 #include "gd/common/init_flags.h"
+#include "gd/os/system_properties.h"
 #include "main/shim/shim.h"
 #include "osi/include/allocator.h"
 #include "osi/include/log.h"
@@ -65,6 +66,29 @@
   return ret;
 }
 
+uint16_t L2CA_LeCreditDefault() {
+  static const uint16_t sL2CAP_LE_CREDIT_DEFAULT =
+      bluetooth::os::GetSystemPropertyUint32Base(
+          "bluetooth.l2cap.le.credit_default.value", 0xffff);
+  return sL2CAP_LE_CREDIT_DEFAULT;
+}
+
+uint16_t L2CA_LeCreditThreshold() {
+  static const uint16_t sL2CAP_LE_CREDIT_THRESHOLD =
+      bluetooth::os::GetSystemPropertyUint32Base(
+          "bluetooth.l2cap.le.credit_threshold.value", 0x0040);
+  return sL2CAP_LE_CREDIT_THRESHOLD;
+}
+
+static bool check_l2cap_credit() {
+  CHECK(L2CA_LeCreditThreshold() < L2CA_LeCreditDefault())
+      << "Threshold must be smaller than default credits";
+  return true;
+}
+
+// Replace static assert with startup assert depending of the config
+static const bool enforce_assert = check_l2cap_credit();
+
 /*******************************************************************************
  *
  * Function         L2CA_Register
diff --git a/system/stack/l2cap/l2c_ble.cc b/system/stack/l2cap/l2c_ble.cc
index 607950c..3d66846 100755
--- a/system/stack/l2cap/l2c_ble.cc
+++ b/system/stack/l2cap/l2c_ble.cc
@@ -989,7 +989,8 @@
       p_ccb->local_conn_cfg.mtu = L2CAP_SDU_LENGTH_LE_MAX;
       p_ccb->local_conn_cfg.mps =
           controller_get_interface()->get_acl_data_size_ble();
-      p_ccb->local_conn_cfg.credits = L2CAP_LE_CREDIT_DEFAULT,
+      p_ccb->local_conn_cfg.credits = L2CA_LeCreditDefault();
+      p_ccb->remote_credit_count = L2CA_LeCreditDefault();
 
       p_ccb->peer_conn_cfg.mtu = mtu;
       p_ccb->peer_conn_cfg.mps = mps;
diff --git a/system/stack/l2cap/l2c_fcr.cc b/system/stack/l2cap/l2c_fcr.cc
index 9c76005..d6d06d9 100644
--- a/system/stack/l2cap/l2c_fcr.cc
+++ b/system/stack/l2cap/l2c_fcr.cc
@@ -734,6 +734,10 @@
 
   } else {
     p_data = p_ccb->ble_sdu;
+    if (p_data == NULL) {
+      osi_free(p_buf);
+      return;
+    }
     if (p_buf->len > (p_ccb->ble_sdu_length - p_data->len)) {
       L2CAP_TRACE_ERROR("%s: buffer length=%d too big. max=%d. Dropped",
                         __func__, p_data->len,
diff --git a/system/stack/l2cap/l2c_main.cc b/system/stack/l2cap/l2c_main.cc
index 06714cb..1d412f7 100644
--- a/system/stack/l2cap/l2c_main.cc
+++ b/system/stack/l2cap/l2c_main.cc
@@ -221,9 +221,9 @@
     --p_ccb->remote_credit_count;
 
     /* If the credits left on the remote device are getting low, send some */
-    if (p_ccb->remote_credit_count <= L2CAP_LE_CREDIT_THRESHOLD) {
-      uint16_t credits = L2CAP_LE_CREDIT_DEFAULT - p_ccb->remote_credit_count;
-      p_ccb->remote_credit_count = L2CAP_LE_CREDIT_DEFAULT;
+    if (p_ccb->remote_credit_count <= L2CA_LeCreditThreshold()) {
+      uint16_t credits = L2CA_LeCreditDefault() - p_ccb->remote_credit_count;
+      p_ccb->remote_credit_count = L2CA_LeCreditDefault();
 
       /* Return back credits */
       l2c_csm_execute(p_ccb, L2CEVT_L2CA_SEND_FLOW_CONTROL_CREDIT, &credits);
diff --git a/system/stack/metrics/stack_metrics_logging.cc b/system/stack/metrics/stack_metrics_logging.cc
index 4e23bd2..d69d54e 100644
--- a/system/stack/metrics/stack_metrics_logging.cc
+++ b/system/stack/metrics/stack_metrics_logging.cc
@@ -42,9 +42,9 @@
       hci_ble_event, cmd_status, reason_code);
 }
 
-void log_smp_pairing_event(const RawAddress& address, uint8_t smp_cmd,
+void log_smp_pairing_event(const RawAddress& address, uint16_t smp_cmd,
                            android::bluetooth::DirectionEnum direction,
-                           uint8_t smp_fail_reason) {
+                           uint16_t smp_fail_reason) {
   bluetooth::shim::LogMetricSmpPairingEvent(address, smp_cmd, direction,
                                             smp_fail_reason);
 }
diff --git a/system/stack/sdp/sdp_db.cc b/system/stack/sdp/sdp_db.cc
index 297b312..acef4a5 100644
--- a/system/stack/sdp/sdp_db.cc
+++ b/system/stack/sdp/sdp_db.cc
@@ -355,6 +355,11 @@
   uint16_t xx, yy, zz;
   tSDP_RECORD* p_rec = &sdp_cb.server_db.record[0];
 
+  if (p_val == nullptr) {
+    SDP_TRACE_WARNING("Trying to add attribute with p_val == nullptr, skipped");
+    return (false);
+  }
+
   if (sdp_cb.trace_level >= BT_TRACE_LEVEL_DEBUG) {
     if ((attr_type == UINT_DESC_TYPE) ||
         (attr_type == TWO_COMP_INT_DESC_TYPE) ||
@@ -402,6 +407,13 @@
     if (p_rec->record_handle == handle) {
       tSDP_ATTRIBUTE* p_attr = &p_rec->attribute[0];
 
+      // error out early, no need to look up
+      if (p_rec->free_pad_ptr >= SDP_MAX_PAD_LEN) {
+        SDP_TRACE_ERROR("the free pad for SDP record with handle %d is "
+                        "full, skip adding the attribute", handle);
+        return (false);
+      }
+
       /* Found the record. Now, see if the attribute already exists */
       for (xx = 0; xx < p_rec->num_attributes; xx++, p_attr++) {
         /* The attribute exists. replace it */
@@ -440,15 +452,13 @@
           attr_len = 0;
       }
 
-      if ((attr_len > 0) && (p_val != 0)) {
+      if (attr_len > 0) {
         p_attr->len = attr_len;
         memcpy(&p_rec->attr_pad[p_rec->free_pad_ptr], p_val, (size_t)attr_len);
         p_attr->value_ptr = &p_rec->attr_pad[p_rec->free_pad_ptr];
         p_rec->free_pad_ptr += attr_len;
-      } else if ((attr_len == 0 &&
-                  p_attr->len !=
-                      0) || /* if truncate to 0 length, simply don't add */
-                 p_val == 0) {
+      } else if (attr_len == 0 && p_attr->len != 0) {
+        /* if truncate to 0 length, simply don't add */
         SDP_TRACE_ERROR(
             "SDP_AddAttribute fail, length exceed maximum: ID %d: attr_len:%d ",
             attr_id, attr_len);
diff --git a/system/stack/sdp/sdp_discovery.cc b/system/stack/sdp/sdp_discovery.cc
index 22d6e7c..9988c54 100644
--- a/system/stack/sdp/sdp_discovery.cc
+++ b/system/stack/sdp/sdp_discovery.cc
@@ -337,7 +337,7 @@
   uint8_t* p_end;
   uint8_t type;
 
-  if (p_ccb->p_db->raw_data) {
+  if (p_ccb->p_db && p_ccb->p_db->raw_data) {
     cpy_len = p_ccb->p_db->raw_size - p_ccb->p_db->raw_used;
     list_len = p_ccb->list_len;
     p = &p_ccb->rsp_list[0];
diff --git a/system/stack/smp/smp_act.cc b/system/stack/smp/smp_act.cc
index dad83c0..3c8218e 100644
--- a/system/stack/smp/smp_act.cc
+++ b/system/stack/smp/smp_act.cc
@@ -1042,7 +1042,7 @@
   tBTM_LE_KEY_VALUE pid_key = {
       .pid_key = {},
   };
-  ;
+
   STREAM_TO_UINT8(pid_key.pid_key.identity_addr_type, p);
   STREAM_TO_BDADDR(pid_key.pid_key.identity_addr, p);
   pid_key.pid_key.irk = p_cb->tk;
diff --git a/system/stack/smp/smp_api.cc b/system/stack/smp/smp_api.cc
index 05e68db..9355168 100644
--- a/system/stack/smp/smp_api.cc
+++ b/system/stack/smp/smp_api.cc
@@ -567,10 +567,13 @@
  * Description      This function is called to generate a public key to be
  *                  passed to a remote device via Out of Band transport.
  *
+ * Returns          true if the request is successfully sent and executed by the
+ *                  state machine, false otherwise
+ *
  ******************************************************************************/
-void SMP_CrLocScOobData() {
+bool SMP_CrLocScOobData() {
   tSMP_INT_DATA smp_int_data;
-  smp_sm_event(&smp_cb, SMP_CR_LOC_SC_OOB_DATA_EVT, &smp_int_data);
+  return smp_sm_event(&smp_cb, SMP_CR_LOC_SC_OOB_DATA_EVT, &smp_int_data);
 }
 
 /*******************************************************************************
diff --git a/system/stack/smp/smp_int.h b/system/stack/smp/smp_int.h
index c4ddf3e..5e73180 100644
--- a/system/stack/smp/smp_int.h
+++ b/system/stack/smp/smp_int.h
@@ -32,6 +32,15 @@
 #include "stack/include/bt_octets.h"
 #include "types/raw_address.h"
 
+typedef enum : uint16_t {
+  SMP_METRIC_COMMAND_LE_FLAG = 0x0000,
+  SMP_METRIC_COMMAND_BR_FLAG = 0x0100,
+  SMP_METRIC_COMMAND_LE_PAIRING_CMPL = 0xFF00,
+  SMP_METRIC_COMMAND_BR_PAIRING_CMPL = 0xFF01,
+} tSMP_METRIC_COMMAND;
+
+constexpr uint16_t SMP_METRIC_STATUS_INTERNAL_FLAG = 0x0100;
+
 typedef enum : uint8_t {
   /* Legacy mode */
   SMP_MODEL_ENCRYPTION_ONLY = 0, /* Just Works model */
@@ -312,7 +321,7 @@
 extern void smp_init(void);
 
 /* smp main */
-extern void smp_sm_event(tSMP_CB* p_cb, tSMP_EVENT event,
+extern bool smp_sm_event(tSMP_CB* p_cb, tSMP_EVENT event,
                          tSMP_INT_DATA* p_data);
 
 extern tSMP_STATE smp_get_state(void);
@@ -414,7 +423,8 @@
 
 /* smp_util.cc */
 extern void smp_log_metrics(const RawAddress& bd_addr, bool is_outgoing,
-                            const uint8_t* p_buf, size_t buf_len);
+                            const uint8_t* p_buf, size_t buf_len,
+                            bool is_over_br);
 extern bool smp_send_cmd(uint8_t cmd_code, tSMP_CB* p_cb);
 extern void smp_cb_cleanup(tSMP_CB* p_cb);
 extern void smp_reset_control_value(tSMP_CB* p_cb);
diff --git a/system/stack/smp/smp_l2c.cc b/system/stack/smp/smp_l2c.cc
index 6a13e8a..264cdc3 100644
--- a/system/stack/smp/smp_l2c.cc
+++ b/system/stack/smp/smp_l2c.cc
@@ -195,7 +195,8 @@
                        smp_rsp_timeout, NULL);
 
     smp_log_metrics(p_cb->pairing_bda, false /* incoming */,
-                    p_buf->data + p_buf->offset, p_buf->len);
+                    p_buf->data + p_buf->offset, p_buf->len,
+                    false /* is_over_br */);
 
     if (cmd == SMP_OPCODE_CONFIRM) {
       SMP_TRACE_DEBUG(
@@ -334,7 +335,8 @@
                        smp_rsp_timeout, NULL);
 
     smp_log_metrics(p_cb->pairing_bda, false /* incoming */,
-                    p_buf->data + p_buf->offset, p_buf->len);
+                    p_buf->data + p_buf->offset, p_buf->len,
+                    true /* is_over_br */);
 
     p_cb->rcvd_cmd_code = cmd;
     p_cb->rcvd_cmd_len = (uint8_t)p_buf->len;
diff --git a/system/stack/smp/smp_main.cc b/system/stack/smp/smp_main.cc
index 3953949..ab30510 100644
--- a/system/stack/smp/smp_main.cc
+++ b/system/stack/smp/smp_main.cc
@@ -973,17 +973,19 @@
  *              not NULL state, adjust the new state to the returned state. If
  *              (api_evt != MAX), call callback function.
  *
- * Returns      void.
+ * Returns      true if the event is executed and a state transition can be
+ *              expected, false if the event is ignored, state is invalid, or
+ *              the role is invalid for the control block.
  *
  ******************************************************************************/
-void smp_sm_event(tSMP_CB* p_cb, tSMP_EVENT event, tSMP_INT_DATA* p_data) {
+bool smp_sm_event(tSMP_CB* p_cb, tSMP_EVENT event, tSMP_INT_DATA* p_data) {
   uint8_t curr_state = p_cb->state;
   tSMP_SM_TBL state_table;
   uint8_t action, entry, i;
 
   if (p_cb->role >= 2) {
     SMP_TRACE_DEBUG("Invalid role: %d", p_cb->role);
-    return;
+    return false;
   }
 
   tSMP_ENTRY_TBL entry_table = smp_entry_table[p_cb->role];
@@ -991,7 +993,7 @@
   SMP_TRACE_EVENT("main smp_sm_event");
   if (curr_state >= SMP_STATE_MAX) {
     SMP_TRACE_DEBUG("Invalid state: %d", curr_state);
-    return;
+    return false;
   }
 
   SMP_TRACE_DEBUG("SMP Role: %s State: [%s (%d)], Event: [%s (%d)]",
@@ -1014,7 +1016,7 @@
     SMP_TRACE_DEBUG("Ignore event [%s (%d)] in state [%s (%d)]",
                     smp_get_event_name(event), event,
                     smp_get_state_name(curr_state), curr_state);
-    return;
+    return false;
   }
 
   /* Get possible next state from state table. */
@@ -1035,6 +1037,7 @@
     }
   }
   SMP_TRACE_DEBUG("result state = %s", smp_get_state_name(p_cb->state));
+  return true;
 }
 
 /*******************************************************************************
diff --git a/system/stack/smp/smp_utils.cc b/system/stack/smp/smp_utils.cc
index 22fcb71..f2aba5e 100644
--- a/system/stack/smp/smp_utils.cc
+++ b/system/stack/smp/smp_utils.cc
@@ -41,6 +41,8 @@
 #include "stack/include/stack_metrics_logging.h"
 #include "types/raw_address.h"
 
+void btm_dev_consolidate_existing_connections(const RawAddress& bd_addr);
+
 #define SMP_PAIRING_REQ_SIZE 7
 #define SMP_CONFIRM_CMD_SIZE (OCTET16_LEN + 1)
 #define SMP_RAND_CMD_SIZE (OCTET16_LEN + 1)
@@ -314,22 +316,26 @@
  * @param buf_len length available to read for p_buf
  */
 void smp_log_metrics(const RawAddress& bd_addr, bool is_outgoing,
-                     const uint8_t* p_buf, size_t buf_len) {
+                     const uint8_t* p_buf, size_t buf_len, bool is_over_br) {
   if (buf_len < 1) {
     LOG(WARNING) << __func__ << ": buffer is too small, size is " << buf_len;
     return;
   }
-  uint8_t cmd;
-  STREAM_TO_UINT8(cmd, p_buf);
+  uint8_t raw_cmd;
+  STREAM_TO_UINT8(raw_cmd, p_buf);
   buf_len--;
   uint8_t failure_reason = 0;
-  if (cmd == SMP_OPCODE_PAIRING_FAILED && buf_len >= 1) {
+  if (raw_cmd == SMP_OPCODE_PAIRING_FAILED && buf_len >= 1) {
     STREAM_TO_UINT8(failure_reason, p_buf);
   }
+  uint16_t metric_cmd =
+      is_over_br ? SMP_METRIC_COMMAND_BR_FLAG : SMP_METRIC_COMMAND_LE_FLAG;
+  metric_cmd |= static_cast<uint16_t>(raw_cmd);
   android::bluetooth::DirectionEnum direction =
       is_outgoing ? android::bluetooth::DirectionEnum::DIRECTION_OUTGOING
                   : android::bluetooth::DirectionEnum::DIRECTION_INCOMING;
-  log_smp_pairing_event(bd_addr, cmd, direction, failure_reason);
+  log_smp_pairing_event(bd_addr, metric_cmd, direction,
+                        static_cast<uint16_t>(failure_reason));
 }
 
 /*******************************************************************************
@@ -350,7 +356,8 @@
   SMP_TRACE_EVENT("%s", __func__);
 
   smp_log_metrics(rem_bda, true /* outgoing */,
-                  p_toL2CAP->data + p_toL2CAP->offset, p_toL2CAP->len);
+                  p_toL2CAP->data + p_toL2CAP->offset, p_toL2CAP->len,
+                  smp_cb.smp_over_br /* is_over_br */);
 
   l2cap_ret = L2CA_SendFixedChnlData(fixed_cid, rem_bda, p_toL2CAP);
   if (l2cap_ret == L2CAP_DW_FAILED) {
@@ -978,6 +985,27 @@
                            smp_status_text(evt_data.cmplt.reason).c_str()));
   }
 
+  // Log pairing complete event
+  {
+    auto direction =
+        p_cb->flags & SMP_PAIR_FLAGS_WE_STARTED_DD
+            ? android::bluetooth::DirectionEnum::DIRECTION_OUTGOING
+            : android::bluetooth::DirectionEnum::DIRECTION_INCOMING;
+    uint16_t metric_cmd = p_cb->smp_over_br
+                              ? SMP_METRIC_COMMAND_BR_PAIRING_CMPL
+                              : SMP_METRIC_COMMAND_LE_PAIRING_CMPL;
+    uint16_t metric_status = p_cb->status;
+    if (metric_status > SMP_MAX_FAIL_RSN_PER_SPEC) {
+      metric_status |= SMP_METRIC_STATUS_INTERNAL_FLAG;
+    }
+    log_smp_pairing_event(p_cb->pairing_bda, metric_cmd, direction,
+                          metric_status);
+  }
+
+  if (p_cb->status == SMP_SUCCESS && p_cb->smp_over_br) {
+    btm_dev_consolidate_existing_connections(pairing_bda);
+  }
+
   smp_reset_control_value(p_cb);
 
   if (p_callback) (*p_callback)(SMP_COMPLT_EVT, pairing_bda, &evt_data);
diff --git a/system/stack/test/ad_parser_unittest.cc b/system/stack/test/ad_parser_unittest.cc
index 36cc7f2..1f6eb29 100644
--- a/system/stack/test/ad_parser_unittest.cc
+++ b/system/stack/test/ad_parser_unittest.cc
@@ -174,4 +174,40 @@
   glued.insert(glued.end(), scan_resp.begin(), scan_resp.end());
 
   EXPECT_TRUE(AdvertiseDataParser::IsValid(glued));
+}
+
+TEST(AdvertiseDataParserTest, GetFieldByTypeInLoop) {
+  // Single field.
+  const uint8_t AD_TYPE_SVC_DATA = 0x16;
+  const std::vector<uint8_t> data0{
+    0x02, 0x01, 0x02,
+    0x07, 0x2e, 0x6a, 0xc1, 0x19, 0x52, 0x1e, 0x49,
+    0x09, 0x16, 0x4e, 0x18, 0x00, 0xff, 0x0f, 0x03, 0x00, 0x00,
+    0x02, 0x0a, 0x7f,
+    0x03, 0x16, 0x4f, 0x18,
+    0x04, 0x16, 0x53, 0x18, 0x00,
+    0x0f, 0x09, 0x48, 0x5f, 0x43, 0x33, 0x45, 0x41, 0x31, 0x36, 0x33, 0x46, 0x35, 0x36, 0x34, 0x46 };
+
+  const uint8_t* p_service_data = data0.data();
+  uint8_t service_data_len = 0;
+
+  int match_no = 0;
+  while ((p_service_data = AdvertiseDataParser::GetFieldByType(
+              p_service_data + service_data_len,
+              data0.size() - (p_service_data - data0.data()) - service_data_len,
+              AD_TYPE_SVC_DATA, &service_data_len))) {
+    auto position = (p_service_data - data0.data());
+    if (match_no == 0) {
+      EXPECT_EQ(position, 13);
+      EXPECT_EQ(service_data_len, 8);
+    } else if (match_no == 1) {
+      EXPECT_EQ(position, 26);
+      EXPECT_EQ(service_data_len, 2);
+    } else if (match_no == 2) {
+      EXPECT_EQ(position, 30);
+      EXPECT_EQ(service_data_len, 3);
+    }
+    match_no++;
+  }
+  EXPECT_EQ(match_no, 3);
 }
\ No newline at end of file
diff --git a/system/stack/test/btm/stack_btm_regression_tests.cc b/system/stack/test/btm/stack_btm_regression_tests.cc
new file mode 100644
index 0000000..93c3d68
--- /dev/null
+++ b/system/stack/test/btm/stack_btm_regression_tests.cc
@@ -0,0 +1,51 @@
+/*
+ *
+ *  Copyright 2022 The Android Open Source Project
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at:
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+
+#include <gtest/gtest.h>
+
+#include "bt_hdr.h"
+#include "btm_ble_api_types.h"
+#include "hci_error_code.h"
+#include "osi/include/allocator.h"
+#include "ble_hci_link_interface.h"
+
+namespace {
+
+class StackBTMRegressionTests : public ::testing::Test {
+ protected:
+  void SetUp() override {}
+  void TearDown() override {}
+};
+
+// regression test for b/260078907
+TEST_F(StackBTMRegressionTests,
+       OOB_in_btm_ble_add_resolving_list_entry_complete) {
+  BT_HDR* pevent = (BT_HDR*)osi_calloc(sizeof(BT_HDR));
+  btm_ble_add_resolving_list_entry_complete(pevent->data, 0);
+  osi_free(pevent);
+}
+
+// regression test for b/255304475
+TEST_F(StackBTMRegressionTests,
+       OOB_in_btm_ble_clear_resolving_list_complete) {
+  BT_HDR* pevent = (BT_HDR*)osi_calloc(sizeof(BT_HDR));
+  btm_ble_clear_resolving_list_complete(pevent->data, 0);
+  osi_free(pevent);
+}
+
+}  // namespace
diff --git a/system/stack/test/btm/stack_btm_test.cc b/system/stack/test/btm/stack_btm_test.cc
index 49ae48c..e170664 100644
--- a/system/stack/test/btm/stack_btm_test.cc
+++ b/system/stack/test/btm/stack_btm_test.cc
@@ -129,8 +129,6 @@
   return &mock_stack_config;
 }
 
-std::map<std::string, int> mock_function_count_map;
-
 namespace {
 
 using testing::_;
diff --git a/system/stack/test/btm_iso_test.cc b/system/stack/test/btm_iso_test.cc
index a5554a0..5085918 100644
--- a/system/stack/test/btm_iso_test.cc
+++ b/system/stack/test/btm_iso_test.cc
@@ -24,6 +24,7 @@
 #include "mock_controller.h"
 #include "mock_hcic_layer.h"
 #include "osi/include/allocator.h"
+#include "stack/btm/btm_dev.h"
 #include "stack/include/bt_hdr.h"
 #include "stack/include/hci_error_code.h"
 #include "stack/include/hcidefs.h"
@@ -42,6 +43,10 @@
 // Iso Manager currently works on top of the legacy HCI layer
 bool bluetooth::shim::is_gd_shim_enabled() { return false; }
 
+tBTM_SEC_DEV_REC* btm_find_dev_by_handle(uint16_t handle) { return nullptr; }
+void BTM_LogHistory(const std::string& tag, const RawAddress& bd_addr,
+                    const std::string& msg, const std::string& extra) {}
+
 namespace bte {
 class BteInterface {
  public:
diff --git a/system/stack/test/common/mock_l2cap_layer.cc b/system/stack/test/common/mock_l2cap_layer.cc
index 2892759..d03d143 100644
--- a/system/stack/test/common/mock_l2cap_layer.cc
+++ b/system/stack/test/common/mock_l2cap_layer.cc
@@ -94,3 +94,8 @@
                                       tL2CAP_LE_CFG_INFO* peer_cfg) {
   return l2cap_interface->ReconfigCreditBasedConnsReq(bd_addr, lcids, peer_cfg);
 }
+uint16_t L2CA_LeCreditDefault() { return l2cap_interface->LeCreditDefault(); }
+
+uint16_t L2CA_LeCreditThreshold() {
+  return l2cap_interface->LeCreditThreshold();
+}
diff --git a/system/stack/test/common/mock_l2cap_layer.h b/system/stack/test/common/mock_l2cap_layer.h
index edb662b..cb2b36d 100644
--- a/system/stack/test/common/mock_l2cap_layer.h
+++ b/system/stack/test/common/mock_l2cap_layer.h
@@ -51,6 +51,8 @@
                                 tL2CAP_LE_CFG_INFO* p_cfg) = 0;
   virtual bool ReconfigCreditBasedConnsReq(const RawAddress& bd_addr, std::vector<uint16_t> &lcids,
                                 tL2CAP_LE_CFG_INFO* peer_cfg) = 0;
+  virtual uint16_t LeCreditDefault() = 0;
+  virtual uint16_t LeCreditThreshold() = 0;
   virtual ~L2capInterface() = default;
 };
 
@@ -84,6 +86,8 @@
                     tL2CAP_LE_CFG_INFO* p_cfg));
   MOCK_METHOD3(ReconfigCreditBasedConnsReq,
                bool(const RawAddress& p_bd_addr, std::vector<uint16_t> &lcids, tL2CAP_LE_CFG_INFO* peer_cfg));
+  MOCK_METHOD(uint16_t, LeCreditDefault, ());
+  MOCK_METHOD(uint16_t, LeCreditThreshold, ());
 };
 
 /**
diff --git a/system/stack/test/eatt/eatt_test.cc b/system/stack/test/eatt/eatt_test.cc
index 0d81f80..5bc9ef3 100644
--- a/system/stack/test/eatt/eatt_test.cc
+++ b/system/stack/test/eatt/eatt_test.cc
@@ -120,6 +120,82 @@
     ASSERT_TRUE(test_tcb.eatt == num_of_accepted_connections);
   }
 
+  void ConnectDeviceBothSides(int num_of_accepted_connections,
+                              std::vector<uint16_t>& incoming_cids) {
+    base::OnceCallback<void(const RawAddress&, uint8_t)> eatt_supp_feat_cb;
+
+    ON_CALL(gatt_interface_, ClientReadSupportedFeatures)
+        .WillByDefault(
+            [&eatt_supp_feat_cb](
+                const RawAddress& addr,
+                base::OnceCallback<void(const RawAddress&, uint8_t)> cb) {
+              eatt_supp_feat_cb = std::move(cb);
+              return true;
+            });
+
+    // Return false to trigger supported features request
+    ON_CALL(gatt_interface_, GetEattSupport)
+        .WillByDefault([](const RawAddress& addr) { return false; });
+
+    std::vector<uint16_t> test_local_cids{61, 62, 63, 64, 65};
+    EXPECT_CALL(l2cap_interface_,
+                ConnectCreditBasedReq(BT_PSM_EATT, test_address, _))
+        .WillOnce(Return(test_local_cids));
+
+    eatt_instance_->Connect(test_address);
+
+    // Let the remote connect while we are trying to connect
+    EXPECT_CALL(
+        l2cap_interface_,
+        ConnectCreditBasedRsp(test_address, 1, incoming_cids, L2CAP_CONN_OK, _))
+        .WillOnce(Return(true));
+    l2cap_app_info_.pL2CA_CreditBasedConnectInd_Cb(
+        test_address, incoming_cids, BT_PSM_EATT, EATT_MIN_MTU_MPS, 1);
+
+    // Respond to feature request scheduled by the connect request
+    ASSERT_TRUE(eatt_supp_feat_cb);
+    if (eatt_supp_feat_cb) {
+      std::move(eatt_supp_feat_cb)
+          .Run(test_address, BLE_GATT_SVR_SUP_FEAT_EATT_BITMASK);
+    }
+
+    int i = 0;
+    for (uint16_t cid : test_local_cids) {
+      EattChannel* channel =
+          eatt_instance_->FindEattChannelByCid(test_address, cid);
+      ASSERT_TRUE(channel != nullptr);
+      ASSERT_TRUE(channel->state_ == EattChannelState::EATT_CHANNEL_PENDING);
+
+      if (i < num_of_accepted_connections) {
+        l2cap_app_info_.pL2CA_CreditBasedConnectCfm_Cb(
+            test_address, cid, EATT_MIN_MTU_MPS, L2CAP_CONN_OK);
+        connected_cids_.push_back(cid);
+
+        ASSERT_TRUE(channel->state_ == EattChannelState::EATT_CHANNEL_OPENED);
+        ASSERT_TRUE(channel->tx_mtu_ == EATT_MIN_MTU_MPS);
+      } else {
+        l2cap_app_info_.pL2CA_Error_Cb(cid, L2CAP_CONN_NO_RESOURCES);
+
+        EattChannel* channel =
+            eatt_instance_->FindEattChannelByCid(test_address, cid);
+        ASSERT_TRUE(channel == nullptr);
+      }
+      i++;
+    }
+
+    // Check the incoming CIDs as well
+    for (auto cid : incoming_cids) {
+      EattChannel* channel =
+          eatt_instance_->FindEattChannelByCid(test_address, cid);
+      ASSERT_NE(nullptr, channel);
+      ASSERT_EQ(channel->state_, EattChannelState::EATT_CHANNEL_OPENED);
+      ASSERT_TRUE(channel->tx_mtu_ == EATT_MIN_MTU_MPS);
+    }
+
+    ASSERT_EQ(test_tcb.eatt,
+              num_of_accepted_connections + incoming_cids.size());
+  }
+
   void DisconnectEattByPeer(void) {
     for (uint16_t cid : connected_cids_)
       l2cap_app_info_.pL2CA_DisconnectInd_Cb(cid, true);
@@ -140,6 +216,9 @@
     bluetooth::gatt::SetMockGattInterface(&gatt_interface_);
     controller::SetMockControllerInterface(&controller_interface);
 
+    // Clear the static memory for each test case
+    memset(&test_tcb, 0, sizeof(test_tcb));
+
     EXPECT_CALL(l2cap_interface_, RegisterLECoc(BT_PSM_EATT, _, _))
         .WillOnce(DoAll(SaveArg<1>(&l2cap_app_info_), Return(BT_PSM_EATT)));
 
@@ -317,6 +396,22 @@
   DisconnectEattDevice(incoming_cids);
 }
 
+TEST_F(EattTest, ConnectInitiatedWhenRemoteConnects) {
+  ON_CALL(btm_api_interface_, IsEncrypted)
+      .WillByDefault(
+          [](const RawAddress& addr, tBT_TRANSPORT transport) { return true; });
+
+  std::vector<uint16_t> incoming_cids{71, 72, 73, 74};
+  ConnectDeviceBothSides(1, incoming_cids);
+
+  std::vector<uint16_t> disconnecting_cids;
+  disconnecting_cids.insert(disconnecting_cids.end(), incoming_cids.begin(),
+                            incoming_cids.end());
+  disconnecting_cids.insert(disconnecting_cids.end(), connected_cids_.begin(),
+                            connected_cids_.end());
+  DisconnectEattDevice(disconnecting_cids);
+}
+
 TEST_F(EattTest, ConnectSucceedMultipleChannels) {
   ConnectDeviceEattSupported(5);
   DisconnectEattDevice(connected_cids_);
diff --git a/system/stack/test/gatt/gatt_api_test.cc b/system/stack/test/gatt/gatt_api_test.cc
new file mode 100644
index 0000000..7900b77
--- /dev/null
+++ b/system/stack/test/gatt/gatt_api_test.cc
@@ -0,0 +1,88 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "gatt_api.h"
+
+#include <base/logging.h>
+#include <gtest/gtest.h>
+
+#include "btm/btm_dev.h"
+#include "gatt/gatt_int.h"
+
+extern tBTM_CB btm_cb;
+
+static const size_t QUEUE_SIZE_MAX = 10;
+
+static tBTM_SEC_DEV_REC* make_bonded_ble_device(const RawAddress& bda,
+                                                const RawAddress& rra) {
+  tBTM_SEC_DEV_REC* dev = btm_sec_allocate_dev_rec();
+  dev->sec_flags |= BTM_SEC_LE_LINK_KEY_KNOWN;
+  dev->bd_addr = bda;
+  dev->ble.pseudo_addr = rra;
+  dev->ble.key_type = BTM_LE_KEY_PID | BTM_LE_KEY_PENC | BTM_LE_KEY_LENC;
+  return dev;
+}
+
+static tBTM_SEC_DEV_REC* make_bonded_dual_device(const RawAddress& bda,
+                                                 const RawAddress& rra) {
+  tBTM_SEC_DEV_REC* dev = make_bonded_ble_device(bda, rra);
+  dev->sec_flags |= BTM_SEC_LINK_KEY_KNOWN;
+  return dev;
+}
+
+extern std::optional<bool> OVERRIDE_GATT_LOAD_BONDED;
+
+class GattApiTest : public ::testing::Test {
+ protected:
+  GattApiTest() = default;
+
+  virtual ~GattApiTest() = default;
+
+  void SetUp() override {
+    btm_cb.sec_dev_rec = list_new(osi_free);
+    gatt_cb.srv_chg_clt_q = fixed_queue_new(QUEUE_SIZE_MAX);
+    logging::SetMinLogLevel(-2);
+  }
+
+  void TearDown() override { list_free(btm_cb.sec_dev_rec); }
+};
+
+static const RawAddress SAMPLE_PUBLIC_BDA = {
+    {0x00, 0x00, 0x11, 0x22, 0x33, 0x44}};
+
+static const RawAddress SAMPLE_RRA_BDA = {{0xAA, 0xAA, 0x11, 0x22, 0x33, 0x44}};
+
+TEST_F(GattApiTest, test_gatt_load_bonded_ble_only) {
+  OVERRIDE_GATT_LOAD_BONDED = std::optional{true};
+  make_bonded_ble_device(SAMPLE_PUBLIC_BDA, SAMPLE_RRA_BDA);
+
+  gatt_load_bonded();
+
+  ASSERT_TRUE(gatt_is_bda_in_the_srv_chg_clt_list(SAMPLE_RRA_BDA));
+  ASSERT_FALSE(gatt_is_bda_in_the_srv_chg_clt_list(SAMPLE_PUBLIC_BDA));
+  OVERRIDE_GATT_LOAD_BONDED.reset();
+}
+
+TEST_F(GattApiTest, test_gatt_load_bonded_dual) {
+  OVERRIDE_GATT_LOAD_BONDED = std::optional{true};
+  make_bonded_dual_device(SAMPLE_PUBLIC_BDA, SAMPLE_RRA_BDA);
+
+  gatt_load_bonded();
+
+  ASSERT_TRUE(gatt_is_bda_in_the_srv_chg_clt_list(SAMPLE_RRA_BDA));
+  ASSERT_TRUE(gatt_is_bda_in_the_srv_chg_clt_list(SAMPLE_PUBLIC_BDA));
+  OVERRIDE_GATT_LOAD_BONDED.reset();
+}
diff --git a/system/stack/test/gatt/gatt_sr_test.cc b/system/stack/test/gatt/gatt_sr_test.cc
index 7e6ab31..f142bae 100644
--- a/system/stack/test/gatt/gatt_sr_test.cc
+++ b/system/stack/test/gatt/gatt_sr_test.cc
@@ -63,6 +63,8 @@
 bool direct_connect_remove(uint8_t app_id, const RawAddress& address) {
   return false;
 }
+bool is_background_connection(const RawAddress& address) { return false; }
+
 }  // namespace connection_manager
 
 BT_HDR* attp_build_sr_msg(tGATT_TCB& tcb, uint8_t op_code, tGATT_SR_MSG* p_msg,
diff --git a/system/stack/test/gatt/mock_gatt_utils_ref.cc b/system/stack/test/gatt/mock_gatt_utils_ref.cc
index d416e98..59d0022 100644
--- a/system/stack/test/gatt/mock_gatt_utils_ref.cc
+++ b/system/stack/test/gatt/mock_gatt_utils_ref.cc
@@ -29,6 +29,7 @@
 bool direct_connect_remove(uint8_t app_id, const RawAddress& address) {
   return false;
 }
+bool is_background_connection(const RawAddress& address) { return false; }
 }  // namespace connection_manager
 
 /** stack/gatt/att_protocol.cc */
diff --git a/system/stack/test/stack_l2cap_test.cc b/system/stack/test/stack_l2cap_test.cc
index 2418772..e8a6f1a 100644
--- a/system/stack/test/stack_l2cap_test.cc
+++ b/system/stack/test/stack_l2cap_test.cc
@@ -17,6 +17,7 @@
 #include <gtest/gtest.h>
 
 #include "common/init_flags.h"
+#include "device/include/controller.h"
 #include "internal_include/bt_trace.h"
 #include "stack/btm/btm_int_types.h"
 #include "stack/include/l2cap_hci_link_interface.h"
@@ -26,24 +27,38 @@
 tBTM_CB btm_cb;
 extern tL2C_CB l2cb;
 
+void l2c_link_send_to_lower_br_edr(tL2C_LCB* p_lcb, BT_HDR* p_buf);
+void l2c_link_send_to_lower_ble(tL2C_LCB* p_lcb, BT_HDR* p_buf);
+
 // Global trace level referred in the code under test
 uint8_t appl_trace_level = BT_TRACE_LEVEL_VERBOSE;
 
 extern "C" void LogMsg(uint32_t trace_set_mask, const char* fmt_str, ...) {}
 
-const char* test_flags[] = {
-    "INIT_logging_debug_enabled_for_all=true",
-    nullptr,
-};
+namespace {
+constexpr uint16_t kAclBufferCountClassic = 123;
+constexpr uint8_t kAclBufferCountBle = 45;
+
+}  // namespace
 
 class StackL2capTest : public ::testing::Test {
  protected:
   void SetUp() override {
-    bluetooth::common::InitFlags::Load(test_flags);
-    l2cb = {};  // TODO Use proper init/free APIs
+    bluetooth::common::InitFlags::SetAllForTesting();
+    controller_.get_acl_buffer_count_classic = []() {
+      return kAclBufferCountClassic;
+    };
+    controller_.get_acl_buffer_count_ble = []() { return kAclBufferCountBle; };
+    controller_.supports_ble = []() -> bool { return true; };
+    l2c_init();
   }
 
-  void TearDown() override {}
+  void TearDown() override {
+    l2c_free();
+    controller_ = {};
+  }
+
+  controller_t controller_;
 };
 
 TEST_F(StackL2capTest, l2cble_process_data_length_change_event) {
@@ -65,3 +80,117 @@
   l2cble_process_data_length_change_event(0x1234, 0x001b, 0x001b);
   ASSERT_EQ(0x001b, l2cb.lcb_pool[0].tx_data_len);
 }
+
+class StackL2capChannelTest : public StackL2capTest {
+ protected:
+  void SetUp() override { StackL2capTest::SetUp(); }
+
+  void TearDown() override { StackL2capTest::TearDown(); }
+
+  tL2C_CCB ccb_ = {
+      .in_use = true,
+      .chnl_state = CST_OPEN,  // tL2C_CHNL_STATE
+      .local_conn_cfg =
+          {
+              // tL2CAP_LE_CFG_INFO
+              .result = 0,
+              .mtu = 100,
+              .mps = 100,
+              .credits = L2CA_LeCreditDefault(),
+              .number_of_channels = L2CAP_CREDIT_BASED_MAX_CIDS,
+          },
+      .peer_conn_cfg =
+          {
+              // tL2CAP_LE_CFG_INFO
+              .result = 0,
+              .mtu = 100,
+              .mps = 100,
+              .credits = L2CA_LeCreditDefault(),
+              .number_of_channels = L2CAP_CREDIT_BASED_MAX_CIDS,
+          },
+      .is_first_seg = false,
+      .ble_sdu = nullptr,     // BT_HDR*; Buffer for storing unassembled sdu
+      .ble_sdu_length = 0,    /* Length of unassembled sdu length*/
+      .p_next_ccb = nullptr,  // struct t_l2c_ccb* Next CCB in the chain
+      .p_prev_ccb = nullptr,  // struct t_l2c_ccb* Previous CCB in the chain
+      .p_lcb = nullptr,  // struct t_l2c_linkcb* Link this CCB is assigned to
+      .local_cid = 40,
+      .remote_cid = 80,
+      .l2c_ccb_timer = nullptr,  // alarm_t* CCB Timer Entry
+      .p_rcb = nullptr,          // tL2C_RCB* Registration CB for this Channel
+      .config_done = 0,          // Configuration flag word
+      .remote_config_rsp_result = 0,  // The config rsp result from remote
+      .local_id = 12,                 // Transaction ID for local trans
+      .remote_id = 22,                // Transaction ID for local
+      .flags = 0,
+      .connection_initiator = false,
+      .our_cfg = {},   // tL2CAP_CFG_INFO Our saved configuration options
+      .peer_cfg = {},  // tL2CAP_CFG_INFO Peer's saved configuration options
+      .xmit_hold_q = nullptr,  // fixed_queue_t*  Transmit data hold queue
+      .cong_sent = false,
+      .buff_quota = 0,
+
+      .ccb_priority =
+          L2CAP_CHNL_PRIORITY_HIGH,  // tL2CAP_CHNL_PRIORITY Channel priority
+      .tx_data_rate = 0,  // tL2CAP_CHNL_PRIORITY  Channel Tx data rate
+      .rx_data_rate = 0,  // tL2CAP_CHNL_PRIORITY  Channel Rx data rate
+
+      .ertm_info =
+          {
+              // .tL2CAP_ERTM_INFO
+              .preferred_mode = 0,
+          },
+      .fcrb =
+          {
+              // tL2C_FCRB
+              .next_tx_seq = 0,
+              .last_rx_ack = 0,
+              .next_seq_expected = 0,
+              .last_ack_sent = 0,
+              .num_tries = 0,
+              .max_held_acks = 0,
+              .remote_busy = false,
+              .rej_sent = false,
+              .srej_sent = false,
+              .wait_ack = false,
+              .rej_after_srej = false,
+              .send_f_rsp = false,
+              .rx_sdu_len = 0,
+              .p_rx_sdu =
+                  nullptr,  // BT_HDR* Buffer holding the SDU being received
+              .waiting_for_ack_q = nullptr,  // fixed_queue_t*
+              .srej_rcv_hold_q = nullptr,    // fixed_queue_t*
+              .retrans_q = nullptr,          // fixed_queue_t*
+              .ack_timer = nullptr,          // alarm_t*
+              .mon_retrans_timer = nullptr,  // alarm_t*
+          },
+      .tx_mps = 0,
+      .max_rx_mtu = 0,
+      .fcr_cfg_tries = 0,
+      .peer_cfg_already_rejected = false,
+      .out_cfg_fcr_present = false,
+      .is_flushable = false,
+      .fixed_chnl_idle_tout = 0,
+      .tx_data_len = 0,
+      .remote_credit_count = 0,
+      .ecoc = false,
+      .reconfig_started = false,
+      .metrics = {},
+  };
+};
+
+TEST_F(StackL2capChannelTest, l2c_lcc_proc_pdu__FirstSegment) {
+  ccb_.is_first_seg = true;
+
+  BT_HDR* p_buf = (BT_HDR*)osi_calloc(sizeof(BT_HDR) + 32);
+  p_buf->len = 32;
+
+  l2c_lcc_proc_pdu(&ccb_, p_buf);
+}
+
+TEST_F(StackL2capChannelTest, l2c_lcc_proc_pdu__NextSegment) {
+  BT_HDR* p_buf = (BT_HDR*)osi_calloc(sizeof(BT_HDR) + 32);
+  p_buf->len = 32;
+
+  l2c_lcc_proc_pdu(&ccb_, p_buf);
+}
diff --git a/system/test/mock/mock_bta_gatts_api.cc b/system/test/mock/mock_bta_gatts_api.cc
index 68380e4..ba86ae5 100644
--- a/system/test/mock/mock_bta_gatts_api.cc
+++ b/system/test/mock/mock_bta_gatts_api.cc
@@ -85,3 +85,4 @@
                                 BTA_GATTS_AddServiceCb cb) {
   mock_function_count_map[__func__]++;
 }
+void BTA_GATTS_InitBonded(void) { mock_function_count_map[__func__]++; }
diff --git a/system/test/mock/mock_bta_hearing_aid.cc b/system/test/mock/mock_bta_hearing_aid.cc
index eabcd09..200df2d 100644
--- a/system/test/mock/mock_bta_hearing_aid.cc
+++ b/system/test/mock/mock_bta_hearing_aid.cc
@@ -52,22 +52,39 @@
   mock_function_count_map[__func__]++;
   return 0;
 }
+
 void HearingAid::AddFromStorage(const HearingDevice& dev_info,
                                 uint16_t is_acceptlisted) {
   mock_function_count_map[__func__]++;
 }
+
 void HearingAid::DebugDump(int fd) { mock_function_count_map[__func__]++; }
-HearingAid* HearingAid::Get() {
-  mock_function_count_map[__func__]++;
-  return nullptr;
-}
+
 bool HearingAid::IsHearingAidRunning() {
   mock_function_count_map[__func__]++;
   return false;
 }
+
 void HearingAid::CleanUp() { mock_function_count_map[__func__]++; }
+
 void HearingAid::Initialize(
     bluetooth::hearing_aid::HearingAidCallbacks* callbacks,
     base::Closure initCb) {
   mock_function_count_map[__func__]++;
 }
+
+void HearingAid::Connect(const RawAddress& address) {
+  mock_function_count_map[__func__]++;
+}
+
+void HearingAid::Disconnect(const RawAddress& address) {
+  mock_function_count_map[__func__]++;
+}
+
+void HearingAid::AddToAcceptlist(const RawAddress& address) {
+  mock_function_count_map[__func__]++;
+}
+
+void HearingAid::SetVolume(int8_t volume) {
+  mock_function_count_map[__func__]++;
+}
diff --git a/system/test/mock/mock_bta_hf_client_api.cc b/system/test/mock/mock_bta_hf_client_api.cc
index 19f6913..f5be68a 100644
--- a/system/test/mock/mock_bta_hf_client_api.cc
+++ b/system/test/mock/mock_bta_hf_client_api.cc
@@ -51,10 +51,15 @@
 void BTA_HfClientClose(uint16_t handle) { mock_function_count_map[__func__]++; }
 void BTA_HfClientDisable(void) { mock_function_count_map[__func__]++; }
 void BTA_HfClientDumpStatistics(int fd) { mock_function_count_map[__func__]++; }
-void BTA_HfClientOpen(const RawAddress& bd_addr, uint16_t* p_handle) {
+bt_status_t BTA_HfClientOpen(const RawAddress& bd_addr, uint16_t* p_handle) {
   mock_function_count_map[__func__]++;
+  return BT_STATUS_SUCCESS;
 }
 void BTA_HfClientSendAT(uint16_t handle, tBTA_HF_CLIENT_AT_CMD_TYPE at,
                         uint32_t val1, uint32_t val2, const char* str) {
   mock_function_count_map[__func__]++;
 }
+int get_default_hf_client_features() {
+  mock_function_count_map[__func__]++;
+  return 0;
+}
diff --git a/system/test/mock/mock_bta_hfp_api.cc b/system/test/mock/mock_bta_hfp_api.cc
new file mode 100644
index 0000000..b9dc9dd
--- /dev/null
+++ b/system/test/mock/mock_bta_hfp_api.cc
@@ -0,0 +1,21 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "bta/include/bta_hfp_api.h"
+
+#define DEFAULT_BTA_HFP_VERSION HFP_VERSION_1_7
+
+int get_default_hfp_version() { return DEFAULT_BTA_HFP_VERSION; }
diff --git a/system/test/mock/mock_main_shim.cc b/system/test/mock/mock_main_shim.cc
index 69cd8df..1e997ce 100644
--- a/system/test/mock/mock_main_shim.cc
+++ b/system/test/mock/mock_main_shim.cc
@@ -25,6 +25,7 @@
 extern std::map<std::string, int> mock_function_count_map;
 
 #define LOG_TAG "bt_shim"
+
 #include "gd/common/init_flags.h"
 #include "main/shim/entry.h"
 #include "main/shim/shim.h"
@@ -45,9 +46,14 @@
   mock_function_count_map[__func__]++;
   return false;
 }
+namespace test {
+namespace mock {
+bool bluetooth_shim_is_gd_stack_started_up = false;
+}
+}  // namespace test
 bool bluetooth::shim::is_gd_stack_started_up() {
   mock_function_count_map[__func__]++;
-  return false;
+  return test::mock::bluetooth_shim_is_gd_stack_started_up;
 }
 bool bluetooth::shim::is_gd_link_policy_enabled() {
   mock_function_count_map[__func__]++;
diff --git a/system/test/mock/mock_main_shim_metrics_api.cc b/system/test/mock/mock_main_shim_metrics_api.cc
index 30aeeb1..8330b85 100644
--- a/system/test/mock/mock_main_shim_metrics_api.cc
+++ b/system/test/mock/mock_main_shim_metrics_api.cc
@@ -128,8 +128,8 @@
       raw_address, handle, cmd_status, transmit_power_level);
 }
 void bluetooth::shim::LogMetricSmpPairingEvent(
-    const RawAddress& raw_address, uint8_t smp_cmd,
-    android::bluetooth::DirectionEnum direction, uint8_t smp_fail_reason) {
+    const RawAddress& raw_address, uint16_t smp_cmd,
+    android::bluetooth::DirectionEnum direction, uint16_t smp_fail_reason) {
   mock_function_count_map[__func__]++;
   test::mock::main_shim_metrics_api::LogMetricSmpPairingEvent(
       raw_address, smp_cmd, direction, smp_fail_reason);
diff --git a/system/test/mock/mock_main_shim_metrics_api.h b/system/test/mock/mock_main_shim_metrics_api.h
index b42529f..d85088f 100644
--- a/system/test/mock/mock_main_shim_metrics_api.h
+++ b/system/test/mock/mock_main_shim_metrics_api.h
@@ -171,19 +171,19 @@
 };
 extern struct LogMetricReadTxPowerLevelResult LogMetricReadTxPowerLevelResult;
 // Name: LogMetricSmpPairingEvent
-// Params: const RawAddress& raw_address, uint8_t smp_cmd,
+// Params: const RawAddress& raw_address, uint16_t smp_cmd,
 // android::bluetooth::DirectionEnum direction, uint8_t smp_fail_reason Returns:
 // void
 struct LogMetricSmpPairingEvent {
-  std::function<void(const RawAddress& raw_address, uint8_t smp_cmd,
+  std::function<void(const RawAddress& raw_address, uint16_t smp_cmd,
                      android::bluetooth::DirectionEnum direction,
-                     uint8_t smp_fail_reason)>
-      body{[](const RawAddress& raw_address, uint8_t smp_cmd,
+                     uint16_t smp_fail_reason)>
+      body{[](const RawAddress& raw_address, uint16_t smp_cmd,
               android::bluetooth::DirectionEnum direction,
-              uint8_t smp_fail_reason) {}};
-  void operator()(const RawAddress& raw_address, uint8_t smp_cmd,
+              uint16_t smp_fail_reason) {}};
+  void operator()(const RawAddress& raw_address, uint16_t smp_cmd,
                   android::bluetooth::DirectionEnum direction,
-                  uint8_t smp_fail_reason) {
+                  uint16_t smp_fail_reason) {
     body(raw_address, smp_cmd, direction, smp_fail_reason);
   };
 };
diff --git a/system/test/mock/mock_stack_acl.cc b/system/test/mock/mock_stack_acl.cc
index 91ee709..771e256 100644
--- a/system/test/mock/mock_stack_acl.cc
+++ b/system/test/mock/mock_stack_acl.cc
@@ -621,9 +621,9 @@
   mock_function_count_map[__func__]++;
   test::mock::stack_acl::btm_read_failed_contact_counter_timeout(data);
 }
-void btm_read_link_quality_complete(uint8_t* p) {
+void btm_read_link_quality_complete(uint8_t* p, uint16_t evt_len) {
   mock_function_count_map[__func__]++;
-  test::mock::stack_acl::btm_read_link_quality_complete(p);
+  test::mock::stack_acl::btm_read_link_quality_complete(p, evt_len);
 }
 void btm_read_link_quality_timeout(UNUSED_ATTR void* data) {
   mock_function_count_map[__func__]++;
@@ -660,17 +660,17 @@
   test::mock::stack_acl::btm_read_remote_version_complete(
       status, handle, lmp_version, manufacturer, lmp_subversion);
 }
-void btm_read_rssi_complete(uint8_t* p) {
+void btm_read_rssi_complete(uint8_t* p, uint16_t evt_len) {
   mock_function_count_map[__func__]++;
-  test::mock::stack_acl::btm_read_rssi_complete(p);
+  test::mock::stack_acl::btm_read_rssi_complete(p, evt_len);
 }
 void btm_read_rssi_timeout(UNUSED_ATTR void* data) {
   mock_function_count_map[__func__]++;
   test::mock::stack_acl::btm_read_rssi_timeout(data);
 }
-void btm_read_tx_power_complete(uint8_t* p, bool is_ble) {
+void btm_read_tx_power_complete(uint8_t* p, uint16_t evt_len, bool is_ble) {
   mock_function_count_map[__func__]++;
-  test::mock::stack_acl::btm_read_tx_power_complete(p, is_ble);
+  test::mock::stack_acl::btm_read_tx_power_complete(p, evt_len, is_ble);
 }
 void btm_read_tx_power_timeout(UNUSED_ATTR void* data) {
   mock_function_count_map[__func__]++;
diff --git a/system/test/mock/mock_stack_acl.h b/system/test/mock/mock_stack_acl.h
index 8747ed7..d2efb0c 100644
--- a/system/test/mock/mock_stack_acl.h
+++ b/system/test/mock/mock_stack_acl.h
@@ -1091,8 +1091,8 @@
 // Params: uint8_t* p
 // Returns: void
 struct btm_read_link_quality_complete {
-  std::function<void(uint8_t* p)> body{[](uint8_t* p) { ; }};
-  void operator()(uint8_t* p) { body(p); };
+  std::function<void(uint8_t* p, uint16_t evt_len)> body{[](uint8_t* p, uint16_t evt_len) { ; }};
+  void operator()(uint8_t* p, uint16_t evt_len) { body(p, evt_len); };
 };
 extern struct btm_read_link_quality_complete btm_read_link_quality_complete;
 // Name: btm_read_link_quality_timeout
@@ -1180,8 +1180,9 @@
 // Params: uint8_t* p
 // Returns: void
 struct btm_read_rssi_complete {
-  std::function<void(uint8_t* p)> body{[](uint8_t* p) { ; }};
-  void operator()(uint8_t* p) { body(p); };
+  std::function<void(uint8_t* p, uint16_t evt_len)> body{
+      [](uint8_t* pm, uint16_t evt_len) { ; }};
+  void operator()(uint8_t* p, uint16_t evt_len) { body(p, evt_len); };
 };
 extern struct btm_read_rssi_complete btm_read_rssi_complete;
 // Name: btm_read_rssi_timeout
@@ -1197,9 +1198,11 @@
 // Params: uint8_t* p, bool is_ble
 // Returns: void
 struct btm_read_tx_power_complete {
-  std::function<void(uint8_t* p, bool is_ble)> body{
-      [](uint8_t* p, bool is_ble) { ; }};
-  void operator()(uint8_t* p, bool is_ble) { body(p, is_ble); };
+  std::function<void(uint8_t* p, uint16_t evt_len, bool is_ble)> body{
+      [](uint8_t* p, uint16_t evt_len, bool is_ble) { ; }};
+  void operator()(uint8_t* p, uint16_t evt_len, bool is_ble) {
+    body(p, evt_len, is_ble);
+  };
 };
 extern struct btm_read_tx_power_complete btm_read_tx_power_complete;
 // Name: btm_read_tx_power_timeout
diff --git a/system/test/mock/mock_stack_avrc_api.cc b/system/test/mock/mock_stack_avrc_api.cc
index c9e58a9..235613b 100644
--- a/system/test/mock/mock_stack_avrc_api.cc
+++ b/system/test/mock/mock_stack_avrc_api.cc
@@ -40,6 +40,10 @@
 #define UNUSED_ATTR
 #endif
 
+bool avrcp_absolute_volume_is_enabled() {
+  mock_function_count_map[__func__]++;
+  return true;
+}
 uint16_t AVRC_Close(uint8_t handle) {
   mock_function_count_map[__func__]++;
   return 0;
diff --git a/system/test/mock/mock_stack_btm_ble.cc b/system/test/mock/mock_stack_btm_ble.cc
index 5ebd1a5..ee5ac6f 100644
--- a/system/test/mock/mock_stack_btm_ble.cc
+++ b/system/test/mock/mock_stack_btm_ble.cc
@@ -226,7 +226,7 @@
                                const Octet16& stk) {
   mock_function_count_map[__func__]++;
 }
-void btm_ble_rand_enc_complete(uint8_t* p, uint16_t op_code,
+void btm_ble_rand_enc_complete(uint8_t* p, uint16_t evt_len, uint16_t op_code,
                                tBTM_RAND_ENC_CB* p_enc_cplt_cback) {
   mock_function_count_map[__func__]++;
 }
diff --git a/system/test/mock/mock_stack_btm_ble_gap.cc b/system/test/mock/mock_stack_btm_ble_gap.cc
index cf3294c..aadc5f2 100644
--- a/system/test/mock/mock_stack_btm_ble_gap.cc
+++ b/system/test/mock/mock_stack_btm_ble_gap.cc
@@ -190,7 +190,7 @@
 void btm_ble_process_phy_update_pkt(uint8_t len, uint8_t* data) {
   mock_function_count_map[__func__]++;
 }
-void btm_ble_read_remote_features_complete(uint8_t* p) {
+void btm_ble_read_remote_features_complete(uint8_t* p, uint8_t length) {
   mock_function_count_map[__func__]++;
 }
 void btm_ble_read_remote_name_cmpl(bool status, const RawAddress& bda,
@@ -220,7 +220,7 @@
                                    tHCI_STATUS status) {
   mock_function_count_map[__func__]++;
 }
-void btm_ble_write_adv_enable_complete(uint8_t* p) {
+void btm_ble_write_adv_enable_complete(uint8_t* p, uint16_t evt_len) {
   mock_function_count_map[__func__]++;
 }
 void btm_clear_all_pending_le_entry(void) {
diff --git a/system/test/mock/mock_stack_btm_dev.cc b/system/test/mock/mock_stack_btm_dev.cc
index a01217b..cc959d3 100644
--- a/system/test/mock/mock_stack_btm_dev.cc
+++ b/system/test/mock/mock_stack_btm_dev.cc
@@ -109,3 +109,16 @@
 void wipe_secrets_and_remove(tBTM_SEC_DEV_REC* p_dev_rec) {
   mock_function_count_map[__func__]++;
 }
+void btm_dev_consolidate_existing_connections(const RawAddress& bd_addr) {
+  mock_function_count_map[__func__]++;
+}
+void BTM_SecDump(const std::string& label) {
+  mock_function_count_map[__func__]++;
+}
+void BTM_SecDumpDev(const RawAddress& bd_addr) {
+  mock_function_count_map[__func__]++;
+}
+std::vector<tBTM_SEC_DEV_REC*> btm_get_sec_dev_rec() {
+  mock_function_count_map[__func__]++;
+  return {};
+}
diff --git a/system/test/mock/mock_stack_btm_devctl.cc b/system/test/mock/mock_stack_btm_devctl.cc
index a51098f..1b9db1a 100644
--- a/system/test/mock/mock_stack_btm_devctl.cc
+++ b/system/test/mock/mock_stack_btm_devctl.cc
@@ -105,7 +105,8 @@
 }
 void BTM_db_reset(void) { mock_function_count_map[__func__]++; }
 void BTM_reset_complete() { mock_function_count_map[__func__]++; }
-void btm_delete_stored_link_key_complete(uint8_t* p) {
+void btm_delete_stored_link_key_complete(uint8_t* p,
+                                         UNUSED_ATTR uint16_t evt_len) {
   mock_function_count_map[__func__]++;
 }
 void btm_dev_free() { mock_function_count_map[__func__]++; }
diff --git a/system/test/mock/mock_stack_btm_sec.cc b/system/test/mock/mock_stack_btm_sec.cc
index 56a0db1..ea6ce17 100644
--- a/system/test/mock/mock_stack_btm_sec.cc
+++ b/system/test/mock/mock_stack_btm_sec.cc
@@ -239,7 +239,7 @@
 void NotifyBondingCanceled(tBTM_STATUS btm_status) {
   mock_function_count_map[__func__]++;
 }
-void btm_create_conn_cancel_complete(const uint8_t* p) {
+void btm_create_conn_cancel_complete(const uint8_t* p, uint16_t evt_len) {
   mock_function_count_map[__func__]++;
 }
 void btm_io_capabilities_req(const RawAddress& p) {
@@ -251,7 +251,7 @@
 void btm_proc_sp_req_evt(tBTM_SP_EVT event, const uint8_t* p) {
   mock_function_count_map[__func__]++;
 }
-void btm_read_local_oob_complete(uint8_t* p) {
+void btm_read_local_oob_complete(uint8_t* p, uint16_t evt_len) {
   mock_function_count_map[__func__]++;
 }
 void btm_rem_oob_req(const uint8_t* p) { mock_function_count_map[__func__]++; }
diff --git a/system/test/mock/mock_stack_gatt_connection_manager.cc b/system/test/mock/mock_stack_gatt_connection_manager.cc
index 99f2904..372b4f0 100644
--- a/system/test/mock/mock_stack_gatt_connection_manager.cc
+++ b/system/test/mock/mock_stack_gatt_connection_manager.cc
@@ -92,3 +92,8 @@
 void connection_manager::reset(bool after_reset) {
   mock_function_count_map[__func__]++;
 }
+
+bool connection_manager::is_background_connection(const RawAddress& address) {
+  mock_function_count_map[__func__]++;
+  return false;
+}
diff --git a/system/test/mock/mock_stack_l2cap_api.cc b/system/test/mock/mock_stack_l2cap_api.cc
index 27cd1e3..7311b3b 100644
--- a/system/test/mock/mock_stack_l2cap_api.cc
+++ b/system/test/mock/mock_stack_l2cap_api.cc
@@ -99,6 +99,8 @@
 struct L2CA_SetChnlFlushability L2CA_SetChnlFlushability;
 struct L2CA_FlushChannel L2CA_FlushChannel;
 struct L2CA_IsLinkEstablished L2CA_IsLinkEstablished;
+struct L2CA_LeCreditDefault L2CA_LeCreditDefault;
+struct L2CA_LeCreditThreshold L2CA_LeCreditThreshold;
 
 }  // namespace stack_l2cap_api
 }  // namespace mock
@@ -287,5 +289,13 @@
   return test::mock::stack_l2cap_api::L2CA_IsLinkEstablished(bd_addr,
                                                              transport);
 }
+uint16_t L2CA_LeCreditDefault() {
+  mock_function_count_map[__func__]++;
+  return test::mock::stack_l2cap_api::L2CA_LeCreditDefault();
+}
+uint16_t L2CA_LeCreditThreshold() {
+  mock_function_count_map[__func__]++;
+  return test::mock::stack_l2cap_api::L2CA_LeCreditThreshold();
+}
 
 // END mockcify generation
diff --git a/system/test/mock/mock_stack_l2cap_api.h b/system/test/mock/mock_stack_l2cap_api.h
index 4488101..592485c 100644
--- a/system/test/mock/mock_stack_l2cap_api.h
+++ b/system/test/mock/mock_stack_l2cap_api.h
@@ -485,6 +485,22 @@
   };
 };
 extern struct L2CA_IsLinkEstablished L2CA_IsLinkEstablished;
+// Name: L2CA_LeCreditDefault
+// Params:
+// Returns: uint16_t
+struct L2CA_LeCreditDefault {
+  std::function<uint16_t()> body{[]() { return 0; }};
+  uint16_t operator()() { return body(); };
+};
+extern struct L2CA_LeCreditDefault L2CA_LeCreditDefault;
+// Name: L2CA_LeCreditThreshold
+// Params:
+// Returns: uint16_t
+struct L2CA_LeCreditThreshold {
+  std::function<uint16_t()> body{[]() { return 0; }};
+  uint16_t operator()() { return body(); };
+};
+extern struct L2CA_LeCreditThreshold L2CA_LeCreditThreshold;
 
 }  // namespace stack_l2cap_api
 }  // namespace mock
diff --git a/system/test/mock/mock_stack_metrics_logging.cc b/system/test/mock/mock_stack_metrics_logging.cc
index 1f51191..028636d 100644
--- a/system/test/mock/mock_stack_metrics_logging.cc
+++ b/system/test/mock/mock_stack_metrics_logging.cc
@@ -86,9 +86,9 @@
       address, connection_handle, direction, link_type, hci_cmd, hci_event,
       hci_ble_event, cmd_status, reason_code);
 }
-void log_smp_pairing_event(const RawAddress& address, uint8_t smp_cmd,
+void log_smp_pairing_event(const RawAddress& address, uint16_t smp_cmd,
                            android::bluetooth::DirectionEnum direction,
-                           uint8_t smp_fail_reason) {
+                           uint16_t smp_fail_reason) {
   mock_function_count_map[__func__]++;
   test::mock::stack_metrics_logging::log_smp_pairing_event(
       address, smp_cmd, direction, smp_fail_reason);
diff --git a/system/test/mock/mock_stack_metrics_logging.h b/system/test/mock/mock_stack_metrics_logging.h
index 967b2f8..8b695f4 100644
--- a/system/test/mock/mock_stack_metrics_logging.h
+++ b/system/test/mock/mock_stack_metrics_logging.h
@@ -96,19 +96,19 @@
 };
 extern struct log_link_layer_connection_event log_link_layer_connection_event;
 // Name: log_smp_pairing_event
-// Params: const RawAddress& address, uint8_t smp_cmd,
+// Params: const RawAddress& address, uint16_t smp_cmd,
 // android::bluetooth::DirectionEnum direction, uint8_t smp_fail_reason Returns:
 // void
 struct log_smp_pairing_event {
-  std::function<void(const RawAddress& address, uint8_t smp_cmd,
+  std::function<void(const RawAddress& address, uint16_t smp_cmd,
                      android::bluetooth::DirectionEnum direction,
-                     uint8_t smp_fail_reason)>
-      body{[](const RawAddress& address, uint8_t smp_cmd,
+                     uint16_t smp_fail_reason)>
+      body{[](const RawAddress& address, uint16_t smp_cmd,
               android::bluetooth::DirectionEnum direction,
-              uint8_t smp_fail_reason) {}};
-  void operator()(const RawAddress& address, uint8_t smp_cmd,
+              uint16_t smp_fail_reason) {}};
+  void operator()(const RawAddress& address, uint16_t smp_cmd,
                   android::bluetooth::DirectionEnum direction,
-                  uint8_t smp_fail_reason) {
+                  uint16_t smp_fail_reason) {
     body(address, smp_cmd, direction, smp_fail_reason);
   };
 };
diff --git a/system/test/mock/mock_stack_smp_api.cc b/system/test/mock/mock_stack_smp_api.cc
index b3b175c..59f13f9 100644
--- a/system/test/mock/mock_stack_smp_api.cc
+++ b/system/test/mock/mock_stack_smp_api.cc
@@ -84,6 +84,9 @@
   mock_function_count_map[__func__]++;
 }
 
-void SMP_CrLocScOobData() { mock_function_count_map[__func__]++; }
+bool SMP_CrLocScOobData() {
+  mock_function_count_map[__func__]++;
+  return false;
+}
 
 void SMP_ClearLocScOobData() { mock_function_count_map[__func__]++; }
diff --git a/system/test/stub/osi.cc b/system/test/stub/osi.cc
index 6c87947..7041855 100644
--- a/system/test/stub/osi.cc
+++ b/system/test/stub/osi.cc
@@ -381,7 +381,7 @@
 
 alarm_t* alarm_new(const char* name) {
   mock_function_count_map[__func__]++;
-  return nullptr;
+  return (alarm_t*)new uint8_t[30];
 }
 alarm_t* alarm_new_periodic(const char* name) {
   mock_function_count_map[__func__]++;
@@ -404,7 +404,11 @@
 }
 void alarm_cleanup(void) { mock_function_count_map[__func__]++; }
 void alarm_debug_dump(int fd) { mock_function_count_map[__func__]++; }
-void alarm_free(alarm_t* alarm) { mock_function_count_map[__func__]++; }
+void alarm_free(alarm_t* alarm) {
+  uint8_t* ptr = (uint8_t*)alarm;
+  delete[] ptr;
+  mock_function_count_map[__func__]++;
+}
 void alarm_set(alarm_t* alarm, uint64_t interval_ms, alarm_callback_t cb,
                void* data) {
   mock_function_count_map[__func__]++;
diff --git a/system/test/suite/Android.bp b/system/test/suite/Android.bp
index 30f6f20..85bcb22 100644
--- a/system/test/suite/Android.bp
+++ b/system/test/suite/Android.bp
@@ -85,6 +85,7 @@
         "libbt-sbc-encoder",
         "libbt-stack",
         "libbt-utils",
+        "libcom.android.sysprop.bluetooth",
         "libflatbuffers-cpp",
         "libFraunhoferAAC",
         "libg722codec",
diff --git a/system/types/ble_address_with_type.h b/system/types/ble_address_with_type.h
index 38a52cc..250072d 100644
--- a/system/types/ble_address_with_type.h
+++ b/system/types/ble_address_with_type.h
@@ -116,9 +116,19 @@
     return (other & ~kBleAddressIdentityBit) ==
            (type & ~kBleAddressIdentityBit);
   }
+
   std::string ToString() const {
     return std::string(bda.ToString() + "[" + AddressTypeText(type) + "]");
   }
+
+  std::string ToStringForLogging() const {
+    return bda.ToStringForLogging() + "[" + AddressTypeText(type) + "]";
+  }
+
+  std::string ToRedactedStringForLogging() const {
+    return bda.ToRedactedStringForLogging() + "[" + AddressTypeText(type) + "]";
+  }
+
   bool operator==(const tBLE_BD_ADDR rhs) const {
     return rhs.type == type && rhs.bda == bda;
   }
diff --git a/system/types/raw_address.cc b/system/types/raw_address.cc
index afaf4ef..0cd9de0 100644
--- a/system/types/raw_address.cc
+++ b/system/types/raw_address.cc
@@ -38,12 +38,22 @@
   std::copy(mac.begin(), mac.end(), address);
 }
 
-std::string RawAddress::ToString() const {
+std::string RawAddress::ToString() const { return ToColonSepHexString(); }
+
+std::string RawAddress::ToColonSepHexString() const {
   return base::StringPrintf("%02x:%02x:%02x:%02x:%02x:%02x", address[0],
                             address[1], address[2], address[3], address[4],
                             address[5]);
 }
 
+std::string RawAddress::ToStringForLogging() const {
+  return ToColonSepHexString();
+}
+
+std::string RawAddress::ToRedactedStringForLogging() const {
+  return base::StringPrintf("xx:xx:xx:xx:%02x:%02x", address[4], address[5]);
+}
+
 std::array<uint8_t, RawAddress::kLength> RawAddress::ToArray() const {
   std::array<uint8_t, kLength> mac;
   std::copy(std::begin(address), std::end(address), std::begin(mac));
diff --git a/system/types/raw_address.h b/system/types/raw_address.h
index b3e7530..d623b9f 100644
--- a/system/types/raw_address.h
+++ b/system/types/raw_address.h
@@ -46,8 +46,22 @@
 
   bool IsEmpty() const { return *this == kEmpty; }
 
+  // TODO (b/258090765): remove it and
+  // replace its usage with ToColonSepHexString
   std::string ToString() const;
 
+  // Return a string representation in the form of
+  // hexadecimal string separated by colon (:), e.g.,
+  // "12:34:56:ab:cd:ef"
+  std::string ToColonSepHexString() const;
+  // same as ToColonSepHexString
+  std::string ToStringForLogging() const;
+
+  // Similar with ToColonHexString, ToRedactedStringForLogging returns a
+  // colon separated hexadecimal reprentation of the address but, with the
+  // leftmost 4 bytes masked with "xx", e.g., "xx:xx:xx:xx:ab:cd".
+  std::string ToRedactedStringForLogging() const;
+
   // Converts |string| to RawAddress and places it in |to|. If |from| does
   // not represent a Bluetooth address, |to| is not modified and this function
   // returns false. Otherwise, it returns true.
diff --git a/system/types/test/raw_address_unittest.cc b/system/types/test/raw_address_unittest.cc
index 3a34295..513c8b7 100644
--- a/system/types/test/raw_address_unittest.cc
+++ b/system/types/test/raw_address_unittest.cc
@@ -198,3 +198,14 @@
   std::array<uint8_t, 6> mac2 = bdaddr.ToArray();
   ASSERT_EQ(mac, mac2);
 }
+
+TEST(RawAddress, ToStringForLoggingTest) {
+  std::array<uint8_t, 6> addr_bytes = {0x11, 0x22, 0x33, 0x44, 0x55, 0xab};
+  RawAddress addr(addr_bytes);
+  const std::string redacted_loggable_str = "xx:xx:xx:xx:55:ab";
+  const std::string loggbable_str = "11:22:33:44:55:ab";
+  std::string ret1 = addr.ToStringForLogging();
+  ASSERT_STREQ(ret1.c_str(), loggbable_str.c_str());
+  std::string ret2 = addr.ToRedactedStringForLogging();
+  ASSERT_STREQ(ret2.c_str(), redacted_loggable_str.c_str());
+}
diff --git a/tools/rootcanal/model/hci/h4_data_channel_packetizer.cc b/tools/rootcanal/model/hci/h4_data_channel_packetizer.cc
index ad103ba..3f73949 100644
--- a/tools/rootcanal/model/hci/h4_data_channel_packetizer.cc
+++ b/tools/rootcanal/model/hci/h4_data_channel_packetizer.cc
@@ -39,7 +39,7 @@
     PacketReadCallback sco_cb, PacketReadCallback iso_cb,
     ClientDisconnectCallback disconnect_cb)
     : uart_socket_(socket),
-      h4_parser_(command_cb, event_cb, acl_cb, sco_cb, iso_cb),
+      h4_parser_(command_cb, event_cb, acl_cb, sco_cb, iso_cb, true),
       disconnect_cb_(std::move(disconnect_cb)) {}
 
 size_t H4DataChannelPacketizer::Send(uint8_t type, const uint8_t* data,
diff --git a/tools/rootcanal/model/hci/h4_parser.cc b/tools/rootcanal/model/hci/h4_parser.cc
index 1471f1e..0498c99 100644
--- a/tools/rootcanal/model/hci/h4_parser.cc
+++ b/tools/rootcanal/model/hci/h4_parser.cc
@@ -16,8 +16,8 @@
 
 #include "model/hci/h4_parser.h"  // for H4Parser, PacketType, H4Pars...
 
-#include <stddef.h>  // for size_t
-
+#include <array>
+#include <cstddef>     // for size_t
 #include <cstdint>     // for uint8_t, int32_t
 #include <functional>  // for function
 #include <utility>     // for move
@@ -60,12 +60,13 @@
 
 H4Parser::H4Parser(PacketReadCallback command_cb, PacketReadCallback event_cb,
                    PacketReadCallback acl_cb, PacketReadCallback sco_cb,
-                   PacketReadCallback iso_cb)
+                   PacketReadCallback iso_cb, bool enable_recovery_state)
     : command_cb_(std::move(command_cb)),
       event_cb_(std::move(event_cb)),
       acl_cb_(std::move(acl_cb)),
       sco_cb_(std::move(sco_cb)),
-      iso_cb_(std::move(iso_cb)) {}
+      iso_cb_(std::move(iso_cb)),
+      enable_recovery_state_(enable_recovery_state) {}
 
 void H4Parser::OnPacketReady() {
   switch (hci_packet_type_) {
@@ -95,6 +96,7 @@
 size_t H4Parser::BytesRequested() {
   switch (state_) {
     case HCI_TYPE:
+    case HCI_RECOVERY:
       return 1;
     case HCI_PREAMBLE:
     case HCI_PAYLOAD:
@@ -102,7 +104,7 @@
   }
 }
 
-bool H4Parser::Consume(uint8_t* buffer, int32_t bytes_read) {
+bool H4Parser::Consume(const uint8_t* buffer, int32_t bytes_read) {
   size_t bytes_to_read = BytesRequested();
   if (bytes_read <= 0) {
     LOG_INFO("remote disconnected, or unhandled error?");
@@ -128,6 +130,36 @@
       packet_type_ = *buffer;
       packet_.clear();
       break;
+
+    case HCI_RECOVERY: {
+      // Skip all received bytes until the HCI Reset command is received.
+      // The parser can end up in a bad state when the host is restarted.
+      const std::array<uint8_t, 4> reset_command{0x01, 0x03, 0x0c, 0x00};
+      size_t offset = packet_.size();
+      LOG_WARN("Received byte in recovery state : 0x%x",
+               static_cast<unsigned>(*buffer));
+      packet_.push_back(*buffer);
+
+      // Last byte does not match expected byte in the sequence.
+      // Drop all the bytes and start over.
+      if (packet_[offset] != reset_command[offset]) {
+        packet_.clear();
+        // The mismatched byte can also be the first of the correct sequence.
+        if (*buffer == reset_command[0]) {
+          packet_.push_back(*buffer);
+        }
+      }
+
+      // Received full reset command.
+      if (packet_.size() == reset_command.size()) {
+        LOG_INFO("Received HCI Reset command, exiting recovery state");
+        // Pop the Idc from the received packet.
+        packet_.erase(packet_.begin());
+        bytes_wanted_ = 0;
+      }
+      break;
+    }
+
     case HCI_PREAMBLE:
     case HCI_PAYLOAD:
       packet_.insert(packet_.end(), buffer, buffer + bytes_read);
@@ -143,13 +175,21 @@
           hci_packet_type_ != PacketType::COMMAND &&
           hci_packet_type_ != PacketType::EVENT &&
           hci_packet_type_ != PacketType::ISO) {
-        LOG_ALWAYS_FATAL("Unimplemented packet type %hhd", packet_type_);
+        if (!enable_recovery_state_) {
+          LOG_ALWAYS_FATAL("Received invalid packet type 0x%x",
+                           static_cast<unsigned>(packet_type_));
+        }
+        LOG_ERROR("Received invalid packet type 0x%x, entering recovery state",
+                  static_cast<unsigned>(packet_type_));
+        state_ = HCI_RECOVERY;
+        hci_packet_type_ = PacketType::COMMAND;
+        bytes_wanted_ = 1;
+      } else {
+        state_ = HCI_PREAMBLE;
+        bytes_wanted_ = preamble_size[static_cast<size_t>(hci_packet_type_)];
       }
-      state_ = HCI_PREAMBLE;
-      bytes_wanted_ = preamble_size[static_cast<size_t>(hci_packet_type_)];
       break;
     case HCI_PREAMBLE:
-
       if (bytes_wanted_ == 0) {
         size_t payload_size =
             HciGetPacketLengthForType(hci_packet_type_, packet_.data());
@@ -162,6 +202,7 @@
         }
       }
       break;
+    case HCI_RECOVERY:
     case HCI_PAYLOAD:
       if (bytes_wanted_ == 0) {
         OnPacketReady();
diff --git a/tools/rootcanal/model/hci/h4_parser.h b/tools/rootcanal/model/hci/h4_parser.h
index 694448c..1a85760 100644
--- a/tools/rootcanal/model/hci/h4_parser.h
+++ b/tools/rootcanal/model/hci/h4_parser.h
@@ -45,14 +45,14 @@
 // The parser keeps internal state and is not thread safe.
 class H4Parser {
  public:
-  enum State { HCI_TYPE, HCI_PREAMBLE, HCI_PAYLOAD };
+  enum State { HCI_TYPE, HCI_PREAMBLE, HCI_PAYLOAD, HCI_RECOVERY };
 
   H4Parser(PacketReadCallback command_cb, PacketReadCallback event_cb,
            PacketReadCallback acl_cb, PacketReadCallback sco_cb,
-           PacketReadCallback iso_cb);
+           PacketReadCallback iso_cb, bool enable_recovery_state = false);
 
   // Consumes the given number of bytes, returns true on success.
-  bool Consume(uint8_t* buffer, int32_t bytes);
+  bool Consume(const uint8_t* buffer, int32_t bytes);
 
   // The maximum number of bytes the parser can consume in the current state.
   size_t BytesRequested();
@@ -62,13 +62,15 @@
 
   State CurrentState() { return state_; };
 
+  void EnableRecovery() { enable_recovery_state_ = true; }
+  void DisableRecovery() { enable_recovery_state_ = false; }
+
  private:
   void OnPacketReady();
 
   // 2 bytes for opcode, 1 byte for parameter length (Volume 2, Part E, 5.4.1)
   static constexpr size_t COMMAND_PREAMBLE_SIZE = 3;
   static constexpr size_t COMMAND_LENGTH_OFFSET = 2;
-
   // 2 bytes for handle, 2 bytes for data length (Volume 2, Part E, 5.4.2)
   static constexpr size_t ACL_PREAMBLE_SIZE = 4;
   static constexpr size_t ACL_LENGTH_OFFSET = 2;
@@ -101,13 +103,28 @@
   uint8_t packet_type_{};
   std::vector<uint8_t> packet_;
   size_t bytes_wanted_{0};
+  bool enable_recovery_state_{false};
 };
 
 inline std::ostream& operator<<(std::ostream& os,
                                 H4Parser::State const& state_) {
-  os << (state_ == H4Parser::State::HCI_TYPE       ? "HCI_TYPE"
-         : state_ == H4Parser::State::HCI_PREAMBLE ? "HCI_PREAMBLE"
-                                                   : "HCI_PAYLOAD");
+  switch (state_) {
+    case H4Parser::State::HCI_TYPE:
+      os << "HCI_TYPE";
+      break;
+    case H4Parser::State::HCI_PREAMBLE:
+      os << "HCI_PREAMBLE";
+      break;
+    case H4Parser::State::HCI_PAYLOAD:
+      os << "HCI_PAYLOAD";
+      break;
+    case H4Parser::State::HCI_RECOVERY:
+      os << "HCI_RECOVERY";
+      break;
+    default:
+      os << "unknown state " << static_cast<int>(state_);
+      break;
+  }
   return os;
 }
 
diff --git a/tools/rootcanal/test/h4_parser_unittest.cc b/tools/rootcanal/test/h4_parser_unittest.cc
index 07a80c5..9e4b59e 100644
--- a/tools/rootcanal/test/h4_parser_unittest.cc
+++ b/tools/rootcanal/test/h4_parser_unittest.cc
@@ -18,6 +18,8 @@
 
 #include <gtest/gtest.h>
 
+#include <array>
+
 namespace rootcanal {
 using PacketData = std::vector<uint8_t>;
 
@@ -57,6 +59,7 @@
         type_ = PacketType::ISO;
         PacketReadCallback(p);
       },
+      true,
   };
   PacketData packet_;
   PacketType type_;
@@ -109,9 +112,10 @@
 }
 
 TEST_F(H4ParserTest, WrongTypeIsDeath) {
+  parser_.DisableRecovery();
   PacketData bad_bit({0xfd});
   ASSERT_DEATH(parser_.Consume(bad_bit.data(), bad_bit.size()),
-               "Unimplemented packet type.*");
+               "Received invalid packet type.*");
 }
 
 TEST_F(H4ParserTest, CallsTheRightCallbacks) {
@@ -139,4 +143,43 @@
   }
 }
 
+TEST_F(H4ParserTest, Recovery) {
+  // Validate that the recovery state is exited only after receiving the
+  // HCI Reset command.
+  parser_.EnableRecovery();
+
+  // Enter recovery state after receiving an invalid packet type.
+  uint8_t invalid_packet_type = 0xfd;
+  ASSERT_TRUE(parser_.Consume(&invalid_packet_type, 1));
+  ASSERT_EQ(parser_.CurrentState(), H4Parser::State::HCI_RECOVERY);
+
+  const std::array<uint8_t, 4> reset_command{0x01, 0x03, 0x0c, 0x00};
+
+  // Send prefixes of the HCI Reset command, restarting over from the start.
+  for (size_t n = 1; n < 4; n++) {
+    for (size_t i = 0; i < n; i++) {
+      ASSERT_TRUE(parser_.Consume(&reset_command[i], 1));
+      ASSERT_EQ(parser_.CurrentState(), H4Parser::State::HCI_RECOVERY);
+    }
+  }
+
+  // Finally send the full HCI Reset command.
+  for (size_t i = 0; i < 4; i++) {
+    ASSERT_EQ(parser_.CurrentState(), H4Parser::State::HCI_RECOVERY);
+    ASSERT_TRUE(parser_.Consume(&reset_command[i], 1));
+  }
+
+  // Validate that the HCI recovery state is exited,
+  // and the HCI Reset command correctly received on the command callback.
+  ASSERT_EQ(parser_.CurrentState(), H4Parser::State::HCI_TYPE);
+  ASSERT_LT(0, (int)packet_.size());
+
+  // Validate that the HCI Reset command was correctly received.
+  ASSERT_EQ(type_, PacketType::COMMAND);
+  ASSERT_EQ(packet_.size(), reset_command.size() - 1);
+  for (size_t i = 1; i < packet_.size(); i++) {
+    ASSERT_EQ(packet_[i - 1], reset_command[i]);
+  }
+}
+
 }  // namespace rootcanal