Snap for 9267292 from 45f55e3b579e4684dcffed31b36d01581f6ec47e to mainline-networking-release

Change-Id: I0c8b317b8d4a565c7a9d2552991d479b510b6c66
diff --git a/android/BluetoothLegacyMigration/Android.bp b/android/BluetoothLegacyMigration/Android.bp
new file mode 100644
index 0000000..b755fa0
--- /dev/null
+++ b/android/BluetoothLegacyMigration/Android.bp
@@ -0,0 +1,14 @@
+package {
+    // See: http://go/android-license-faq
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_app {
+    name: "BluetoothLegacyMigration",
+
+    srcs: [ "BluetoothLegacyMigration.kt" ],
+
+    // Must match Bluetooth.apk certificate because of sharedUserId
+    certificate: ":com.android.bluetooth.certificate",
+    platform_apis: true,
+}
diff --git a/android/BluetoothLegacyMigration/AndroidManifest.xml b/android/BluetoothLegacyMigration/AndroidManifest.xml
new file mode 100644
index 0000000..86989f9
--- /dev/null
+++ b/android/BluetoothLegacyMigration/AndroidManifest.xml
@@ -0,0 +1,20 @@
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.bluetooth"
+    android:sharedUserId="android.uid.bluetooth">
+
+    <!-- This "legacy" instance is retained on the device to preserve the
+        database contents before Bluetooth was migrated into a Mainline module.
+        This ensures that we can migrate information to new folder app -->
+<application
+    android:icon="@mipmap/bt_share"
+    android:allowBackup="false"
+    android:label="Bluetooth Legacy">
+        <provider
+            android:name="com.google.android.bluetooth.BluetoothLegacyMigration"
+            android:authorities="bluetooth_legacy.provider"
+            android:directBootAware="true"
+            android:exported="false"
+            android:permission="android.permission.BLUETOOTH_PRIVILEGED"
+            />
+    </application>
+</manifest>
diff --git a/android/BluetoothLegacyMigration/BluetoothLegacyMigration.kt b/android/BluetoothLegacyMigration/BluetoothLegacyMigration.kt
new file mode 100644
index 0000000..9c88a06
--- /dev/null
+++ b/android/BluetoothLegacyMigration/BluetoothLegacyMigration.kt
@@ -0,0 +1,259 @@
+/*
+ * 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.google.android.bluetooth
+
+import android.content.ContentProvider
+import android.content.ContentValues
+import android.content.Context
+import android.content.UriMatcher
+import android.database.Cursor
+import android.database.sqlite.SQLiteDatabase
+import android.net.Uri
+import android.os.Bundle
+import android.util.Log
+
+/**
+ * Define an implementation of ContentProvider for the Bluetooth migration
+ */
+class BluetoothLegacyMigration: ContentProvider() {
+    companion object {
+        private const val TAG = "BluetoothLegacyMigration"
+
+        private const val AUTHORITY = "bluetooth_legacy.provider"
+
+        private const val START_LEGACY_MIGRATION_CALL = "start_legacy_migration"
+        private const val FINISH_LEGACY_MIGRATION_CALL = "finish_legacy_migration"
+
+        private const val PHONEBOOK_ACCESS_PERMISSION = "phonebook_access_permission"
+        private const val MESSAGE_ACCESS_PERMISSION = "message_access_permission"
+        private const val SIM_ACCESS_PERMISSION = "sim_access_permission"
+
+        private const val VOLUME_MAP = "bluetooth_volume_map"
+
+        private const val OPP = "OPPMGR"
+        private const val BLUETOOTH_OPP_CHANNEL = "btopp_channels"
+        private const val BLUETOOTH_OPP_NAME = "btopp_names"
+
+        private const val BLUETOOTH_SIGNED_DEFAULT = "com.google.android.bluetooth_preferences"
+
+        private const val KEY_LIST = "key_list"
+
+        private enum class UriId(
+            val fileName: String,
+            val handler: (ctx: Context) -> DatabaseHandler
+        ) {
+            BLUETOOTH(BluetoothDatabase.DATABASE_NAME, ::BluetoothDatabase),
+            OPP(OppDatabase.DATABASE_NAME, ::OppDatabase),
+        }
+
+        private val URI_MATCHER = UriMatcher(UriMatcher.NO_MATCH).apply {
+            UriId.values().map { addURI(AUTHORITY, it.fileName, it.ordinal) }
+        }
+
+        private fun putObjectInBundle(bundle: Bundle, key: String, obj: Any?) {
+            when (obj) {
+                is Boolean -> bundle.putBoolean(key, obj)
+                is Int -> bundle.putInt(key, obj)
+                is Long -> bundle.putLong(key, obj)
+                is String -> bundle.putString(key, obj)
+                null -> throw UnsupportedOperationException("null type is not handled")
+                else -> throw UnsupportedOperationException("${obj.javaClass.simpleName}: type is not handled")
+            }
+        }
+    }
+
+    private lateinit var mContext: Context
+
+    /**
+     * Always return true, indicating that the
+     * provider loaded correctly.
+     */
+    override fun onCreate(): Boolean {
+        mContext = context!!.createDeviceProtectedStorageContext()
+        return true
+    }
+
+    /**
+     * Use a content URI to get database name associated
+     *
+     * @param uri Content uri
+     * @return A {@link Cursor} containing the results of the query.
+     */
+    override fun getType(uri: Uri): String {
+        val database = UriId.values().firstOrNull { it.ordinal == URI_MATCHER.match(uri) }
+            ?: throw UnsupportedOperationException("This Uri is not supported: $uri")
+        return database.fileName
+    }
+
+    /**
+     * Use a content URI to get information about a database
+     *
+     * @param uri Content uri
+     * @param projection unused
+     * @param selection unused
+     * @param selectionArgs unused
+     * @param sortOrder unused
+     * @return A {@link Cursor} containing the results of the query.
+     *
+     */
+    @Override
+    override fun query(
+        uri: Uri,
+        projection: Array<String>?,
+        selection: String?,
+        selectionArgs: Array<String>?,
+        sortOrder: String?
+    ): Cursor? {
+        val database = UriId.values().firstOrNull { it.ordinal == URI_MATCHER.match(uri) }
+            ?: throw UnsupportedOperationException("This Uri is not supported: $uri")
+        return database.handler(mContext).toCursor()
+    }
+
+    /**
+     * insert() is not supported
+     */
+    override fun insert(uri: Uri, values: ContentValues?): Uri? {
+        throw UnsupportedOperationException()
+    }
+
+    /**
+     * delete() is not supported
+     */
+    override fun delete(uri: Uri, selection: String?, selectionArgs: Array<String>?): Int {
+        throw UnsupportedOperationException()
+    }
+
+    /**
+     * update() is not supported
+     */
+    override fun update(
+        uri: Uri, values: ContentValues?, selection: String?, selectionArgs: Array<String>?
+    ): Int {
+        throw UnsupportedOperationException()
+    }
+
+    abstract class MigrationHandler {
+        abstract fun toBundle(): Bundle?
+        abstract fun delete()
+    }
+
+    private class SharedPreferencesHandler(private val ctx: Context, private val key: String) :
+        MigrationHandler() {
+
+        override fun toBundle(): Bundle? {
+            val pref = ctx.getSharedPreferences(key, Context.MODE_PRIVATE)
+            if (pref.all.isEmpty()) {
+                Log.d(TAG, "No migration needed for shared preference: $key")
+                return null
+            }
+            val bundle = Bundle()
+            val keys = arrayListOf<String>()
+            for (e in pref.all) {
+                keys += e.key
+                putObjectInBundle(bundle, e.key, e.value)
+            }
+            bundle.putStringArrayList(KEY_LIST, keys)
+            Log.d(TAG, "SharedPreferences migrating ${keys.size} key(s) from $key")
+            return bundle
+        }
+
+        override fun delete() {
+            ctx.deleteSharedPreferences(key)
+            Log.d(TAG, "$key: SharedPreferences deleted")
+        }
+    }
+
+    abstract class DatabaseHandler(private val ctx: Context, private val dbName: String) :
+        MigrationHandler() {
+
+        abstract val sql: String
+
+        fun toCursor(): Cursor? {
+            val databasePath = ctx.getDatabasePath(dbName)
+            if (!databasePath.exists()) {
+                Log.d(TAG, "No migration needed for database: $dbName")
+                return null
+            }
+            val db = SQLiteDatabase.openDatabase(
+                databasePath,
+                SQLiteDatabase.OpenParams.Builder().addOpenFlags(SQLiteDatabase.OPEN_READONLY)
+                    .build()
+            )
+            return db.rawQuery(sql, null)
+        }
+
+        override fun toBundle(): Bundle? {
+            throw UnsupportedOperationException()
+        }
+
+        override fun delete() {
+            val databasePath = ctx.getDatabasePath(dbName)
+            databasePath.delete()
+            Log.d(TAG, "$dbName: database deleted")
+        }
+    }
+
+    private class BluetoothDatabase(ctx: Context) : DatabaseHandler(ctx, DATABASE_NAME) {
+        companion object {
+            const val DATABASE_NAME = "bluetooth_db"
+        }
+        private val dbTable = "metadata"
+        override val sql = "select * from $dbTable"
+    }
+
+    private class OppDatabase(ctx: Context) : DatabaseHandler(ctx, DATABASE_NAME) {
+        companion object {
+            const val DATABASE_NAME = "btopp.db"
+        }
+        private val dbTable = "btopp"
+        override val sql = "select * from $dbTable"
+    }
+
+    /**
+     * Fetch legacy data describe by {@code arg} and perform {@code method} action on it
+     *
+     * @param method Action to perform. One of START_LEGACY_MIGRATION_CALL|FINISH_LEGACY_MIGRATION_CALL
+     * @param arg item on witch to perform the action specified by {@code method}
+     * @param extras unused
+     * @return A {@link Bundle} containing the results of the query.
+     */
+    override fun call(method: String, arg: String?, extras: Bundle?): Bundle? {
+        val migrationHandler = when (arg) {
+            OPP,
+            VOLUME_MAP,
+            BLUETOOTH_OPP_NAME,
+            BLUETOOTH_OPP_CHANNEL,
+            SIM_ACCESS_PERMISSION,
+            MESSAGE_ACCESS_PERMISSION,
+            PHONEBOOK_ACCESS_PERMISSION -> SharedPreferencesHandler(mContext, arg)
+            BLUETOOTH_SIGNED_DEFAULT -> {
+                val key = mContext.packageName + "_preferences"
+                SharedPreferencesHandler(mContext, key)
+            }
+            BluetoothDatabase.DATABASE_NAME -> BluetoothDatabase(mContext)
+            OppDatabase.DATABASE_NAME -> OppDatabase(mContext)
+            else -> throw UnsupportedOperationException()
+        }
+        return when (method) {
+            START_LEGACY_MIGRATION_CALL -> migrationHandler.toBundle()
+            FINISH_LEGACY_MIGRATION_CALL -> {
+                migrationHandler.delete()
+                return null
+            }
+            else -> throw UnsupportedOperationException()
+        }
+    }
+}
diff --git a/android/BluetoothLegacyMigration/OWNERS b/android/BluetoothLegacyMigration/OWNERS
new file mode 100644
index 0000000..8f5c903
--- /dev/null
+++ b/android/BluetoothLegacyMigration/OWNERS
@@ -0,0 +1,8 @@
+# Reviewers for /android/BluetoothLegacyMigration
+
+eruffieux@google.com
+rahulsabnis@google.com
+sattiraju@google.com
+siyuanh@google.com
+wescande@google.com
+zachoverflow@google.com
diff --git a/android/BluetoothLegacyMigration/res/mipmap-anydpi/bt_share.xml b/android/BluetoothLegacyMigration/res/mipmap-anydpi/bt_share.xml
new file mode 100644
index 0000000..ff00b0d
--- /dev/null
+++ b/android/BluetoothLegacyMigration/res/mipmap-anydpi/bt_share.xml
@@ -0,0 +1,28 @@
+<!--
+   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
+  -->
+<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
+  <foreground>
+    <inset
+      android:drawable="@*android:drawable/ic_bluetooth_share_icon"
+      android:insetTop="25%"
+      android:insetRight="25%"
+      android:insetBottom="25%"
+      android:insetLeft="25%" />
+  </foreground>
+  <background>
+    <color android:color="#e9ddd4" />
+  </background>
+</adaptive-icon>
diff --git a/android/app/jni/com_android_bluetooth_gatt.cpp b/android/app/jni/com_android_bluetooth_gatt.cpp
index 82aa164..c6196bc 100644
--- a/android/app/jni/com_android_bluetooth_gatt.cpp
+++ b/android/app/jni/com_android_bluetooth_gatt.cpp
@@ -199,6 +199,7 @@
  */
 
 void btgattc_register_app_cb(int status, int clientIf, const Uuid& app_uuid) {
+  std::shared_lock<std::shared_mutex> lock(callbacks_mutex);
   CallbackEnv sCallbackEnv(__func__);
   if (!sCallbackEnv.valid()) return;
   sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onClientRegistered, status,
@@ -212,6 +213,7 @@
                             uint16_t periodic_adv_int,
                             std::vector<uint8_t> adv_data,
                             RawAddress* original_bda) {
+  std::shared_lock<std::shared_mutex> lock(callbacks_mutex);
   CallbackEnv sCallbackEnv(__func__);
   if (!sCallbackEnv.valid()) return;
 
@@ -233,6 +235,7 @@
 
 void btgattc_open_cb(int conn_id, int status, int clientIf,
                      const RawAddress& bda) {
+  std::shared_lock<std::shared_mutex> lock(callbacks_mutex);
   CallbackEnv sCallbackEnv(__func__);
   if (!sCallbackEnv.valid()) return;
 
@@ -244,6 +247,7 @@
 
 void btgattc_close_cb(int conn_id, int status, int clientIf,
                       const RawAddress& bda) {
+  std::shared_lock<std::shared_mutex> lock(callbacks_mutex);
   CallbackEnv sCallbackEnv(__func__);
   if (!sCallbackEnv.valid()) return;
 
@@ -254,6 +258,7 @@
 }
 
 void btgattc_search_complete_cb(int conn_id, int status) {
+  std::shared_lock<std::shared_mutex> lock(callbacks_mutex);
   CallbackEnv sCallbackEnv(__func__);
   if (!sCallbackEnv.valid()) return;
 
@@ -263,6 +268,7 @@
 
 void btgattc_register_for_notification_cb(int conn_id, int registered,
                                           int status, uint16_t handle) {
+  std::shared_lock<std::shared_mutex> lock(callbacks_mutex);
   CallbackEnv sCallbackEnv(__func__);
   if (!sCallbackEnv.valid()) return;
 
@@ -271,6 +277,7 @@
 }
 
 void btgattc_notify_cb(int conn_id, const btgatt_notify_params_t& p_data) {
+  std::shared_lock<std::shared_mutex> lock(callbacks_mutex);
   CallbackEnv sCallbackEnv(__func__);
   if (!sCallbackEnv.valid()) return;
 
@@ -288,6 +295,7 @@
 
 void btgattc_read_characteristic_cb(int conn_id, int status,
                                     btgatt_read_params_t* p_data) {
+  std::shared_lock<std::shared_mutex> lock(callbacks_mutex);
   CallbackEnv sCallbackEnv(__func__);
   if (!sCallbackEnv.valid()) return;
 
@@ -308,6 +316,7 @@
 
 void btgattc_write_characteristic_cb(int conn_id, int status, uint16_t handle,
                                      uint16_t len, const uint8_t* value) {
+  std::shared_lock<std::shared_mutex> lock(callbacks_mutex);
   CallbackEnv sCallbackEnv(__func__);
   if (!sCallbackEnv.valid()) return;
 
@@ -319,6 +328,7 @@
 }
 
 void btgattc_execute_write_cb(int conn_id, int status) {
+  std::shared_lock<std::shared_mutex> lock(callbacks_mutex);
   CallbackEnv sCallbackEnv(__func__);
   if (!sCallbackEnv.valid()) return;
 
@@ -328,6 +338,7 @@
 
 void btgattc_read_descriptor_cb(int conn_id, int status,
                                 const btgatt_read_params_t& p_data) {
+  std::shared_lock<std::shared_mutex> lock(callbacks_mutex);
   CallbackEnv sCallbackEnv(__func__);
   if (!sCallbackEnv.valid()) return;
 
@@ -346,6 +357,7 @@
 
 void btgattc_write_descriptor_cb(int conn_id, int status, uint16_t handle,
                                  uint16_t len, const uint8_t* value) {
+  std::shared_lock<std::shared_mutex> lock(callbacks_mutex);
   CallbackEnv sCallbackEnv(__func__);
   if (!sCallbackEnv.valid()) return;
 
@@ -358,6 +370,7 @@
 
 void btgattc_remote_rssi_cb(int client_if, const RawAddress& bda, int rssi,
                             int status) {
+  std::shared_lock<std::shared_mutex> lock(callbacks_mutex);
   CallbackEnv sCallbackEnv(__func__);
   if (!sCallbackEnv.valid()) return;
 
@@ -369,6 +382,7 @@
 }
 
 void btgattc_configure_mtu_cb(int conn_id, int status, int mtu) {
+  std::shared_lock<std::shared_mutex> lock(callbacks_mutex);
   CallbackEnv sCallbackEnv(__func__);
   if (!sCallbackEnv.valid()) return;
   sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onConfigureMTU, conn_id,
@@ -376,6 +390,7 @@
 }
 
 void btgattc_congestion_cb(int conn_id, bool congested) {
+  std::shared_lock<std::shared_mutex> lock(callbacks_mutex);
   CallbackEnv sCallbackEnv(__func__);
   if (!sCallbackEnv.valid()) return;
   sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onClientCongestion,
@@ -384,6 +399,7 @@
 
 void btgattc_batchscan_reports_cb(int client_if, int status, int report_format,
                                   int num_records, std::vector<uint8_t> data) {
+  std::shared_lock<std::shared_mutex> lock(callbacks_mutex);
   CallbackEnv sCallbackEnv(__func__);
   if (!sCallbackEnv.valid()) return;
   ScopedLocalRef<jbyteArray> jb(sCallbackEnv.get(),
@@ -396,6 +412,7 @@
 }
 
 void btgattc_batchscan_threshold_cb(int client_if) {
+  std::shared_lock<std::shared_mutex> lock(callbacks_mutex);
   CallbackEnv sCallbackEnv(__func__);
   if (!sCallbackEnv.valid()) return;
   sCallbackEnv->CallVoidMethod(mCallbacksObj,
@@ -403,6 +420,7 @@
 }
 
 void btgattc_track_adv_event_cb(btgatt_track_adv_info_t* p_adv_track_info) {
+  std::shared_lock<std::shared_mutex> lock(callbacks_mutex);
   CallbackEnv sCallbackEnv(__func__);
   if (!sCallbackEnv.valid()) return;
 
@@ -506,6 +524,7 @@
 
 void btgattc_get_gatt_db_cb(int conn_id, const btgatt_db_element_t* db,
                             int count) {
+  std::shared_lock<std::shared_mutex> lock(callbacks_mutex);
   CallbackEnv sCallbackEnv(__func__);
   if (!sCallbackEnv.valid()) return;
 
@@ -525,6 +544,7 @@
 
 void btgattc_phy_updated_cb(int conn_id, uint8_t tx_phy, uint8_t rx_phy,
                             uint8_t status) {
+  std::shared_lock<std::shared_mutex> lock(callbacks_mutex);
   CallbackEnv sCallbackEnv(__func__);
   if (!sCallbackEnv.valid()) return;
 
@@ -534,6 +554,7 @@
 
 void btgattc_conn_updated_cb(int conn_id, uint16_t interval, uint16_t latency,
                              uint16_t timeout, uint8_t status) {
+  std::shared_lock<std::shared_mutex> lock(callbacks_mutex);
   CallbackEnv sCallbackEnv(__func__);
   if (!sCallbackEnv.valid()) return;
 
@@ -542,6 +563,7 @@
 }
 
 void btgattc_service_changed_cb(int conn_id) {
+  std::shared_lock<std::shared_mutex> lock(callbacks_mutex);
   CallbackEnv sCallbackEnv(__func__);
   if (!sCallbackEnv.valid()) return;
 
@@ -583,6 +605,7 @@
  */
 
 void btgatts_register_app_cb(int status, int server_if, const Uuid& uuid) {
+  std::shared_lock<std::shared_mutex> lock(callbacks_mutex);
   CallbackEnv sCallbackEnv(__func__);
   if (!sCallbackEnv.valid()) return;
   sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onServerRegistered, status,
@@ -591,6 +614,7 @@
 
 void btgatts_connection_cb(int conn_id, int server_if, int connected,
                            const RawAddress& bda) {
+  std::shared_lock<std::shared_mutex> lock(callbacks_mutex);
   CallbackEnv sCallbackEnv(__func__);
   if (!sCallbackEnv.valid()) return;
 
@@ -603,6 +627,7 @@
 void btgatts_service_added_cb(int status, int server_if,
                               const btgatt_db_element_t* service,
                               size_t service_count) {
+  std::shared_lock<std::shared_mutex> lock(callbacks_mutex);
   CallbackEnv sCallbackEnv(__func__);
   if (!sCallbackEnv.valid()) return;
 
@@ -620,6 +645,7 @@
 }
 
 void btgatts_service_stopped_cb(int status, int server_if, int srvc_handle) {
+  std::shared_lock<std::shared_mutex> lock(callbacks_mutex);
   CallbackEnv sCallbackEnv(__func__);
   if (!sCallbackEnv.valid()) return;
   sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onServiceStopped, status,
@@ -627,6 +653,7 @@
 }
 
 void btgatts_service_deleted_cb(int status, int server_if, int srvc_handle) {
+  std::shared_lock<std::shared_mutex> lock(callbacks_mutex);
   CallbackEnv sCallbackEnv(__func__);
   if (!sCallbackEnv.valid()) return;
   sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onServiceDeleted, status,
@@ -637,6 +664,7 @@
                                             const RawAddress& bda,
                                             int attr_handle, int offset,
                                             bool is_long) {
+  std::shared_lock<std::shared_mutex> lock(callbacks_mutex);
   CallbackEnv sCallbackEnv(__func__);
   if (!sCallbackEnv.valid()) return;
 
@@ -650,6 +678,7 @@
 void btgatts_request_read_descriptor_cb(int conn_id, int trans_id,
                                         const RawAddress& bda, int attr_handle,
                                         int offset, bool is_long) {
+  std::shared_lock<std::shared_mutex> lock(callbacks_mutex);
   CallbackEnv sCallbackEnv(__func__);
   if (!sCallbackEnv.valid()) return;
 
@@ -666,6 +695,7 @@
                                              bool need_rsp, bool is_prep,
                                              const uint8_t* value,
                                              size_t length) {
+  std::shared_lock<std::shared_mutex> lock(callbacks_mutex);
   CallbackEnv sCallbackEnv(__func__);
   if (!sCallbackEnv.valid()) return;
 
@@ -685,6 +715,7 @@
                                          int offset, bool need_rsp,
                                          bool is_prep, const uint8_t* value,
                                          size_t length) {
+  std::shared_lock<std::shared_mutex> lock(callbacks_mutex);
   CallbackEnv sCallbackEnv(__func__);
   if (!sCallbackEnv.valid()) return;
 
@@ -701,6 +732,7 @@
 
 void btgatts_request_exec_write_cb(int conn_id, int trans_id,
                                    const RawAddress& bda, int exec_write) {
+  std::shared_lock<std::shared_mutex> lock(callbacks_mutex);
   CallbackEnv sCallbackEnv(__func__);
   if (!sCallbackEnv.valid()) return;
 
@@ -711,6 +743,7 @@
 }
 
 void btgatts_response_confirmation_cb(int status, int handle) {
+  std::shared_lock<std::shared_mutex> lock(callbacks_mutex);
   CallbackEnv sCallbackEnv(__func__);
   if (!sCallbackEnv.valid()) return;
   sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onResponseSendCompleted,
@@ -718,6 +751,7 @@
 }
 
 void btgatts_indication_sent_cb(int conn_id, int status) {
+  std::shared_lock<std::shared_mutex> lock(callbacks_mutex);
   CallbackEnv sCallbackEnv(__func__);
   if (!sCallbackEnv.valid()) return;
   sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onNotificationSent,
@@ -725,6 +759,7 @@
 }
 
 void btgatts_congestion_cb(int conn_id, bool congested) {
+  std::shared_lock<std::shared_mutex> lock(callbacks_mutex);
   CallbackEnv sCallbackEnv(__func__);
   if (!sCallbackEnv.valid()) return;
   sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onServerCongestion,
@@ -732,6 +767,7 @@
 }
 
 void btgatts_mtu_changed_cb(int conn_id, int mtu) {
+  std::shared_lock<std::shared_mutex> lock(callbacks_mutex);
   CallbackEnv sCallbackEnv(__func__);
   if (!sCallbackEnv.valid()) return;
   sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onServerMtuChanged,
@@ -740,6 +776,7 @@
 
 void btgatts_phy_updated_cb(int conn_id, uint8_t tx_phy, uint8_t rx_phy,
                             uint8_t status) {
+  std::shared_lock<std::shared_mutex> lock(callbacks_mutex);
   CallbackEnv sCallbackEnv(__func__);
   if (!sCallbackEnv.valid()) return;
 
@@ -749,6 +786,7 @@
 
 void btgatts_conn_updated_cb(int conn_id, uint16_t interval, uint16_t latency,
                              uint16_t timeout, uint8_t status) {
+  std::shared_lock<std::shared_mutex> lock(callbacks_mutex);
   CallbackEnv sCallbackEnv(__func__);
   if (!sCallbackEnv.valid()) return;
 
@@ -890,6 +928,7 @@
 
   void OnScannerRegistered(const Uuid app_uuid, uint8_t scannerId,
                            uint8_t status) {
+    std::shared_lock<std::shared_mutex> lock(callbacks_mutex);
     CallbackEnv sCallbackEnv(__func__);
     if (!sCallbackEnv.valid()) return;
     sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onScannerRegistered,
@@ -897,6 +936,7 @@
   }
 
   void OnSetScannerParameterComplete(uint8_t scannerId, uint8_t status) {
+    std::shared_lock<std::shared_mutex> lock(callbacks_mutex);
     CallbackEnv sCallbackEnv(__func__);
     if (!sCallbackEnv.valid()) return;
     sCallbackEnv->CallVoidMethod(
@@ -907,6 +947,7 @@
                     uint8_t primary_phy, uint8_t secondary_phy,
                     uint8_t advertising_sid, int8_t tx_power, int8_t rssi,
                     uint16_t periodic_adv_int, std::vector<uint8_t> adv_data) {
+    std::shared_lock<std::shared_mutex> lock(callbacks_mutex);
     CallbackEnv sCallbackEnv(__func__);
     if (!sCallbackEnv.valid()) return;
 
@@ -932,6 +973,7 @@
   }
 
   void OnTrackAdvFoundLost(AdvertisingTrackInfo track_info) {
+    std::shared_lock<std::shared_mutex> lock(callbacks_mutex);
     CallbackEnv sCallbackEnv(__func__);
     if (!sCallbackEnv.valid()) return;
 
@@ -973,6 +1015,7 @@
 
   void OnBatchScanReports(int client_if, int status, int report_format,
                           int num_records, std::vector<uint8_t> data) {
+    std::shared_lock<std::shared_mutex> lock(callbacks_mutex);
     CallbackEnv sCallbackEnv(__func__);
     if (!sCallbackEnv.valid()) return;
     ScopedLocalRef<jbyteArray> jb(sCallbackEnv.get(),
@@ -986,6 +1029,7 @@
   }
 
   void OnBatchScanThresholdCrossed(int client_if) {
+    std::shared_lock<std::shared_mutex> lock(callbacks_mutex);
     CallbackEnv sCallbackEnv(__func__);
     if (!sCallbackEnv.valid()) return;
     sCallbackEnv->CallVoidMethod(mCallbacksObj,
@@ -996,6 +1040,7 @@
                              uint8_t sid, uint8_t address_type,
                              RawAddress address, uint8_t phy,
                              uint16_t interval) override {
+    std::shared_lock<std::shared_mutex> lock(callbacks_mutex);
     CallbackEnv sCallbackEnv(__func__);
     if (!sCallbackEnv.valid()) return;
     if (!mPeriodicScanCallbacksObj) {
@@ -1013,6 +1058,7 @@
   void OnPeriodicSyncReport(uint16_t sync_handle, int8_t tx_power, int8_t rssi,
                             uint8_t data_status,
                             std::vector<uint8_t> data) override {
+    std::shared_lock<std::shared_mutex> lock(callbacks_mutex);
     CallbackEnv sCallbackEnv(__func__);
     if (!sCallbackEnv.valid()) return;
 
@@ -1027,6 +1073,7 @@
   }
 
   void OnPeriodicSyncLost(uint16_t sync_handle) override {
+    std::shared_lock<std::shared_mutex> lock(callbacks_mutex);
     CallbackEnv sCallbackEnv(__func__);
     if (!sCallbackEnv.valid()) return;
 
@@ -1036,6 +1083,7 @@
 
   void OnPeriodicSyncTransferred(int pa_source, uint8_t status,
                                  RawAddress address) override {
+    std::shared_lock<std::shared_mutex> lock(callbacks_mutex);
     CallbackEnv sCallbackEnv(__func__);
     if (!sCallbackEnv.valid()) return;
     if (!mPeriodicScanCallbacksObj) {
@@ -1168,6 +1216,7 @@
 static const bt_interface_t* btIf;
 
 static void initializeNative(JNIEnv* env, jobject object) {
+  std::unique_lock<std::shared_mutex> lock(callbacks_mutex);
   if (btIf) return;
 
   btIf = getBluetoothInterface();
@@ -1210,6 +1259,8 @@
 }
 
 static void cleanupNative(JNIEnv* env, jobject object) {
+  std::unique_lock<std::shared_mutex> lock(callbacks_mutex);
+
   if (!btIf) return;
 
   if (sGattIf != NULL) {
@@ -1250,6 +1301,7 @@
 
 void btgattc_register_scanner_cb(const Uuid& app_uuid, uint8_t scannerId,
                                  uint8_t status) {
+  std::shared_lock<std::shared_mutex> lock(callbacks_mutex);
   CallbackEnv sCallbackEnv(__func__);
   if (!sCallbackEnv.valid()) return;
   sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onScannerRegistered,
@@ -1305,6 +1357,7 @@
 
 static void readClientPhyCb(uint8_t clientIf, RawAddress bda, uint8_t tx_phy,
                             uint8_t rx_phy, uint8_t status) {
+  std::shared_lock<std::shared_mutex> lock(callbacks_mutex);
   CallbackEnv sCallbackEnv(__func__);
   if (!sCallbackEnv.valid()) return;
 
@@ -1451,6 +1504,7 @@
 }
 
 void set_scan_params_cmpl_cb(int client_if, uint8_t status) {
+  std::shared_lock<std::shared_mutex> lock(callbacks_mutex);
   CallbackEnv sCallbackEnv(__func__);
   if (!sCallbackEnv.valid()) return;
   sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onScanParamSetupCompleted,
@@ -1468,6 +1522,7 @@
 
 void scan_filter_param_cb(uint8_t client_if, uint8_t avbl_space, uint8_t action,
                           uint8_t status) {
+  std::shared_lock<std::shared_mutex> lock(callbacks_mutex);
   CallbackEnv sCallbackEnv(__func__);
   if (!sCallbackEnv.valid()) return;
   sCallbackEnv->CallVoidMethod(mCallbacksObj,
@@ -1547,6 +1602,7 @@
 static void scan_filter_cfg_cb(uint8_t client_if, uint8_t filt_type,
                                uint8_t avbl_space, uint8_t action,
                                uint8_t status) {
+  std::shared_lock<std::shared_mutex> lock(callbacks_mutex);
   CallbackEnv sCallbackEnv(__func__);
   if (!sCallbackEnv.valid()) return;
   sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onScanFilterConfig, action,
@@ -1697,6 +1753,7 @@
 }
 
 void scan_enable_cb(uint8_t client_if, uint8_t action, uint8_t status) {
+  std::shared_lock<std::shared_mutex> lock(callbacks_mutex);
   CallbackEnv sCallbackEnv(__func__);
   if (!sCallbackEnv.valid()) return;
   sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onScanFilterEnableDisabled,
@@ -1729,6 +1786,7 @@
 }
 
 void batchscan_cfg_storage_cb(uint8_t client_if, uint8_t status) {
+  std::shared_lock<std::shared_mutex> lock(callbacks_mutex);
   CallbackEnv sCallbackEnv(__func__);
   if (!sCallbackEnv.valid()) return;
   sCallbackEnv->CallVoidMethod(
@@ -1746,6 +1804,7 @@
 }
 
 void batchscan_enable_cb(uint8_t client_if, uint8_t status) {
+  std::shared_lock<std::shared_mutex> lock(callbacks_mutex);
   CallbackEnv sCallbackEnv(__func__);
   if (!sCallbackEnv.valid()) return;
   sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onBatchScanStartStopped,
@@ -1820,6 +1879,7 @@
 
 static void readServerPhyCb(uint8_t serverIf, RawAddress bda, uint8_t tx_phy,
                             uint8_t rx_phy, uint8_t status) {
+  std::shared_lock<std::shared_mutex> lock(callbacks_mutex);
   CallbackEnv sCallbackEnv(__func__);
   if (!sCallbackEnv.valid()) return;
 
@@ -2000,7 +2060,7 @@
 }
 
 static void advertiseInitializeNative(JNIEnv* env, jobject object) {
-  std::shared_lock<std::shared_mutex> lock(callbacks_mutex);
+  std::unique_lock<std::shared_mutex> lock(callbacks_mutex);
   if (mAdvertiseCallbacksObj != NULL) {
     ALOGW("Cleaning up Advertise callback object");
     env->DeleteGlobalRef(mAdvertiseCallbacksObj);
@@ -2011,7 +2071,7 @@
 }
 
 static void advertiseCleanupNative(JNIEnv* env, jobject object) {
-  std::shared_lock<std::shared_mutex> lock(callbacks_mutex);
+  std::unique_lock<std::shared_mutex> lock(callbacks_mutex);
   if (mAdvertiseCallbacksObj != NULL) {
     env->DeleteGlobalRef(mAdvertiseCallbacksObj);
     mAdvertiseCallbacksObj = NULL;
@@ -2100,6 +2160,7 @@
 
 static void ble_advertising_set_started_cb(int reg_id, uint8_t advertiser_id,
                                            int8_t tx_power, uint8_t status) {
+  std::shared_lock<std::shared_mutex> lock(callbacks_mutex);
   CallbackEnv sCallbackEnv(__func__);
   if (!sCallbackEnv.valid()) return;
   sCallbackEnv->CallVoidMethod(mAdvertiseCallbacksObj,
@@ -2109,6 +2170,7 @@
 
 static void ble_advertising_set_timeout_cb(uint8_t advertiser_id,
                                            uint8_t status) {
+  std::shared_lock<std::shared_mutex> lock(callbacks_mutex);
   CallbackEnv sCallbackEnv(__func__);
   if (!sCallbackEnv.valid()) return;
   sCallbackEnv->CallVoidMethod(mAdvertiseCallbacksObj,
@@ -2160,6 +2222,7 @@
 
 static void getOwnAddressCb(uint8_t advertiser_id, uint8_t address_type,
                             RawAddress address) {
+  std::shared_lock<std::shared_mutex> lock(callbacks_mutex);
   CallbackEnv sCallbackEnv(__func__);
   if (!sCallbackEnv.valid()) return;
 
@@ -2178,6 +2241,7 @@
 
 static void callJniCallback(jmethodID method, uint8_t advertiser_id,
                             uint8_t status) {
+  std::shared_lock<std::shared_mutex> lock(callbacks_mutex);
   CallbackEnv sCallbackEnv(__func__);
   if (!sCallbackEnv.valid()) return;
   sCallbackEnv->CallVoidMethod(mAdvertiseCallbacksObj, method, advertiser_id,
@@ -2185,6 +2249,7 @@
 }
 
 static void enableSetCb(uint8_t advertiser_id, bool enable, uint8_t status) {
+  std::shared_lock<std::shared_mutex> lock(callbacks_mutex);
   CallbackEnv sCallbackEnv(__func__);
   if (!sCallbackEnv.valid()) return;
   sCallbackEnv->CallVoidMethod(mAdvertiseCallbacksObj,
@@ -2224,6 +2289,7 @@
 
 static void setAdvertisingParametersNativeCb(uint8_t advertiser_id,
                                              uint8_t status, int8_t tx_power) {
+  std::shared_lock<std::shared_mutex> lock(callbacks_mutex);
   CallbackEnv sCallbackEnv(__func__);
   if (!sCallbackEnv.valid()) return;
   sCallbackEnv->CallVoidMethod(mAdvertiseCallbacksObj,
@@ -2268,6 +2334,7 @@
 
 static void enablePeriodicSetCb(uint8_t advertiser_id, bool enable,
                                 uint8_t status) {
+  std::shared_lock<std::shared_mutex> lock(callbacks_mutex);
   CallbackEnv sCallbackEnv(__func__);
   if (!sCallbackEnv.valid()) return;
   sCallbackEnv->CallVoidMethod(mAdvertiseCallbacksObj,
@@ -2295,6 +2362,7 @@
 }
 
 static void periodicScanInitializeNative(JNIEnv* env, jobject object) {
+  std::unique_lock<std::shared_mutex> lock(callbacks_mutex);
   if (mPeriodicScanCallbacksObj != NULL) {
     ALOGW("Cleaning up periodic scan callback object");
     env->DeleteGlobalRef(mPeriodicScanCallbacksObj);
@@ -2305,6 +2373,7 @@
 }
 
 static void periodicScanCleanupNative(JNIEnv* env, jobject object) {
+  std::unique_lock<std::shared_mutex> lock(callbacks_mutex);
   if (mPeriodicScanCallbacksObj != NULL) {
     env->DeleteGlobalRef(mPeriodicScanCallbacksObj);
     mPeriodicScanCallbacksObj = NULL;
diff --git a/android/app/src/com/android/bluetooth/BluetoothMethodProxy.java b/android/app/src/com/android/bluetooth/BluetoothMethodProxy.java
index 5710451..622f776 100644
--- a/android/app/src/com/android/bluetooth/BluetoothMethodProxy.java
+++ b/android/app/src/com/android/bluetooth/BluetoothMethodProxy.java
@@ -17,6 +17,7 @@
 package com.android.bluetooth;
 
 import android.content.ContentResolver;
+import android.content.ContentValues;
 import android.content.Context;
 import android.database.Cursor;
 import android.net.Uri;
@@ -75,6 +76,23 @@
     }
 
     /**
+     * Proxies {@link ContentResolver#delete(Uri, String, String[])}.
+     */
+    public int contentResolverDelete(ContentResolver contentResolver, final Uri url,
+            final String where,
+            final String[] selectionArgs) {
+        return contentResolver.delete(url, where, selectionArgs);
+    }
+
+    /**
+     * Proxies {@link ContentResolver#update(Uri, ContentValues, String, String[])}.
+     */
+    public int contentResolverUpdate(ContentResolver contentResolver, final Uri contentUri,
+            final ContentValues contentValues, String where, String[] selectionArgs) {
+        return contentResolver.update(contentUri, contentValues, where, selectionArgs);
+    }
+
+    /**
      * Proxies {@link HeaderSet#getHeader}.
      */
     public Object getHeader(HeaderSet headerSet, int headerId) throws IOException {
diff --git a/android/app/src/com/android/bluetooth/avrcpcontroller/BrowseTree.java b/android/app/src/com/android/bluetooth/avrcpcontroller/BrowseTree.java
index 508c68c..ecfd441 100644
--- a/android/app/src/com/android/bluetooth/avrcpcontroller/BrowseTree.java
+++ b/android/app/src/com/android/bluetooth/avrcpcontroller/BrowseTree.java
@@ -23,6 +23,8 @@
 
 import com.android.bluetooth.Utils;
 
+import com.google.common.annotations.VisibleForTesting;
+
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.HashSet;
@@ -57,7 +59,8 @@
     public static final String PLAYER_PREFIX = "PLAYER";
 
     // Static instance of Folder ID <-> Folder Instance (for navigation purposes)
-    private final HashMap<String, BrowseNode> mBrowseMap = new HashMap<String, BrowseNode>();
+    @VisibleForTesting
+    final HashMap<String, BrowseNode> mBrowseMap = new HashMap<String, BrowseNode>();
     private BrowseNode mCurrentBrowseNode;
     private BrowseNode mCurrentBrowsedPlayer;
     private BrowseNode mCurrentAddressedPlayer;
diff --git a/android/app/src/com/android/bluetooth/btservice/ActiveDeviceManager.java b/android/app/src/com/android/bluetooth/btservice/ActiveDeviceManager.java
index c4be364..dc656d4 100644
--- a/android/app/src/com/android/bluetooth/btservice/ActiveDeviceManager.java
+++ b/android/app/src/com/android/bluetooth/btservice/ActiveDeviceManager.java
@@ -46,9 +46,10 @@
 import com.android.bluetooth.le_audio.LeAudioService;
 import com.android.internal.annotations.VisibleForTesting;
 
-import java.util.LinkedList;
+import java.util.ArrayList;
 import java.util.List;
 import java.util.Objects;
+import java.util.concurrent.RejectedExecutionException;
 
 /**
  * The active device manager is responsible for keeping track of the
@@ -130,14 +131,18 @@
     private Handler mHandler = null;
     private final AudioManager mAudioManager;
     private final AudioManagerAudioDeviceCallback mAudioManagerAudioDeviceCallback;
+    private final AudioManagerOnModeChangedListener mAudioManagerOnModeChangedListener;
 
-    private final List<BluetoothDevice> mA2dpConnectedDevices = new LinkedList<>();
-    private final List<BluetoothDevice> mHfpConnectedDevices = new LinkedList<>();
-    private final List<BluetoothDevice> mHearingAidConnectedDevices = new LinkedList<>();
-    private final List<BluetoothDevice> mLeAudioConnectedDevices = new LinkedList<>();
-    private final List<BluetoothDevice> mLeHearingAidConnectedDevices = new LinkedList<>();
+    private final List<BluetoothDevice> mA2dpConnectedDevices = new ArrayList<>();
+    private final List<BluetoothDevice> mHfpConnectedDevices = new ArrayList<>();
+    private final List<BluetoothDevice> mHearingAidConnectedDevices = new ArrayList<>();
+    private final List<BluetoothDevice> mLeAudioConnectedDevices = new ArrayList<>();
+    private final List<BluetoothDevice> mLeHearingAidConnectedDevices = new ArrayList<>();
+    private final List<BluetoothDevice> mPendingLeHearingAidActiveDevice = new ArrayList<>();
     private BluetoothDevice mA2dpActiveDevice = null;
+    private BluetoothDevice mPendingA2dpActiveDevice = null;
     private BluetoothDevice mHfpActiveDevice = null;
+    private BluetoothDevice mPendingHfpActiveDevice = null;
     private BluetoothDevice mHearingAidActiveDevice = null;
     private BluetoothDevice mLeAudioActiveDevice = null;
     private BluetoothDevice mLeHearingAidActiveDevice = null;
@@ -244,16 +249,43 @@
                         if (mA2dpConnectedDevices.contains(device)) {
                             break;      // The device is already connected
                         }
+                        // New connected A2DP device
                         mA2dpConnectedDevices.add(device);
                         if (mHearingAidActiveDevice == null && mLeHearingAidActiveDevice == null) {
-                            // New connected device: select it as active
-                            setA2dpActiveDevice(device);
-                            setLeAudioActiveDevice(null);
+                            // Lazy active A2DP if it is not being used.
+                            // This will prevent the deactivation of LE audio
+                            // earlier than the activation of HFP.
+                            switch (mAudioManager.getMode()) {
+                                case AudioManager.MODE_NORMAL:
+                                    break;
+                                case AudioManager.MODE_RINGTONE: {
+                                    HeadsetService headsetService = mFactory.getHeadsetService();
+                                    if (mHfpActiveDevice == null && headsetService != null
+                                            && headsetService.isInbandRingingEnabled()) {
+                                        mPendingA2dpActiveDevice = device;
+                                    }
+                                    break;
+                                }
+                                case AudioManager.MODE_IN_CALL:
+                                case AudioManager.MODE_IN_COMMUNICATION:
+                                case AudioManager.MODE_CALL_SCREENING:
+                                case AudioManager.MODE_CALL_REDIRECT:
+                                case AudioManager.MODE_COMMUNICATION_REDIRECT: {
+                                    if (mHfpActiveDevice == null) {
+                                        mPendingA2dpActiveDevice = device;
+                                    }
+                                }
+                            }
+                            if (mPendingA2dpActiveDevice == null) {
+                                // select the device as active if not lazy active
+                                setA2dpActiveDevice(device);
+                                setLeAudioActiveDevice(null);
+                            }
                         }
                         break;
                     }
                     if (prevState == BluetoothProfile.STATE_CONNECTED) {
-                        // Device disconnected
+                        // A2DP device disconnected
                         if (DBG) {
                             Log.d(TAG,
                                     "handleMessage(MESSAGE_A2DP_ACTION_CONNECTION_STATE_CHANGED): "
@@ -282,6 +314,9 @@
                         setHearingAidActiveDevice(null);
                         setLeAudioActiveDevice(null);
                     }
+                    if (mHfpConnectedDevices.contains(device)) {
+                        setHfpActiveDevice(device);
+                    }
                     // Just assign locally the new value
                     mA2dpActiveDevice = device;
                 }
@@ -307,16 +342,37 @@
                         if (mHfpConnectedDevices.contains(device)) {
                             break;      // The device is already connected
                         }
+                        // New connected HFP device.
                         mHfpConnectedDevices.add(device);
                         if (mHearingAidActiveDevice == null && mLeHearingAidActiveDevice == null) {
-                            // New connected device: select it as active
-                            setHfpActiveDevice(device);
-                            setLeAudioActiveDevice(null);
+                            // Lazy active HFP if it is not being used.
+                            // This will prevent the deactivation of LE audio
+                            // earlier than the activation of A2DP.
+                            switch (mAudioManager.getMode()) {
+                                case AudioManager.MODE_NORMAL:
+                                    if (mA2dpActiveDevice == null) {
+                                        mPendingHfpActiveDevice = device;
+                                    }
+                                    break;
+                                case AudioManager.MODE_RINGTONE: {
+                                    HeadsetService headsetService = mFactory.getHeadsetService();
+                                    if (headsetService == null
+                                            || !headsetService.isInbandRingingEnabled()) {
+                                        mPendingHfpActiveDevice = device;
+                                    }
+                                    break;
+                                }
+                            }
+                            if (mPendingHfpActiveDevice == null) {
+                                // select the device as active if not lazy active
+                                setHfpActiveDevice(device);
+                                setLeAudioActiveDevice(null);
+                            }
                         }
                         break;
                     }
                     if (prevState == BluetoothProfile.STATE_CONNECTED) {
-                        // Device disconnected
+                        // HFP device disconnected
                         if (DBG) {
                             Log.d(TAG,
                                     "handleMessage(MESSAGE_HFP_ACTION_CONNECTION_STATE_CHANGED): "
@@ -345,6 +401,9 @@
                         setHearingAidActiveDevice(null);
                         setLeAudioActiveDevice(null);
                     }
+                    if (mA2dpConnectedDevices.contains(device)) {
+                        setA2dpActiveDevice(device);
+                    }
                     // Just assign locally the new value
                     mHfpActiveDevice = device;
                 }
@@ -370,7 +429,7 @@
                             break;      // The device is already connected
                         }
                         mHearingAidConnectedDevices.add(device);
-                        // New connected device: select it as active
+                        // New connected hearing aid device: select it as active
                         setHearingAidActiveDevice(device);
                         setA2dpActiveDevice(null);
                         setHfpActiveDevice(null);
@@ -378,7 +437,7 @@
                         break;
                     }
                     if (prevState == BluetoothProfile.STATE_CONNECTED) {
-                        // Device disconnected
+                        // Hearing aid device disconnected
                         if (DBG) {
                             Log.d(TAG, "handleMessage(MESSAGE_HEARING_AID_ACTION_CONNECTION_STATE"
                                     + "_CHANGED): device " + device + " disconnected");
@@ -432,21 +491,28 @@
                             break;      // The device is already connected
                         }
                         mLeAudioConnectedDevices.add(device);
-                        if (mHearingAidActiveDevice == null && mLeHearingAidActiveDevice == null) {
-                            // New connected device: select it as active
+                        if (mHearingAidActiveDevice == null && mLeHearingAidActiveDevice == null
+                                && mPendingLeHearingAidActiveDevice.isEmpty()) {
+                            // New connected LE audio device: select it as active
                             setLeAudioActiveDevice(device);
                             setA2dpActiveDevice(null);
                             setHfpActiveDevice(null);
+                        } else if (mPendingLeHearingAidActiveDevice.contains(device)) {
+                            setLeHearingAidActiveDevice(device);
+                            setHearingAidActiveDevice(null);
+                            setA2dpActiveDevice(null);
+                            setHfpActiveDevice(null);
                         }
                         break;
                     }
                     if (prevState == BluetoothProfile.STATE_CONNECTED) {
-                        // Device disconnected
+                        // LE audio device disconnected
                         if (DBG) {
                             Log.d(TAG, "handleMessage(MESSAGE_LE_AUDIO_ACTION_CONNECTION_STATE"
                                     + "_CHANGED): device " + device + " disconnected");
                         }
                         mLeAudioConnectedDevices.remove(device);
+                        mLeHearingAidConnectedDevices.remove(device);
                         if (Objects.equals(mLeAudioActiveDevice, device)) {
                             if (mLeAudioConnectedDevices.isEmpty()) {
                                 setLeAudioActiveDevice(null);
@@ -499,34 +565,28 @@
                         }
                         mLeHearingAidConnectedDevices.add(device);
                         if (!mLeAudioConnectedDevices.contains(device)) {
-                            mLeAudioConnectedDevices.add(device);
-                        }
-                        // New connected device: select it as active
-                        setLeAudioActiveDevice(device);
-                        if (mLeAudioActiveDevice == device) {
-                            // setLeAudioActiveDevice succeed
+                            mPendingLeHearingAidActiveDevice.add(device);
+                        } else if (Objects.equals(mLeAudioActiveDevice, device)) {
                             mLeHearingAidActiveDevice = device;
+                        } else {
+                            // New connected LE hearing aid device: select it as active
+                            setLeHearingAidActiveDevice(device);
+                            setHearingAidActiveDevice(null);
+                            setA2dpActiveDevice(null);
+                            setHfpActiveDevice(null);
                         }
-                        setA2dpActiveDevice(null);
-                        setHfpActiveDevice(null);
                         break;
                     }
                     if (prevState == BluetoothProfile.STATE_CONNECTED) {
-                        // Device disconnected
+                        // LE hearing aid device disconnected
                         if (DBG) {
                             Log.d(TAG, "handleMessage(MESSAGE_HAP_ACTION_CONNECTION_STATE"
                                     + "_CHANGED): device " + device + " disconnected");
                         }
                         mLeHearingAidConnectedDevices.remove(device);
-                        mLeAudioConnectedDevices.remove(device);
-                        // mLeAudioConnectedDevices should contain all of
-                        // mLeHearingAidConnectedDevices. Call setLeAudioActiveDevice(null)
-                        // only if there are no LE audio devices.
-                        if (Objects.equals(mLeHearingAidConnectedDevices, device)) {
-                            if (mLeAudioConnectedDevices.isEmpty()) {
-                                setLeAudioActiveDevice(null);
-                            }
-                            setFallbackDeviceActive();
+                        mPendingLeHearingAidActiveDevice.remove(device);
+                        if (Objects.equals(mLeHearingAidActiveDevice, device)) {
+                            mLeHearingAidActiveDevice = null;
                         }
                     }
                 }
@@ -536,8 +596,8 @@
                     Intent intent = (Intent) msg.obj;
                     BluetoothDevice device =
                             intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
-                    if (device != null && !mLeAudioConnectedDevices.contains(device)) {
-                        mLeAudioConnectedDevices.add(device);
+                    if (device != null && !mLeHearingAidConnectedDevices.contains(device)) {
+                        mLeHearingAidConnectedDevices.add(device);
                     }
                     if (DBG) {
                         Log.d(TAG, "handleMessage(MESSAGE_HAP_ACTION_ACTIVE_DEVICE_CHANGED): "
@@ -556,6 +616,40 @@
         }
     }
 
+    private class AudioManagerOnModeChangedListener implements AudioManager.OnModeChangedListener {
+        public void onModeChanged(int mode) {
+            switch (mode) {
+                case AudioManager.MODE_NORMAL: {
+                    if (mPendingA2dpActiveDevice != null) {
+                        setA2dpActiveDevice(mPendingA2dpActiveDevice);
+                        setLeAudioActiveDevice(null);
+                    }
+                    break;
+                }
+                case AudioManager.MODE_RINGTONE: {
+                    final HeadsetService headsetService = mFactory.getHeadsetService();
+                    if (headsetService != null && headsetService.isInbandRingingEnabled()
+                            && mPendingHfpActiveDevice != null) {
+                        setHfpActiveDevice(mPendingHfpActiveDevice);
+                        setLeAudioActiveDevice(null);
+                    }
+                    break;
+                }
+                case AudioManager.MODE_IN_CALL:
+                case AudioManager.MODE_IN_COMMUNICATION:
+                case AudioManager.MODE_CALL_SCREENING:
+                case AudioManager.MODE_CALL_REDIRECT:
+                case AudioManager.MODE_COMMUNICATION_REDIRECT:  {
+                    if (mPendingHfpActiveDevice != null) {
+                        setHfpActiveDevice(mPendingHfpActiveDevice);
+                        setLeAudioActiveDevice(null);
+                    }
+                    break;
+                }
+            }
+        }
+    }
+
     /** Notifications of audio device connection and disconnection events. */
     @SuppressLint("AndroidFrameworkRequiresPermission")
     private class AudioManagerAudioDeviceCallback extends AudioDeviceCallback {
@@ -602,6 +696,7 @@
         mFactory = factory;
         mAudioManager = service.getSystemService(AudioManager.class);
         mAudioManagerAudioDeviceCallback = new AudioManagerAudioDeviceCallback();
+        mAudioManagerOnModeChangedListener = new AudioManagerOnModeChangedListener();
     }
 
     void start() {
@@ -628,6 +723,11 @@
         mAdapterService.registerReceiver(mReceiver, filter);
 
         mAudioManager.registerAudioDeviceCallback(mAudioManagerAudioDeviceCallback, mHandler);
+        mAudioManager.addOnModeChangedListener(command -> {
+            if (!mHandler.post(command)) {
+                throw new RejectedExecutionException(mHandler + " is shutting down");
+            }
+        }, mAudioManagerOnModeChangedListener);
     }
 
     void cleanup() {
@@ -670,6 +770,10 @@
             return;
         }
         mA2dpActiveDevice = device;
+        mPendingA2dpActiveDevice = null;
+        if (mPendingHfpActiveDevice != null) {
+            setHfpActiveDevice(mPendingHfpActiveDevice);
+        }
     }
 
     @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
@@ -685,6 +789,10 @@
             return;
         }
         mHfpActiveDevice = device;
+        mPendingHfpActiveDevice = null;
+        if (mPendingA2dpActiveDevice != null) {
+            setA2dpActiveDevice(mPendingA2dpActiveDevice);
+        }
     }
 
     private void setHearingAidActiveDevice(BluetoothDevice device) {
@@ -715,6 +823,18 @@
         mLeAudioActiveDevice = device;
         if (device == null) {
             mLeHearingAidActiveDevice = null;
+            mPendingLeHearingAidActiveDevice.remove(device);
+        }
+    }
+
+    private void setLeHearingAidActiveDevice(BluetoothDevice device) {
+        if (!Objects.equals(mLeAudioActiveDevice, device)) {
+            setLeAudioActiveDevice(device);
+        }
+        if (Objects.equals(mLeAudioActiveDevice, device)) {
+            // setLeAudioActiveDevice succeed
+            mLeHearingAidActiveDevice = device;
+            mPendingLeHearingAidActiveDevice.remove(device);
         }
     }
 
@@ -726,18 +846,34 @@
         if (dbManager == null) {
             return;
         }
-
+        List<BluetoothDevice> connectedHearingAidDevices = new ArrayList<>();
         if (!mHearingAidConnectedDevices.isEmpty()) {
+            connectedHearingAidDevices.addAll(mHearingAidConnectedDevices);
+        }
+        if (!mLeHearingAidConnectedDevices.isEmpty()) {
+            connectedHearingAidDevices.addAll(mLeHearingAidConnectedDevices);
+        }
+        if (!connectedHearingAidDevices.isEmpty()) {
             BluetoothDevice device =
-                    dbManager.getMostRecentlyConnectedDevicesInList(mHearingAidConnectedDevices);
+                    dbManager.getMostRecentlyConnectedDevicesInList(connectedHearingAidDevices);
             if (device != null) {
-                if (DBG) {
-                    Log.d(TAG, "set hearing aid device active: " + device);
+                if (mHearingAidConnectedDevices.contains(device)) {
+                    if (DBG) {
+                        Log.d(TAG, "set hearing aid device active: " + device);
+                    }
+                    setHearingAidActiveDevice(device);
+                    setA2dpActiveDevice(null);
+                    setHfpActiveDevice(null);
+                    setLeAudioActiveDevice(null);
+                } else {
+                    if (DBG) {
+                        Log.d(TAG, "set LE hearing aid device active: " + device);
+                    }
+                    setLeHearingAidActiveDevice(device);
+                    setHearingAidActiveDevice(null);
+                    setA2dpActiveDevice(null);
+                    setHfpActiveDevice(null);
                 }
-                setHearingAidActiveDevice(device);
-                setA2dpActiveDevice(null);
-                setHfpActiveDevice(null);
-                setLeAudioActiveDevice(null);
                 return;
             }
         }
@@ -754,7 +890,7 @@
             headsetFallbackDevice = headsetService.getFallbackDevice();
         }
 
-        List<BluetoothDevice> connectedDevices = new LinkedList<>();
+        List<BluetoothDevice> connectedDevices = new ArrayList<>();
         connectedDevices.addAll(mLeAudioConnectedDevices);
         switch (mAudioManager.getMode()) {
             case AudioManager.MODE_NORMAL:
@@ -821,8 +957,15 @@
         mHfpConnectedDevices.clear();
         mHfpActiveDevice = null;
 
+        mHearingAidConnectedDevices.clear();
         mHearingAidActiveDevice = null;
+
+        mLeAudioConnectedDevices.clear();
         mLeAudioActiveDevice = null;
+
+        mLeHearingAidConnectedDevices.clear();
+        mLeHearingAidActiveDevice = null;
+        mPendingLeHearingAidActiveDevice.clear();
     }
 
     @VisibleForTesting
diff --git a/android/app/src/com/android/bluetooth/btservice/AdapterApp.java b/android/app/src/com/android/bluetooth/btservice/AdapterApp.java
index 0c22c7c..99e3f4f 100644
--- a/android/app/src/com/android/bluetooth/btservice/AdapterApp.java
+++ b/android/app/src/com/android/bluetooth/btservice/AdapterApp.java
@@ -52,6 +52,11 @@
         if (DBG) {
             Log.d(TAG, "onCreate");
         }
+        try {
+            DataMigration.run(this);
+        } catch (Exception e) {
+            Log.e(TAG, "Migration failure: ", e);
+        }
         Config.init(this);
     }
 
diff --git a/android/app/src/com/android/bluetooth/btservice/AdapterService.java b/android/app/src/com/android/bluetooth/btservice/AdapterService.java
index 9199513..fc0b087 100644
--- a/android/app/src/com/android/bluetooth/btservice/AdapterService.java
+++ b/android/app/src/com/android/bluetooth/btservice/AdapterService.java
@@ -205,11 +205,11 @@
     static final String LOCAL_MAC_ADDRESS_PERM = android.Manifest.permission.LOCAL_MAC_ADDRESS;
     static final String RECEIVE_MAP_PERM = android.Manifest.permission.RECEIVE_BLUETOOTH_MAP;
 
-    private static final String PHONEBOOK_ACCESS_PERMISSION_PREFERENCE_FILE =
+    static final String PHONEBOOK_ACCESS_PERMISSION_PREFERENCE_FILE =
             "phonebook_access_permission";
-    private static final String MESSAGE_ACCESS_PERMISSION_PREFERENCE_FILE =
+    static final String MESSAGE_ACCESS_PERMISSION_PREFERENCE_FILE =
             "message_access_permission";
-    private static final String SIM_ACCESS_PERMISSION_PREFERENCE_FILE = "sim_access_permission";
+    static final String SIM_ACCESS_PERMISSION_PREFERENCE_FILE = "sim_access_permission";
 
     private static final int CONTROLLER_ENERGY_UPDATE_TIMEOUT_MILLIS = 30;
 
@@ -5105,6 +5105,8 @@
     private static final String GATT_ROBUST_CACHING_SERVER_FLAG = "INIT_gatt_robust_caching_server";
     private static final String IRK_ROTATION_FLAG = "INIT_irk_rotation";
     private static final String PASS_PHY_UPDATE_CALLBACK_FLAG = "INIT_pass_phy_update_callback";
+    private static final String BTM_DM_FLUSH_DISCOVERY_QUEUE_ON_SEARCH_CANCEL =
+                                    "INIT_btm_dm_flush_discovery_queue_on_search_cancel";
 
     /**
      * Logging flags logic (only applies to DEBUG and VERBOSE levels):
@@ -5172,6 +5174,12 @@
                 DeviceConfig.NAMESPACE_BLUETOOTH, PASS_PHY_UPDATE_CALLBACK_FLAG, true)) {
             initFlags.add(String.format("%s=%s", PASS_PHY_UPDATE_CALLBACK_FLAG, "true"));
         }
+        if (DeviceConfig.getBoolean(
+                DeviceConfig.NAMESPACE_BLUETOOTH,
+                BTM_DM_FLUSH_DISCOVERY_QUEUE_ON_SEARCH_CANCEL, false)) {
+            initFlags.add(String.format("%s=%s",
+                    BTM_DM_FLUSH_DISCOVERY_QUEUE_ON_SEARCH_CANCEL, "true"));
+        }
         if (DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_BLUETOOTH,
                 LOGGING_DEBUG_ENABLED_FOR_ALL_FLAG, false)) {
             initFlags.add(String.format("%s=%s", LOGGING_DEBUG_ENABLED_FOR_ALL_FLAG, "true"));
diff --git a/android/app/src/com/android/bluetooth/btservice/DataMigration.java b/android/app/src/com/android/bluetooth/btservice/DataMigration.java
new file mode 100644
index 0000000..4846d60
--- /dev/null
+++ b/android/app/src/com/android/bluetooth/btservice/DataMigration.java
@@ -0,0 +1,271 @@
+/*
+ * 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.content.ContentProviderClient;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Bundle;
+import android.util.Log;
+
+import com.android.bluetooth.btservice.storage.BluetoothDatabaseMigration;
+import com.android.bluetooth.opp.BluetoothOppProvider;
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.List;
+
+/**
+ * @hide
+ */
+final class DataMigration {
+    private DataMigration(){}
+    private static final String TAG = "DataMigration";
+
+    @VisibleForTesting
+    static final String AUTHORITY = "bluetooth_legacy.provider";
+
+    @VisibleForTesting
+    static final String START_MIGRATION_CALL = "start_legacy_migration";
+    @VisibleForTesting
+    static final String FINISH_MIGRATION_CALL = "finish_legacy_migration";
+
+    @VisibleForTesting
+    static final String BLUETOOTH_DATABASE = "bluetooth_db";
+    @VisibleForTesting
+    static final String OPP_DATABASE = "btopp.db";
+
+    // AvrcpVolumeManager.VOLUME_MAP
+    private static final String VOLUME_MAP_PREFERENCE_FILE = "bluetooth_volume_map";
+    // com.android.blueotooth.opp.Constants.BLUETOOTHOPP_CHANNEL_PREFERENCE
+    private static final String BLUETOOTHOPP_CHANNEL_PREFERENCE = "btopp_channels";
+
+    // com.android.blueotooth.opp.Constants.BLUETOOTHOPP_NAME_PREFERENCE
+    private static final String BLUETOOTHOPP_NAME_PREFERENCE = "btopp_names";
+
+    // com.android.blueotooth.opp.OPP_PREFERENCE_FILE
+    private static final String OPP_PREFERENCE_FILE = "OPPMGR";
+
+    @VisibleForTesting
+    static final String[] sharedPreferencesKeys = {
+        // Bundles of Boolean
+        AdapterService.PHONEBOOK_ACCESS_PERMISSION_PREFERENCE_FILE,
+        AdapterService.MESSAGE_ACCESS_PERMISSION_PREFERENCE_FILE,
+        AdapterService.SIM_ACCESS_PERMISSION_PREFERENCE_FILE,
+
+        // Bundles of Integer
+        VOLUME_MAP_PREFERENCE_FILE,
+        BLUETOOTHOPP_CHANNEL_PREFERENCE,
+
+        // Bundles of String
+        BLUETOOTHOPP_NAME_PREFERENCE,
+
+        // Bundle of Boolean and String
+        OPP_PREFERENCE_FILE,
+    };
+
+    // Main key use for storing all the key in the associate bundle
+    @VisibleForTesting
+    static final String KEY_LIST = "key_list";
+
+    @VisibleForTesting
+    static final String BLUETOOTH_CONFIG = "bluetooth_config";
+    static final String MIGRATION_DONE_PROPERTY = "migration_done";
+    @VisibleForTesting
+    static final String MIGRATION_ATTEMPT_PROPERTY = "migration_attempt";
+
+    @VisibleForTesting
+    public static final int MIGRATION_STATUS_TO_BE_DONE = 0;
+    @VisibleForTesting
+    public static final int MIGRATION_STATUS_COMPLETED = 1;
+    @VisibleForTesting
+    public static final int MIGRATION_STATUS_MISSING_APK = 2;
+    @VisibleForTesting
+    public static final int MIGRATION_STATUS_MAX_ATTEMPT = 3;
+
+    @VisibleForTesting
+    static final int MAX_ATTEMPT = 3;
+
+    static int run(Context ctx) {
+        if (migrationStatus(ctx) == MIGRATION_STATUS_COMPLETED) {
+            Log.d(TAG, "Legacy migration skiped: already completed");
+            return MIGRATION_STATUS_COMPLETED;
+        }
+        if (!isMigrationApkInstalled(ctx)) {
+            Log.d(TAG, "Legacy migration skiped: no migration app installed");
+            markMigrationStatus(ctx, MIGRATION_STATUS_MISSING_APK);
+            return MIGRATION_STATUS_MISSING_APK;
+        }
+        if (!incrementeMigrationAttempt(ctx)) {
+            Log.d(TAG, "Legacy migration skiped: still failing after too many attempt");
+            markMigrationStatus(ctx, MIGRATION_STATUS_MAX_ATTEMPT);
+            return MIGRATION_STATUS_MAX_ATTEMPT;
+        }
+
+        for (String pref: sharedPreferencesKeys) {
+            sharedPreferencesMigration(pref, ctx);
+        }
+        // Migration for DefaultSharedPreferences used in PbapUtils. Contains Long
+        sharedPreferencesMigration(ctx.getPackageName() + "_preferences", ctx);
+
+        bluetoothDatabaseMigration(ctx);
+        oppDatabaseMigration(ctx);
+
+        markMigrationStatus(ctx, MIGRATION_STATUS_COMPLETED);
+        Log.d(TAG, "Legacy migration completed");
+        return MIGRATION_STATUS_COMPLETED;
+    }
+
+    @VisibleForTesting
+    static boolean bluetoothDatabaseMigration(Context ctx) {
+        final String logHeader = BLUETOOTH_DATABASE + ": ";
+        ContentResolver resolver = ctx.getContentResolver();
+        Cursor cursor = resolver.query(
+                Uri.parse("content://" + AUTHORITY + "/" + BLUETOOTH_DATABASE),
+                null, null, null, null);
+        if (cursor == null) {
+            Log.d(TAG, logHeader + "Nothing to migrate");
+            return true;
+        }
+        boolean status = BluetoothDatabaseMigration.run(ctx, cursor);
+        cursor.close();
+        if (status) {
+            resolver.call(AUTHORITY, FINISH_MIGRATION_CALL, BLUETOOTH_DATABASE, null);
+            Log.d(TAG, logHeader + "Migration complete. File is deleted");
+        } else {
+            Log.e(TAG, logHeader + "Invalid data. Incomplete migration. File is not deleted");
+        }
+        return status;
+    }
+
+    @VisibleForTesting
+    static boolean oppDatabaseMigration(Context ctx) {
+        final String logHeader = OPP_DATABASE + ": ";
+        ContentResolver resolver = ctx.getContentResolver();
+        Cursor cursor = resolver.query(
+                Uri.parse("content://" + AUTHORITY + "/" + OPP_DATABASE),
+                null, null, null, null);
+        if (cursor == null) {
+            Log.d(TAG, logHeader + "Nothing to migrate");
+            return true;
+        }
+        boolean status = BluetoothOppProvider.oppDatabaseMigration(ctx, cursor);
+        cursor.close();
+        if (status) {
+            resolver.call(AUTHORITY, FINISH_MIGRATION_CALL, OPP_DATABASE, null);
+            Log.d(TAG, logHeader + "Migration complete. File is deleted");
+        } else {
+            Log.e(TAG, logHeader + "Invalid data. Incomplete migration. File is not deleted");
+        }
+        return status;
+    }
+
+    private static boolean writeObjectToEditor(SharedPreferences.Editor editor, Bundle b,
+            String itemKey) {
+        Object value = b.get(itemKey);
+        if (value == null) {
+            Log.e(TAG, itemKey + ": No value associated with this itemKey");
+            return false;
+        }
+        if (value instanceof Boolean) {
+            editor.putBoolean(itemKey, (Boolean) value);
+        } else if (value instanceof Integer) {
+            editor.putInt(itemKey, (Integer) value);
+        } else if (value instanceof Long) {
+            editor.putLong(itemKey, (Long) value);
+        } else if (value instanceof String) {
+            editor.putString(itemKey, (String) value);
+        } else {
+            Log.e(TAG, itemKey + ": Failed to migrate: "
+                     + value.getClass().getSimpleName() + ": Data type not handled");
+            return false;
+        }
+        return true;
+    }
+
+    @VisibleForTesting
+    static boolean sharedPreferencesMigration(String prefKey, Context ctx) {
+        final String logHeader = "SharedPreferencesMigration - " + prefKey + ": ";
+        ContentResolver resolver = ctx.getContentResolver();
+        Bundle b = resolver.call(AUTHORITY, START_MIGRATION_CALL, prefKey, null);
+        if (b == null) {
+            Log.d(TAG, logHeader + "Nothing to migrate");
+            return true;
+        }
+        List<String> keys = b.getStringArrayList(KEY_LIST);
+        if (keys == null) {
+            Log.e(TAG, logHeader + "Wrong format of bundle: No keys to migrate");
+            return false;
+        }
+        SharedPreferences pref = ctx.getSharedPreferences(prefKey, Context.MODE_PRIVATE);
+        SharedPreferences.Editor editor = pref.edit();
+        boolean status = true;
+        for (String itemKey : keys) {
+            // prevent overriding any user settings if it's a new attempt
+            if (!pref.contains(itemKey)) {
+                status &= writeObjectToEditor(editor, b, itemKey);
+            } else {
+                Log.d(TAG, logHeader + itemKey + ": Already exists, not overriding data.");
+            }
+        }
+        editor.apply();
+        if (status) {
+            resolver.call(AUTHORITY, FINISH_MIGRATION_CALL, prefKey, null);
+            Log.d(TAG, logHeader + "Migration complete. File is deleted");
+        } else {
+            Log.e(TAG, logHeader + "Invalid data. Incomplete migration. File is not deleted");
+        }
+        return status;
+    }
+
+    @VisibleForTesting
+    static int migrationStatus(Context ctx) {
+        SharedPreferences pref = ctx.getSharedPreferences(BLUETOOTH_CONFIG, Context.MODE_PRIVATE);
+        return pref.getInt(MIGRATION_DONE_PROPERTY, MIGRATION_STATUS_TO_BE_DONE);
+    }
+
+    @VisibleForTesting
+    static boolean incrementeMigrationAttempt(Context ctx) {
+        SharedPreferences pref = ctx.getSharedPreferences(BLUETOOTH_CONFIG, Context.MODE_PRIVATE);
+        int currentAttempt = Math.min(pref.getInt(MIGRATION_ATTEMPT_PROPERTY, 0), MAX_ATTEMPT);
+        pref.edit()
+            .putInt(MIGRATION_ATTEMPT_PROPERTY, currentAttempt + 1)
+            .apply();
+        return currentAttempt < MAX_ATTEMPT;
+    }
+
+    @VisibleForTesting
+    static boolean isMigrationApkInstalled(Context ctx) {
+        ContentResolver resolver = ctx.getContentResolver();
+        ContentProviderClient client = resolver.acquireContentProviderClient(AUTHORITY);
+        if (client != null) {
+            client.close();
+            return true;
+        }
+        return false;
+    }
+
+    @VisibleForTesting
+    static void markMigrationStatus(Context ctx, int status) {
+        ctx.getSharedPreferences(BLUETOOTH_CONFIG, Context.MODE_PRIVATE)
+            .edit()
+            .putInt(MIGRATION_DONE_PROPERTY, status)
+            .apply();
+    }
+}
diff --git a/android/app/src/com/android/bluetooth/btservice/storage/BluetoothDatabaseMigration.java b/android/app/src/com/android/bluetooth/btservice/storage/BluetoothDatabaseMigration.java
new file mode 100644
index 0000000..a56c6a4
--- /dev/null
+++ b/android/app/src/com/android/bluetooth/btservice/storage/BluetoothDatabaseMigration.java
@@ -0,0 +1,197 @@
+/*
+ * 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.BluetoothA2dp;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothProfile;
+import android.bluetooth.BluetoothUtils;
+import android.content.Context;
+import android.database.Cursor;
+import android.util.Log;
+import android.util.Pair;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Class for regrouping the migration that occur when going mainline
+ * @hide
+ */
+public final class BluetoothDatabaseMigration {
+    private static final String TAG = "BluetoothDatabaseMigration";
+    /**
+     * @hide
+     */
+    public static boolean run(Context ctx, Cursor cursor) {
+        boolean result = true;
+        MetadataDatabase database = MetadataDatabase.createDatabaseWithoutMigration(ctx);
+        while (cursor.moveToNext()) {
+            try {
+                final String primaryKey = cursor.getString(cursor.getColumnIndexOrThrow("address"));
+                String logKey = BluetoothUtils.toAnonymizedAddress(primaryKey);
+                if (logKey == null) { // handle non device address
+                    logKey = primaryKey;
+                }
+
+                Metadata metadata = new Metadata(primaryKey);
+
+                metadata.migrated = fetchInt(cursor, "migrated") > 0;
+                migrate_a2dpSupportsOptionalCodecs(cursor, logKey, metadata);
+                migrate_a2dpOptionalCodecsEnabled(cursor, logKey, metadata);
+                metadata.last_active_time = fetchInt(cursor, "last_active_time");
+                metadata.is_active_a2dp_device = fetchInt(cursor, "is_active_a2dp_device") > 0;
+                migrate_connectionPolicy(cursor, logKey, metadata);
+                migrate_customizedMeta(cursor, metadata);
+
+                database.insert(metadata);
+                Log.d(TAG, "One item migrated: " + metadata);
+            } catch (IllegalArgumentException e) {
+                Log.e(TAG, "Failed to migrate one item: " + e);
+                result = false;
+            }
+        }
+        return result;
+    }
+
+    private static final List<Pair<Integer, String>> CONNECTION_POLICIES =
+            Arrays.asList(
+            new Pair(BluetoothProfile.A2DP, "a2dp_connection_policy"),
+            new Pair(BluetoothProfile.A2DP_SINK, "a2dp_sink_connection_policy"),
+            new Pair(BluetoothProfile.HEADSET, "hfp_connection_policy"),
+            new Pair(BluetoothProfile.HEADSET_CLIENT, "hfp_client_connection_policy"),
+            new Pair(BluetoothProfile.HID_HOST, "hid_host_connection_policy"),
+            new Pair(BluetoothProfile.PAN, "pan_connection_policy"),
+            new Pair(BluetoothProfile.PBAP, "pbap_connection_policy"),
+            new Pair(BluetoothProfile.PBAP_CLIENT, "pbap_client_connection_policy"),
+            new Pair(BluetoothProfile.MAP, "map_connection_policy"),
+            new Pair(BluetoothProfile.SAP, "sap_connection_policy"),
+            new Pair(BluetoothProfile.HEARING_AID, "hearing_aid_connection_policy"),
+            new Pair(BluetoothProfile.HAP_CLIENT, "hap_client_connection_policy"),
+            new Pair(BluetoothProfile.MAP_CLIENT, "map_client_connection_policy"),
+            new Pair(BluetoothProfile.LE_AUDIO, "le_audio_connection_policy"),
+            new Pair(BluetoothProfile.VOLUME_CONTROL, "volume_control_connection_policy"),
+            new Pair(BluetoothProfile.CSIP_SET_COORDINATOR,
+                "csip_set_coordinator_connection_policy"),
+            new Pair(BluetoothProfile.LE_CALL_CONTROL, "le_call_control_connection_policy"),
+            new Pair(BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT,
+                "bass_client_connection_policy"),
+            new Pair(BluetoothProfile.BATTERY, "battery_connection_policy")
+    );
+
+    private static final List<Pair<Integer, String>> CUSTOMIZED_META_KEYS =
+            Arrays.asList(
+            new Pair(BluetoothDevice.METADATA_MANUFACTURER_NAME, "manufacturer_name"),
+            new Pair(BluetoothDevice.METADATA_MODEL_NAME, "model_name"),
+            new Pair(BluetoothDevice.METADATA_SOFTWARE_VERSION, "software_version"),
+            new Pair(BluetoothDevice.METADATA_HARDWARE_VERSION, "hardware_version"),
+            new Pair(BluetoothDevice.METADATA_COMPANION_APP, "companion_app"),
+            new Pair(BluetoothDevice.METADATA_MAIN_ICON, "main_icon"),
+            new Pair(BluetoothDevice.METADATA_IS_UNTETHERED_HEADSET, "is_untethered_headset"),
+            new Pair(BluetoothDevice.METADATA_UNTETHERED_LEFT_ICON, "untethered_left_icon"),
+            new Pair(BluetoothDevice.METADATA_UNTETHERED_RIGHT_ICON, "untethered_right_icon"),
+            new Pair(BluetoothDevice.METADATA_UNTETHERED_CASE_ICON, "untethered_case_icon"),
+            new Pair(BluetoothDevice.METADATA_UNTETHERED_LEFT_BATTERY,
+                "untethered_left_battery"),
+            new Pair(BluetoothDevice.METADATA_UNTETHERED_RIGHT_BATTERY,
+                "untethered_right_battery"),
+            new Pair(BluetoothDevice.METADATA_UNTETHERED_CASE_BATTERY,
+                "untethered_case_battery"),
+            new Pair(BluetoothDevice.METADATA_UNTETHERED_LEFT_CHARGING,
+                "untethered_left_charging"),
+            new Pair(BluetoothDevice.METADATA_UNTETHERED_RIGHT_CHARGING,
+                "untethered_right_charging"),
+            new Pair(BluetoothDevice.METADATA_UNTETHERED_CASE_CHARGING,
+                    "untethered_case_charging"),
+            new Pair(BluetoothDevice.METADATA_ENHANCED_SETTINGS_UI_URI,
+                    "enhanced_settings_ui_uri"),
+            new Pair(BluetoothDevice.METADATA_DEVICE_TYPE, "device_type"),
+            new Pair(BluetoothDevice.METADATA_MAIN_BATTERY, "main_battery"),
+            new Pair(BluetoothDevice.METADATA_MAIN_CHARGING, "main_charging"),
+            new Pair(BluetoothDevice.METADATA_MAIN_LOW_BATTERY_THRESHOLD,
+                    "main_low_battery_threshold"),
+            new Pair(BluetoothDevice.METADATA_UNTETHERED_LEFT_LOW_BATTERY_THRESHOLD,
+                    "untethered_left_low_battery_threshold"),
+            new Pair(BluetoothDevice.METADATA_UNTETHERED_RIGHT_LOW_BATTERY_THRESHOLD,
+                    "untethered_right_low_battery_threshold"),
+            new Pair(BluetoothDevice.METADATA_UNTETHERED_CASE_LOW_BATTERY_THRESHOLD,
+                    "untethered_case_low_battery_threshold"),
+            new Pair(BluetoothDevice.METADATA_SPATIAL_AUDIO, "spatial_audio"),
+            new Pair(BluetoothDevice.METADATA_FAST_PAIR_CUSTOMIZED_FIELDS,
+                    "fastpair_customized")
+    );
+
+    private static int fetchInt(Cursor cursor, String key) {
+        return cursor.getInt(cursor.getColumnIndexOrThrow(key));
+    }
+
+    private static void migrate_a2dpSupportsOptionalCodecs(Cursor cursor, String logKey,
+            Metadata metadata) {
+        final String key = "a2dpSupportsOptionalCodecs";
+        final List<Integer> allowedValue =  new ArrayList<>(Arrays.asList(
+                BluetoothA2dp.OPTIONAL_CODECS_SUPPORT_UNKNOWN,
+                BluetoothA2dp.OPTIONAL_CODECS_NOT_SUPPORTED,
+                BluetoothA2dp.OPTIONAL_CODECS_SUPPORTED));
+        final int value = fetchInt(cursor, key);
+        if (!allowedValue.contains(value)) {
+            throw new IllegalArgumentException(logKey + ": Bad value for [" + key + "]: " + value);
+        }
+        metadata.a2dpSupportsOptionalCodecs = value;
+    }
+
+    private static void migrate_a2dpOptionalCodecsEnabled(Cursor cursor, String logKey,
+            Metadata metadata) {
+        final String key = "a2dpOptionalCodecsEnabled";
+        final List<Integer> allowedValue =  new ArrayList<>(Arrays.asList(
+                BluetoothA2dp.OPTIONAL_CODECS_PREF_UNKNOWN,
+                BluetoothA2dp.OPTIONAL_CODECS_PREF_DISABLED,
+                BluetoothA2dp.OPTIONAL_CODECS_PREF_ENABLED));
+        final int value = fetchInt(cursor, key);
+        if (!allowedValue.contains(value)) {
+            throw new IllegalArgumentException(logKey + ": Bad value for [" + key + "]: " + value);
+        }
+        metadata.a2dpOptionalCodecsEnabled = value;
+    }
+
+    private static void migrate_connectionPolicy(Cursor cursor, String logKey,
+            Metadata metadata) {
+        final List<Integer> allowedValue =  new ArrayList<>(Arrays.asList(
+                BluetoothProfile.CONNECTION_POLICY_UNKNOWN,
+                BluetoothProfile.CONNECTION_POLICY_FORBIDDEN,
+                BluetoothProfile.CONNECTION_POLICY_ALLOWED));
+        for (Pair<Integer, String> p : CONNECTION_POLICIES) {
+            final int policy = cursor.getInt(cursor.getColumnIndexOrThrow(p.second));
+            if (allowedValue.contains(policy)) {
+                metadata.setProfileConnectionPolicy(p.first, policy);
+            } else {
+                throw new IllegalArgumentException(logKey + ": Bad value for ["
+                        + BluetoothProfile.getProfileName(p.first)
+                        + "]: " + policy);
+            }
+        }
+    }
+
+    private static void migrate_customizedMeta(Cursor cursor, Metadata metadata) {
+        for (Pair<Integer, String> p : CUSTOMIZED_META_KEYS) {
+            final byte[] blob = cursor.getBlob(cursor.getColumnIndexOrThrow(p.second));
+            // There is no specific pattern to check the custom meta data
+            metadata.setCustomizedMeta(p.first, blob);
+        }
+    }
+
+}
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 91e33e0..f79c5fb 100644
--- a/android/app/src/com/android/bluetooth/btservice/storage/Metadata.java
+++ b/android/app/src/com/android/bluetooth/btservice/storage/Metadata.java
@@ -21,17 +21,24 @@
 import android.bluetooth.BluetoothA2dp.OptionalCodecsSupportStatus;
 import android.bluetooth.BluetoothDevice;
 import android.bluetooth.BluetoothProfile;
+import android.bluetooth.BluetoothUtils;
 
 import androidx.annotation.NonNull;
 import androidx.room.Embedded;
 import androidx.room.Entity;
 import androidx.room.PrimaryKey;
 
+import com.android.internal.annotations.VisibleForTesting;
+
 import java.util.ArrayList;
 import java.util.List;
 
+/**
+ * @hide
+ */
 @Entity(tableName = "metadata")
-class Metadata {
+@VisibleForTesting
+public class Metadata {
     @PrimaryKey
     @NonNull
     private String address;
@@ -62,7 +69,11 @@
         is_active_a2dp_device = true;
     }
 
-    String getAddress() {
+    /**
+     * @hide
+     */
+    @VisibleForTesting
+    public String getAddress() {
         return address;
     }
 
@@ -75,7 +86,7 @@
      */
     @NonNull
     public String getAnonymizedAddress() {
-        return "XX:XX:XX" + getAddress().substring(8);
+        return BluetoothUtils.toAnonymizedAddress(address);
     }
 
     void setProfileConnectionPolicy(int profile, int connectionPolicy) {
@@ -148,7 +159,11 @@
         }
     }
 
-    int getProfileConnectionPolicy(int profile) {
+    /**
+     * @hide
+     */
+    @VisibleForTesting
+    public int getProfileConnectionPolicy(int profile) {
         switch (profile) {
             case BluetoothProfile.A2DP:
                 return profileConnectionPolicies.a2dp_connection_policy;
@@ -275,7 +290,11 @@
         }
     }
 
-    byte[] getCustomizedMeta(int key) {
+    /**
+     * @hide
+     */
+    @VisibleForTesting
+    public byte[] getCustomizedMeta(int key) {
         byte[] value = null;
         switch (key) {
             case BluetoothDevice.METADATA_MANUFACTURER_NAME:
@@ -372,7 +391,8 @@
 
     public String toString() {
         StringBuilder builder = new StringBuilder();
-        builder.append(address)
+        builder.append(getAnonymizedAddress())
+            .append(" last_active_time=" + last_active_time)
             .append(" {profile connection policy(")
             .append(profileConnectionPolicies)
             .append("), optional codec(support=")
diff --git a/android/app/src/com/android/bluetooth/gatt/ScanManager.java b/android/app/src/com/android/bluetooth/gatt/ScanManager.java
index 675b899..8718036 100644
--- a/android/app/src/com/android/bluetooth/gatt/ScanManager.java
+++ b/android/app/src/com/android/bluetooth/gatt/ScanManager.java
@@ -282,6 +282,9 @@
         if (client == null) {
             client = mScanNative.getRegularScanClient(scannerId);
         }
+        if (client == null) {
+            client = mScanNative.getSuspendedScanClient(scannerId);
+        }
         sendMessage(MSG_STOP_BLE_SCAN, client);
     }
 
@@ -1127,6 +1130,15 @@
             return null;
         }
 
+        ScanClient getSuspendedScanClient(int scannerId) {
+            for (ScanClient client : mSuspendedScanClients) {
+                if (client.scannerId == scannerId) {
+                    return client;
+                }
+            }
+            return null;
+        }
+
         void stopBatchScan(ScanClient client) {
             mBatchClients.remove(client);
             removeScanFilters(client.scannerId);
diff --git a/android/app/src/com/android/bluetooth/le_audio/LeAudioNativeInterface.java b/android/app/src/com/android/bluetooth/le_audio/LeAudioNativeInterface.java
index 8ff4b79..ba5bcf5 100644
--- a/android/app/src/com/android/bluetooth/le_audio/LeAudioNativeInterface.java
+++ b/android/app/src/com/android/bluetooth/le_audio/LeAudioNativeInterface.java
@@ -29,6 +29,7 @@
 
 import com.android.bluetooth.Utils;
 import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
 
 import java.util.Arrays;
 
@@ -67,6 +68,16 @@
         }
     }
 
+    /**
+     * Set singleton instance.
+     */
+    @VisibleForTesting
+    static void setInstance(LeAudioNativeInterface instance) {
+        synchronized (INSTANCE_LOCK) {
+            sInstance = instance;
+        }
+    }
+
     private byte[] getByteAddress(BluetoothDevice device) {
         if (device == null) {
             return Utils.getBytesFromAddress("00:00:00:00:00:00");
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 856679c..155385b 100644
--- a/android/app/src/com/android/bluetooth/le_audio/LeAudioService.java
+++ b/android/app/src/com/android/bluetooth/le_audio/LeAudioService.java
@@ -326,6 +326,11 @@
         mLeAudioNativeIsInitialized = false;
         mHfpHandoverDevice = null;
 
+        mActiveAudioOutDevice = null;
+        mActiveAudioInDevice = null;
+        mDatabaseManager = null;
+        mLeAudioCodecConfig = null;
+
         // Set the service and BLE devices as inactive
         setLeAudioService(null);
 
diff --git a/android/app/src/com/android/bluetooth/map/BluetoothMapContent.java b/android/app/src/com/android/bluetooth/map/BluetoothMapContent.java
index afa59bd..5c0a88c 100644
--- a/android/app/src/com/android/bluetooth/map/BluetoothMapContent.java
+++ b/android/app/src/com/android/bluetooth/map/BluetoothMapContent.java
@@ -37,6 +37,7 @@
 import android.text.util.Rfc822Tokenizer;
 import android.util.Log;
 
+import com.android.bluetooth.BluetoothMethodProxy;
 import com.android.bluetooth.DeviceWorkArounds;
 import com.android.bluetooth.SignedLongLong;
 import com.android.bluetooth.map.BluetoothMapUtils.TYPE;
@@ -1290,7 +1291,8 @@
         String uriStr = new String(Mms.CONTENT_URI + "/" + id + "/part");
         Uri uriAddress = Uri.parse(uriStr);
         // TODO: maybe use a projection with only "ct" and "text"
-        Cursor c = r.query(uriAddress, null, selection, null, null);
+        Cursor c = BluetoothMethodProxy.getInstance().contentResolverQuery(r, uriAddress, null,
+                selection, null, null);
         try {
             if (c != null && c.moveToFirst()) {
                 do {
@@ -1405,7 +1407,8 @@
         String orderBy = Contacts.DISPLAY_NAME + " ASC";
         Cursor c = null;
         try {
-            c = resolver.query(uri, projection, selection, null, orderBy);
+            c = BluetoothMethodProxy.getInstance().contentResolverQuery(resolver, uri, projection,
+                    selection, null, orderBy);
             if (c != null) {
                 int colIndex = c.getColumnIndex(Contacts.DISPLAY_NAME);
                 if (c.getCount() >= 1) {
@@ -1447,7 +1450,8 @@
             Log.v(TAG, "whereClause is " + whereClause);
         }
         try {
-            cr = r.query(sAllThreadsUri, RECIPIENT_ID_PROJECTION, whereClause, null, null);
+            cr = BluetoothMethodProxy.getInstance().contentResolverQuery(r, sAllThreadsUri,
+                    RECIPIENT_ID_PROJECTION, whereClause, null, null);
             if (cr != null && cr.moveToFirst()) {
                 recipientIds = cr.getString(0);
                 if (V) {
@@ -1479,7 +1483,8 @@
                 Log.v(TAG, "whereClause is " + whereClause);
             }
             try {
-                cr = r.query(sAllCanonical, null, whereClause, null, null);
+                cr = BluetoothMethodProxy.getInstance().contentResolverQuery(r, sAllCanonical, null,
+                        whereClause, null, null);
                 if (cr != null && cr.moveToFirst()) {
                     do {
                         //TODO: Multiple Recipeints are appended with ";" for now.
@@ -1511,7 +1516,8 @@
         String[] projection = {Mms.Addr.ADDRESS};
         Cursor c = null;
         try {
-            c = r.query(uriAddress, projection, selection, null, null); // TODO: Add projection
+            c = BluetoothMethodProxy.getInstance().contentResolverQuery(r, uriAddress, projection,
+                    selection, null, null); // TODO: Add projection
             int colIndex = c.getColumnIndex(Mms.Addr.ADDRESS);
             if (c != null) {
                 if (c.moveToFirst()) {
diff --git a/android/app/src/com/android/bluetooth/mcp/McpService.java b/android/app/src/com/android/bluetooth/mcp/McpService.java
index bfccf7e..22d5ee2 100644
--- a/android/app/src/com/android/bluetooth/mcp/McpService.java
+++ b/android/app/src/com/android/bluetooth/mcp/McpService.java
@@ -206,10 +206,10 @@
          * 2. authorized devices
          * 3. Any LeAudio devices which are allowed to connect
          */
-        if (mDeviceAuthorizations.getOrDefault(device, Utils.isPtsTestMode()
-                ? BluetoothDevice.ACCESS_ALLOWED : BluetoothDevice.ACCESS_UNKNOWN)
-                        == BluetoothDevice.ACCESS_ALLOWED) {
-            return BluetoothDevice.ACCESS_ALLOWED;
+        int authorization = mDeviceAuthorizations.getOrDefault(device, Utils.isPtsTestMode()
+                ? BluetoothDevice.ACCESS_ALLOWED : BluetoothDevice.ACCESS_UNKNOWN);
+        if (authorization != BluetoothDevice.ACCESS_UNKNOWN) {
+            return authorization;
         }
 
         LeAudioService leAudioService = LeAudioService.getLeAudioService();
diff --git a/android/app/src/com/android/bluetooth/opp/BluetoothOppBatch.java b/android/app/src/com/android/bluetooth/opp/BluetoothOppBatch.java
index 45e79d4..dff6fff 100644
--- a/android/app/src/com/android/bluetooth/opp/BluetoothOppBatch.java
+++ b/android/app/src/com/android/bluetooth/opp/BluetoothOppBatch.java
@@ -37,6 +37,8 @@
 import android.content.Context;
 import android.util.Log;
 
+import com.android.bluetooth.BluetoothMethodProxy;
+
 import java.util.ArrayList;
 
 /**
@@ -148,7 +150,9 @@
 
             if (info.mStatus < 200) {
                 if (info.mDirection == BluetoothShare.DIRECTION_INBOUND && info.mUri != null) {
-                    mContext.getContentResolver().delete(info.mUri, null, null);
+                    BluetoothMethodProxy.getInstance().contentResolverDelete(
+                            mContext.getContentResolver(), info.mUri, null, null
+                    );
                 }
                 if (V) {
                     Log.v(TAG, "Cancel batch for info " + info.mId);
diff --git a/android/app/src/com/android/bluetooth/opp/BluetoothOppPreference.java b/android/app/src/com/android/bluetooth/opp/BluetoothOppPreference.java
index 3070947..740513f 100644
--- a/android/app/src/com/android/bluetooth/opp/BluetoothOppPreference.java
+++ b/android/app/src/com/android/bluetooth/opp/BluetoothOppPreference.java
@@ -102,7 +102,8 @@
     }
 
     public String getName(BluetoothDevice remoteDevice) {
-        if (remoteDevice.getIdentityAddress().equals("FF:FF:FF:00:00:00")) {
+        String identityAddress = remoteDevice.getIdentityAddress();
+        if (identityAddress != null && identityAddress.equals("FF:FF:FF:00:00:00")) {
             return "localhost";
         }
         if (!mNames.isEmpty()) {
diff --git a/android/app/src/com/android/bluetooth/opp/BluetoothOppProvider.java b/android/app/src/com/android/bluetooth/opp/BluetoothOppProvider.java
index 4079a87..6505305 100644
--- a/android/app/src/com/android/bluetooth/opp/BluetoothOppProvider.java
+++ b/android/app/src/com/android/bluetooth/opp/BluetoothOppProvider.java
@@ -44,6 +44,10 @@
 import android.net.Uri;
 import android.util.Log;
 
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
 /**
  * This provider allows application to interact with Bluetooth OPP manager
  */
@@ -98,7 +102,7 @@
      * when a new version of the provider needs an updated version of the
      * database.
      */
-    private final class DatabaseHelper extends SQLiteOpenHelper {
+    private static final class DatabaseHelper extends SQLiteOpenHelper {
 
         DatabaseHelper(final Context context) {
             super(context, DB_NAME, null, DB_VERSION);
@@ -137,7 +141,7 @@
 
     }
 
-    private void createTable(SQLiteDatabase db) {
+    private static void createTable(SQLiteDatabase db) {
         try {
             db.execSQL("CREATE TABLE " + DB_TABLE + "(" + BluetoothShare._ID
                     + " INTEGER PRIMARY KEY AUTOINCREMENT," + BluetoothShare.URI + " TEXT, "
@@ -155,7 +159,7 @@
         }
     }
 
-    private void dropTable(SQLiteDatabase db) {
+    private static void dropTable(SQLiteDatabase db) {
         try {
             db.execSQL("DROP TABLE IF EXISTS " + DB_TABLE);
         } catch (SQLException ex) {
@@ -198,6 +202,62 @@
         }
     }
 
+    private static void putString(String key, Cursor from, ContentValues to) {
+        to.put(key, from.getString(from.getColumnIndexOrThrow(key)));
+    }
+    private static void putInteger(String key, Cursor from, ContentValues to) {
+        to.put(key, from.getInt(from.getColumnIndexOrThrow(key)));
+    }
+    private static void putLong(String key, Cursor from, ContentValues to) {
+        to.put(key, from.getLong(from.getColumnIndexOrThrow(key)));
+    }
+
+    /**
+     * @hide
+     */
+    public static boolean oppDatabaseMigration(Context ctx, Cursor cursor) {
+        boolean result = true;
+        SQLiteDatabase db = new DatabaseHelper(ctx).getWritableDatabase();
+        while (cursor.moveToNext()) {
+            try {
+                ContentValues values = new ContentValues();
+
+                final List<String> stringKeys =  new ArrayList<>(Arrays.asList(
+                            BluetoothShare.URI,
+                            BluetoothShare.FILENAME_HINT,
+                            BluetoothShare.MIMETYPE,
+                            BluetoothShare.DESTINATION));
+                for (String k : stringKeys) {
+                    putString(k, cursor, values);
+                }
+
+                final List<String> integerKeys =  new ArrayList<>(Arrays.asList(
+                            BluetoothShare.VISIBILITY,
+                            BluetoothShare.USER_CONFIRMATION,
+                            BluetoothShare.DIRECTION,
+                            BluetoothShare.STATUS,
+                            Constants.MEDIA_SCANNED));
+                for (String k : integerKeys) {
+                    putInteger(k, cursor, values);
+                }
+
+                final List<String> longKeys =  new ArrayList<>(Arrays.asList(
+                            BluetoothShare.TOTAL_BYTES,
+                            BluetoothShare.TIMESTAMP));
+                for (String k : longKeys) {
+                    putLong(k, cursor, values);
+                }
+
+                db.insert(DB_TABLE, null, values);
+                Log.d(TAG, "One item migrated: " + values);
+            } catch (IllegalArgumentException e) {
+                Log.e(TAG, "Failed to migrate one item: " + e);
+                result = false;
+            }
+        }
+        return result;
+    }
+
     @Override
     public Uri insert(Uri uri, ContentValues values) {
         SQLiteDatabase db = mOpenHelper.getWritableDatabase();
diff --git a/android/app/src/com/android/bluetooth/opp/BluetoothOppTransferActivity.java b/android/app/src/com/android/bluetooth/opp/BluetoothOppTransferActivity.java
index bba6758..5f59890 100644
--- a/android/app/src/com/android/bluetooth/opp/BluetoothOppTransferActivity.java
+++ b/android/app/src/com/android/bluetooth/opp/BluetoothOppTransferActivity.java
@@ -52,6 +52,8 @@
 
 import com.android.bluetooth.R;
 
+import com.google.common.annotations.VisibleForTesting;
+
 /**
  * Handle all transfer related dialogs: -Ongoing transfer -Receiving one file
  * dialog -Sending one file dialog -sending multiple files dialog -Complete
@@ -83,7 +85,8 @@
 
     private TextView mLine1View, mLine2View, mLine3View, mLine5View;
 
-    private int mWhichDialog;
+    @VisibleForTesting
+    int mWhichDialog;
 
     private BluetoothAdapter mAdapter;
 
diff --git a/android/app/src/com/android/bluetooth/opp/BluetoothOppUtility.java b/android/app/src/com/android/bluetooth/opp/BluetoothOppUtility.java
index f1f1faa..89e51c8 100644
--- a/android/app/src/com/android/bluetooth/opp/BluetoothOppUtility.java
+++ b/android/app/src/com/android/bluetooth/opp/BluetoothOppUtility.java
@@ -51,6 +51,7 @@
 import android.util.EventLog;
 import android.util.Log;
 
+import com.android.bluetooth.BluetoothMethodProxy;
 import com.android.bluetooth.R;
 
 import java.io.File;
@@ -88,7 +89,9 @@
 
     public static BluetoothOppTransferInfo queryRecord(Context context, Uri uri) {
         BluetoothOppTransferInfo info = new BluetoothOppTransferInfo();
-        Cursor cursor = context.getContentResolver().query(uri, null, null, null, null);
+        Cursor cursor = BluetoothMethodProxy.getInstance().contentResolverQuery(
+                context.getContentResolver(), uri, null, null, null, null
+        );
         if (cursor != null) {
             if (cursor.moveToFirst()) {
                 fillRecord(context, cursor, info);
@@ -157,10 +160,14 @@
     public static ArrayList<String> queryTransfersInBatch(Context context, Long timeStamp) {
         ArrayList<String> uris = new ArrayList();
         final String where = BluetoothShare.TIMESTAMP + " == " + timeStamp;
-        Cursor metadataCursor =
-                context.getContentResolver().query(BluetoothShare.CONTENT_URI, new String[]{
-                        BluetoothShare._DATA
-                }, where, null, BluetoothShare._ID);
+        Cursor metadataCursor = BluetoothMethodProxy.getInstance().contentResolverQuery(
+                context.getContentResolver(),
+                BluetoothShare.CONTENT_URI,
+                new String[]{BluetoothShare._DATA},
+                where,
+                null,
+                BluetoothShare._ID
+        );
 
         if (metadataCursor == null) {
             return null;
@@ -200,8 +207,10 @@
         }
 
         Uri path = null;
-        Cursor metadataCursor = context.getContentResolver().query(uri, new String[]{
-                BluetoothShare.URI}, null, null, null);
+        Cursor metadataCursor = BluetoothMethodProxy.getInstance().contentResolverQuery(
+                context.getContentResolver(), uri, new String[]{BluetoothShare.URI},
+                null, null, null
+        );
         if (metadataCursor != null) {
             try {
                 if (metadataCursor.moveToFirst()) {
@@ -307,7 +316,8 @@
     public static void updateVisibilityToHidden(Context context, Uri uri) {
         ContentValues updateValues = new ContentValues();
         updateValues.put(BluetoothShare.VISIBILITY, BluetoothShare.VISIBILITY_HIDDEN);
-        context.getContentResolver().update(uri, updateValues, null, null);
+        BluetoothMethodProxy.getInstance().contentResolverUpdate(context.getContentResolver(), uri,
+                updateValues, null, null);
     }
 
     /**
diff --git a/android/app/src/com/android/bluetooth/opp/Constants.java b/android/app/src/com/android/bluetooth/opp/Constants.java
index 84f735d..f1e5a30 100644
--- a/android/app/src/com/android/bluetooth/opp/Constants.java
+++ b/android/app/src/com/android/bluetooth/opp/Constants.java
@@ -38,6 +38,7 @@
 import android.net.Uri;
 import android.util.Log;
 
+import com.android.bluetooth.BluetoothMethodProxy;
 import com.android.obex.HeaderSet;
 
 import java.io.IOException;
@@ -229,7 +230,8 @@
         Uri contentUri = Uri.parse(BluetoothShare.CONTENT_URI + "/" + id);
         ContentValues updateValues = new ContentValues();
         updateValues.put(BluetoothShare.STATUS, status);
-        context.getContentResolver().update(contentUri, updateValues, null, null);
+        BluetoothMethodProxy.getInstance().contentResolverUpdate(context.getContentResolver(),
+                contentUri, updateValues, null, null);
         Constants.sendIntentIfCompleted(context, contentUri, status);
     }
 
diff --git a/android/app/src/com/android/bluetooth/sap/SapRilReceiver.java b/android/app/src/com/android/bluetooth/sap/SapRilReceiver.java
index c21778b..0e8580b 100644
--- a/android/app/src/com/android/bluetooth/sap/SapRilReceiver.java
+++ b/android/app/src/com/android/bluetooth/sap/SapRilReceiver.java
@@ -369,4 +369,7 @@
         mSapServerMsgHandler.sendMessage(newMsg);
     }
 
+    AtomicLong getSapProxyCookie() {
+        return mSapProxyCookie;
+    }
 }
diff --git a/android/app/src/com/android/bluetooth/sap/SapServer.java b/android/app/src/com/android/bluetooth/sap/SapServer.java
index 2514863..0ad1f8d 100644
--- a/android/app/src/com/android/bluetooth/sap/SapServer.java
+++ b/android/app/src/com/android/bluetooth/sap/SapServer.java
@@ -25,6 +25,8 @@
 import android.util.Log;
 
 import com.android.bluetooth.R;
+import com.android.bluetooth.Utils;
+import com.android.internal.annotations.VisibleForTesting;
 
 import java.io.BufferedInputStream;
 import java.io.BufferedOutputStream;
@@ -51,26 +53,31 @@
     public static final boolean DEBUG = SapService.DEBUG;
     public static final boolean VERBOSE = SapService.VERBOSE;
 
-    private enum SAP_STATE {
+    @VisibleForTesting
+    enum SAP_STATE {
         DISCONNECTED, CONNECTING, CONNECTING_CALL_ONGOING, CONNECTED, CONNECTED_BUSY, DISCONNECTING;
     }
 
-    private SAP_STATE mState = SAP_STATE.DISCONNECTED;
+    @VisibleForTesting
+    SAP_STATE mState = SAP_STATE.DISCONNECTED;
 
     private Context mContext = null;
     /* RFCOMM socket I/O streams */
     private BufferedOutputStream mRfcommOut = null;
     private BufferedInputStream mRfcommIn = null;
     /* References to the SapRilReceiver object */
-    private SapRilReceiver mRilBtReceiver = null;
+    @VisibleForTesting
+    SapRilReceiver mRilBtReceiver = null;
     /* The message handler members */
-    private Handler mSapHandler = null;
+    @VisibleForTesting
+    Handler mSapHandler = null;
     private HandlerThread mHandlerThread = null;
     /* Reference to the SAP service - which created this instance of the SAP server */
     private Handler mSapServiceHandler = null;
 
     /* flag for when user forces disconnect of rfcomm */
-    private boolean mIsLocalInitDisconnect = false;
+    @VisibleForTesting
+    boolean mIsLocalInitDisconnect = false;
     private CountDownLatch mDeinitSignal = new CountDownLatch(1);
 
     /* Message ID's handled by the message handler */
@@ -86,7 +93,8 @@
     public static final String SAP_DISCONNECT_TYPE_EXTRA =
             "com.android.bluetooth.sap.extra.DISCONNECT_TYPE";
     public static final int NOTIFICATION_ID = android.R.drawable.stat_sys_data_bluetooth;
-    private static final String SAP_NOTIFICATION_CHANNEL = "sap_notification_channel";
+    @VisibleForTesting
+    static final String SAP_NOTIFICATION_CHANNEL = "sap_notification_channel";
     public static final int ISAP_GET_SERVICE_DELAY_MILLIS = 3 * 1000;
     private static final int DISCONNECT_TIMEOUT_IMMEDIATE = 5000; /* ms */
     private static final int DISCONNECT_TIMEOUT_RFCOMM = 2000; /* ms */
@@ -99,7 +107,8 @@
     /* We store the mMaxMessageSize, as we need a copy of it when the init. sequence completes */
     private int mMaxMsgSize = 0;
     /* keep track of the current RIL test mode */
-    private int mTestMode = SapMessage.INVALID_VALUE; // used to set the RIL in test mode
+    @VisibleForTesting
+    int mTestMode = SapMessage.INVALID_VALUE; // used to set the RIL in test mode
 
     /**
      * SapServer constructor
@@ -127,9 +136,11 @@
     /**
      * This handles the response from RIL.
      */
-    private BroadcastReceiver mIntentReceiver;
+    @VisibleForTesting
+    BroadcastReceiver mIntentReceiver;
 
-    private class SapServerBroadcastReceiver extends BroadcastReceiver {
+    @VisibleForTesting
+    class SapServerBroadcastReceiver extends BroadcastReceiver {
         @Override
         public void onReceive(Context context, Intent intent) {
             if (intent.getAction().equals(TelephonyManager.ACTION_PHONE_STATE_CHANGED)) {
@@ -177,12 +188,13 @@
      * @param testMode Use SapMessage.TEST_MODE_XXX
      */
     public void setTestMode(int testMode) {
-        if (SapMessage.TEST) {
+        if (SapMessage.TEST || Utils.isInstrumentationTestMode()) {
             mTestMode = testMode;
         }
     }
 
-    private void sendDisconnectInd(int discType) {
+    @VisibleForTesting
+    void sendDisconnectInd(int discType) {
         if (VERBOSE) {
             Log.v(TAG, "in sendDisconnectInd()");
         }
@@ -556,7 +568,8 @@
      *
      * @param msg the incoming SapMessage
      */
-    private void onConnectRequest(SapMessage msg) {
+    @VisibleForTesting
+    void onConnectRequest(SapMessage msg) {
         SapMessage reply = new SapMessage(SapMessage.ID_CONNECT_RESP);
 
         if (mState == SAP_STATE.CONNECTING) {
@@ -601,7 +614,8 @@
         }
     }
 
-    private void clearPendingRilResponses(SapMessage msg) {
+    @VisibleForTesting
+    void clearPendingRilResponses(SapMessage msg) {
         if (mState == SAP_STATE.CONNECTED_BUSY) {
             msg.setClearRilQueue(true);
         }
@@ -611,7 +625,8 @@
      * Send RFCOMM message to the Sap Server Handler Thread
      * @param sapMsg The message to send
      */
-    private void sendClientMessage(SapMessage sapMsg) {
+    @VisibleForTesting
+    void sendClientMessage(SapMessage sapMsg) {
         Message newMsg = mSapHandler.obtainMessage(SAP_MSG_RFC_REPLY, sapMsg);
         mSapHandler.sendMessage(newMsg);
     }
@@ -620,7 +635,8 @@
      * Send a RIL message to the SapServer message handler thread
      * @param sapMsg
      */
-    private void sendRilThreadMessage(SapMessage sapMsg) {
+    @VisibleForTesting
+    void sendRilThreadMessage(SapMessage sapMsg) {
         Message newMsg = mSapHandler.obtainMessage(SAP_MSG_RIL_REQ, sapMsg);
         mSapHandler.sendMessage(newMsg);
     }
@@ -629,7 +645,8 @@
      * Examine if a call is ongoing, by asking the telephony manager
      * @return false if the phone is IDLE (can be used for SAP), true otherwise.
      */
-    private boolean isCallOngoing() {
+    @VisibleForTesting
+    boolean isCallOngoing() {
         TelephonyManager tManager = mContext.getSystemService(TelephonyManager.class);
         if (tManager.getCallState() == TelephonyManager.CALL_STATE_IDLE) {
             return false;
@@ -642,7 +659,8 @@
      * We add thread protection, as we access the state from two threads.
      * @param newState
      */
-    private void changeState(SAP_STATE newState) {
+    @VisibleForTesting
+    void changeState(SAP_STATE newState) {
         if (DEBUG) {
             Log.i(TAG_HANDLER, "Changing state from " + mState.name() + " to " + newState.name());
         }
@@ -706,7 +724,7 @@
                 startDisconnectTimer(SapMessage.DISC_RFCOMM, DISCONNECT_TIMEOUT_RFCOMM);
                 break;
             case SAP_PROXY_DEAD:
-                if ((long) msg.obj == mRilBtReceiver.mSapProxyCookie.get()) {
+                if ((long) msg.obj == mRilBtReceiver.getSapProxyCookie().get()) {
                     mRilBtReceiver.notifyShutdown(); /* Only needed in case of a connection error */
                     mRilBtReceiver.resetSapProxy();
 
@@ -726,7 +744,8 @@
      * Close the in/out rfcomm streams, to trigger a shutdown of the SapServer main thread.
      * Use this after completing the deinit sequence.
      */
-    private void shutdown() {
+    @VisibleForTesting
+    void shutdown() {
 
         if (DEBUG) {
             Log.i(TAG_HANDLER, "in Shutdown()");
@@ -749,7 +768,8 @@
         clearNotification();
     }
 
-    private void startDisconnectTimer(int discType, int timeMs) {
+    @VisibleForTesting
+    void startDisconnectTimer(int discType, int timeMs) {
 
         stopDisconnectTimer();
         synchronized (this) {
@@ -769,7 +789,8 @@
         }
     }
 
-    private void stopDisconnectTimer() {
+    @VisibleForTesting
+    void stopDisconnectTimer() {
         synchronized (this) {
             if (mPendingDiscIntent != null) {
                 AlarmManager alarmManager = mContext.getSystemService(AlarmManager.class);
@@ -789,7 +810,8 @@
      * here before they go to the client
      * @param sapMsg the message to send to the SAP client
      */
-    private void handleRfcommReply(SapMessage sapMsg) {
+    @VisibleForTesting
+    void handleRfcommReply(SapMessage sapMsg) {
         if (sapMsg != null) {
 
             if (DEBUG) {
@@ -908,7 +930,8 @@
         }
     }
 
-    private void handleRilInd(SapMessage sapMsg) {
+    @VisibleForTesting
+    void handleRilInd(SapMessage sapMsg) {
         if (sapMsg == null) {
             return;
         }
@@ -939,7 +962,8 @@
      * This is only to be called from the handlerThread, else use sendRilThreadMessage();
      * @param sapMsg
      */
-    private void sendRilMessage(SapMessage sapMsg) {
+    @VisibleForTesting
+    void sendRilMessage(SapMessage sapMsg) {
         if (VERBOSE) {
             Log.i(TAG_HANDLER,
                     "sendRilMessage() - " + SapMessage.getMsgTypeName(sapMsg.getMsgType()));
@@ -975,7 +999,8 @@
     /**
      * Only call this from the sapHandler thread.
      */
-    private void sendReply(SapMessage msg) {
+    @VisibleForTesting
+    void sendReply(SapMessage msg) {
         if (VERBOSE) {
             Log.i(TAG_HANDLER,
                     "sendReply() RFCOMM - " + SapMessage.getMsgTypeName(msg.getMsgType()));
@@ -992,7 +1017,8 @@
         }
     }
 
-    private static String getMessageName(int messageId) {
+    @VisibleForTesting
+    static String getMessageName(int messageId) {
         switch (messageId) {
             case SAP_MSG_RFC_REPLY:
                 return "SAP_MSG_REPLY";
diff --git a/android/app/tests/unit/AndroidTest.xml b/android/app/tests/unit/AndroidTest.xml
index 981e0a8..4966540 100644
--- a/android/app/tests/unit/AndroidTest.xml
+++ b/android/app/tests/unit/AndroidTest.xml
@@ -20,6 +20,9 @@
         <option name="cleanup-apks" value="true" />
         <option name="test-file-name" value="BluetoothInstrumentationTests.apk" />
     </target_preparer>
+    <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer">
+        <option name="force-root" value="true" />
+    </target_preparer>
     <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
         <option name="run-command" value="settings put global ble_scan_always_enabled 0" />
         <option name="run-command" value="su u$(am get-current-user)_system svc bluetooth disable" />
@@ -32,6 +35,7 @@
     <option name="test-tag" value="BluetoothInstrumentationTests" />
     <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
         <option name="package" value="com.android.bluetooth.tests" />
+        <option name="test-filter-dir" value="/data/data/{PACKAGE}/cache" />
         <option name="hidden-api-checks" value="false"/>
     </test>
 
diff --git a/android/app/tests/unit/src/com/android/bluetooth/avrcpcontroller/AvrcpPlayerTest.java b/android/app/tests/unit/src/com/android/bluetooth/avrcpcontroller/AvrcpPlayerTest.java
new file mode 100644
index 0000000..30e9c62
--- /dev/null
+++ b/android/app/tests/unit/src/com/android/bluetooth/avrcpcontroller/AvrcpPlayerTest.java
@@ -0,0 +1,190 @@
+/*
+ * 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.avrcpcontroller;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.when;
+
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.net.Uri;
+import android.support.v4.media.session.PlaybackStateCompat;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+public class AvrcpPlayerTest {
+    private static final int TEST_PLAYER_ID = 1;
+    private static final int TEST_PLAYER_TYPE = AvrcpPlayer.TYPE_VIDEO;
+    private static final int TEST_PLAYER_SUB_TYPE = AvrcpPlayer.SUB_TYPE_AUDIO_BOOK;
+    private static final String TEST_NAME = "test_name";
+    private static final int TEST_FEATURE = AvrcpPlayer.FEATURE_PLAY;
+    private static final int TEST_PLAY_STATUS = PlaybackStateCompat.STATE_STOPPED;
+    private static final int TEST_PLAY_TIME = 1;
+
+    private final AvrcpItem mAvrcpItem = new AvrcpItem.Builder().build();
+    private final byte[] mTestAddress = new byte[]{01, 01, 01, 01, 01, 01};
+    private BluetoothAdapter mAdapter;
+    private BluetoothDevice mTestDevice = null;
+
+    @Mock
+    private PlayerApplicationSettings mPlayerApplicationSettings;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        mAdapter = BluetoothAdapter.getDefaultAdapter();
+        mTestDevice = mAdapter.getRemoteDevice(mTestAddress);
+    }
+
+    @Test
+    public void buildAvrcpPlayer() {
+        AvrcpPlayer.Builder builder = new AvrcpPlayer.Builder();
+        builder.setDevice(mTestDevice);
+        builder.setPlayerId(TEST_PLAYER_ID);
+        builder.setPlayerType(TEST_PLAYER_TYPE);
+        builder.setPlayerSubType(TEST_PLAYER_SUB_TYPE);
+        builder.setName(TEST_NAME);
+        builder.setSupportedFeature(TEST_FEATURE);
+        builder.setPlayStatus(TEST_PLAY_STATUS);
+        builder.setCurrentTrack(mAvrcpItem);
+
+        AvrcpPlayer avrcpPlayer = builder.build();
+
+        assertThat(avrcpPlayer.getDevice()).isEqualTo(mTestDevice);
+        assertThat(avrcpPlayer.getId()).isEqualTo(TEST_PLAYER_ID);
+        assertThat(avrcpPlayer.getName()).isEqualTo(TEST_NAME);
+        assertThat(avrcpPlayer.supportsFeature(TEST_FEATURE)).isTrue();
+        assertThat(avrcpPlayer.getPlayStatus()).isEqualTo(TEST_PLAY_STATUS);
+        assertThat(avrcpPlayer.getCurrentTrack()).isEqualTo(mAvrcpItem);
+        assertThat(avrcpPlayer.getPlaybackState().getActions()).isEqualTo(
+                PlaybackStateCompat.ACTION_PREPARE | PlaybackStateCompat.ACTION_PLAY);
+    }
+
+    @Test
+    public void setAndGetPlayTime() {
+        AvrcpPlayer avrcpPlayer = new AvrcpPlayer.Builder().build();
+
+        avrcpPlayer.setPlayTime(TEST_PLAY_TIME);
+
+        assertThat(avrcpPlayer.getPlayTime()).isEqualTo(TEST_PLAY_TIME);
+    }
+
+    @Test
+    public void setPlayStatus() {
+        AvrcpPlayer avrcpPlayer = new AvrcpPlayer.Builder().build();
+        avrcpPlayer.setPlayTime(TEST_PLAY_TIME);
+
+        avrcpPlayer.setPlayStatus(PlaybackStateCompat.STATE_PLAYING);
+        assertThat(avrcpPlayer.getPlaybackState().getPlaybackSpeed()).isEqualTo(1);
+
+        avrcpPlayer.setPlayStatus(PlaybackStateCompat.STATE_PAUSED);
+        assertThat(avrcpPlayer.getPlaybackState().getPlaybackSpeed()).isEqualTo(0);
+
+        avrcpPlayer.setPlayStatus(PlaybackStateCompat.STATE_FAST_FORWARDING);
+        assertThat(avrcpPlayer.getPlaybackState().getPlaybackSpeed()).isEqualTo(3);
+
+        avrcpPlayer.setPlayStatus(PlaybackStateCompat.STATE_REWINDING);
+        assertThat(avrcpPlayer.getPlaybackState().getPlaybackSpeed()).isEqualTo(-3);
+    }
+
+    @Test
+    public void setSupportedPlayerApplicationSettings() {
+        when(mPlayerApplicationSettings.supportsSetting(
+                PlayerApplicationSettings.REPEAT_STATUS)).thenReturn(true);
+        when(mPlayerApplicationSettings.supportsSetting(
+                PlayerApplicationSettings.SHUFFLE_STATUS)).thenReturn(true);
+        AvrcpPlayer avrcpPlayer = new AvrcpPlayer.Builder().build();
+        long expectedActions =
+                PlaybackStateCompat.ACTION_PREPARE | PlaybackStateCompat.ACTION_SET_REPEAT_MODE
+                        | PlaybackStateCompat.ACTION_SET_SHUFFLE_MODE;
+
+        avrcpPlayer.setSupportedPlayerApplicationSettings(mPlayerApplicationSettings);
+
+        assertThat(avrcpPlayer.getPlaybackState().getActions()).isEqualTo(expectedActions);
+    }
+
+    @Test
+    public void supportsSetting() {
+        int settingType = 1;
+        int settingValue = 1;
+        when(mPlayerApplicationSettings.supportsSetting(settingType, settingValue)).thenReturn(
+                true);
+        AvrcpPlayer avrcpPlayer = new AvrcpPlayer.Builder().build();
+
+        avrcpPlayer.setSupportedPlayerApplicationSettings(mPlayerApplicationSettings);
+
+        assertThat(avrcpPlayer.supportsSetting(settingType, settingValue)).isTrue();
+    }
+
+    @Test
+    public void updateAvailableActions() {
+        byte[] supportedFeatures = new byte[16];
+        setSupportedFeature(supportedFeatures, AvrcpPlayer.FEATURE_STOP);
+        setSupportedFeature(supportedFeatures, AvrcpPlayer.FEATURE_PAUSE);
+        setSupportedFeature(supportedFeatures, AvrcpPlayer.FEATURE_REWIND);
+        setSupportedFeature(supportedFeatures, AvrcpPlayer.FEATURE_FAST_FORWARD);
+        setSupportedFeature(supportedFeatures, AvrcpPlayer.FEATURE_FORWARD);
+        setSupportedFeature(supportedFeatures, AvrcpPlayer.FEATURE_PREVIOUS);
+        long expectedActions = PlaybackStateCompat.ACTION_PREPARE | PlaybackStateCompat.ACTION_STOP
+                | PlaybackStateCompat.ACTION_PAUSE | PlaybackStateCompat.ACTION_REWIND
+                | PlaybackStateCompat.ACTION_FAST_FORWARD | PlaybackStateCompat.ACTION_SKIP_TO_NEXT
+                | PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS;
+
+        AvrcpPlayer avrcpPlayer = new AvrcpPlayer.Builder().setSupportedFeatures(
+                supportedFeatures).build();
+
+        assertThat(avrcpPlayer.getPlaybackState().getActions()).isEqualTo(expectedActions);
+    }
+
+    @Test
+    public void toString_returnsInfo() {
+        AvrcpPlayer avrcpPlayer = new AvrcpPlayer.Builder().setPlayerId(TEST_PLAYER_ID).setName(
+                TEST_NAME).setCurrentTrack(mAvrcpItem).build();
+
+        assertThat(avrcpPlayer.toString()).isEqualTo(
+                "<AvrcpPlayer id=" + TEST_PLAYER_ID + " name=" + TEST_NAME + " track="
+                        + mAvrcpItem + " playState=" + avrcpPlayer.getPlaybackState() + ">");
+    }
+
+    @Test
+    public void notifyImageDownload() {
+        String uuid = "1111";
+        Uri uri = Uri.parse("http://test.com");
+        AvrcpItem trackWithDifferentUuid = new AvrcpItem.Builder().build();
+        AvrcpItem trackWithSameUuid = new AvrcpItem.Builder().build();
+        trackWithSameUuid.setCoverArtUuid(uuid);
+        AvrcpPlayer avrcpPlayer = new AvrcpPlayer.Builder().build();
+
+        assertThat(avrcpPlayer.notifyImageDownload(uuid, uri)).isFalse();
+
+        avrcpPlayer.updateCurrentTrack(trackWithDifferentUuid);
+        assertThat(avrcpPlayer.notifyImageDownload(uuid, uri)).isFalse();
+
+        avrcpPlayer.updateCurrentTrack(trackWithSameUuid);
+        assertThat(avrcpPlayer.notifyImageDownload(uuid, uri)).isTrue();
+    }
+
+    private void setSupportedFeature(byte[] supportedFeatures, int feature) {
+        int byteNumber = feature / 8;
+        byte bitMask = (byte) (1 << (feature % 8));
+        supportedFeatures[byteNumber] = (byte) (supportedFeatures[byteNumber] | bitMask);
+    }
+}
diff --git a/android/app/tests/unit/src/com/android/bluetooth/avrcpcontroller/BrowseTreeTest.java b/android/app/tests/unit/src/com/android/bluetooth/avrcpcontroller/BrowseTreeTest.java
new file mode 100644
index 0000000..53af271
--- /dev/null
+++ b/android/app/tests/unit/src/com/android/bluetooth/avrcpcontroller/BrowseTreeTest.java
@@ -0,0 +1,202 @@
+/*
+ * 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.avrcpcontroller;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+
+import com.android.bluetooth.avrcpcontroller.BrowseTree.BrowseNode;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.Set;
+
+public class BrowseTreeTest {
+    private static final String ILLEGAL_ID = "illegal_id";
+    private static final String TEST_HANDLE = "test_handle";
+    private static final String TEST_NODE_ID = "test_node_id";
+
+    private final byte[] mTestAddress = new byte[]{01, 01, 01, 01, 01, 01};
+    private BluetoothAdapter mAdapter;
+    private BluetoothDevice mTestDevice = null;
+
+    @Before
+    public void setUp() {
+        mAdapter = BluetoothAdapter.getDefaultAdapter();
+        mTestDevice = mAdapter.getRemoteDevice(mTestAddress);
+    }
+
+    @Test
+    public void constructor_withoutDevice() {
+        BrowseTree browseTree = new BrowseTree(null);
+
+        assertThat(browseTree.mRootNode.mItem.getDevice()).isEqualTo(null);
+    }
+
+    @Test
+    public void constructor_withDevice() {
+        BrowseTree browseTree = new BrowseTree(mTestDevice);
+
+        assertThat(browseTree.mRootNode.mItem.getDevice()).isEqualTo(mTestDevice);
+    }
+
+    @Test
+    public void clear() {
+        BrowseTree browseTree = new BrowseTree(mTestDevice);
+
+        browseTree.clear();
+
+        assertThat(browseTree.mBrowseMap).isEmpty();
+    }
+
+    @Test
+    public void getTrackFromNowPlayingList() {
+        BrowseTree browseTree = new BrowseTree(mTestDevice);
+        BrowseNode trackInNowPlayingList = browseTree.new BrowseNode(new AvrcpItem.Builder()
+                .setUuid(ILLEGAL_ID).setTitle(ILLEGAL_ID).setBrowsable(true).build());
+
+        browseTree.mNowPlayingNode.addChild(trackInNowPlayingList);
+
+        assertThat(browseTree.getTrackFromNowPlayingList(0)).isEqualTo(
+                trackInNowPlayingList);
+    }
+
+    @Test
+    public void onConnected() {
+        BrowseTree browseTree = new BrowseTree(null);
+
+        assertThat(browseTree.mRootNode.getChildrenCount()).isEqualTo(0);
+
+        browseTree.onConnected(mTestDevice);
+
+        assertThat(browseTree.mRootNode.getChildrenCount()).isEqualTo(1);
+    }
+
+    @Test
+    public void findBrowseNodeByID() {
+        BrowseTree browseTree = new BrowseTree(mTestDevice);
+
+        assertThat(browseTree.findBrowseNodeByID(ILLEGAL_ID)).isNull();
+        assertThat(browseTree.findBrowseNodeByID(BrowseTree.ROOT)).isEqualTo(browseTree.mRootNode);
+    }
+
+    @Test
+    public void setAndGetCurrentBrowsedFolder() {
+        BrowseTree browseTree = new BrowseTree(mTestDevice);
+
+        assertThat(browseTree.setCurrentBrowsedFolder(ILLEGAL_ID)).isFalse();
+        assertThat(browseTree.setCurrentBrowsedFolder(BrowseTree.NOW_PLAYING_PREFIX)).isTrue();
+        assertThat(browseTree.getCurrentBrowsedFolder()).isEqualTo(browseTree.mNowPlayingNode);
+    }
+
+    @Test
+    public void setAndGetCurrentBrowsedPlayer() {
+        BrowseTree browseTree = new BrowseTree(mTestDevice);
+
+        assertThat(browseTree.setCurrentBrowsedPlayer(ILLEGAL_ID, 0, 0)).isFalse();
+        assertThat(
+                browseTree.setCurrentBrowsedPlayer(BrowseTree.NOW_PLAYING_PREFIX, 2, 1)).isTrue();
+        assertThat(browseTree.getCurrentBrowsedPlayer()).isEqualTo(browseTree.mNowPlayingNode);
+    }
+
+    @Test
+    public void setAndGetCurrentAddressedPlayer() {
+        BrowseTree browseTree = new BrowseTree(mTestDevice);
+
+        assertThat(browseTree.setCurrentAddressedPlayer(ILLEGAL_ID)).isFalse();
+        assertThat(browseTree.setCurrentAddressedPlayer(BrowseTree.NOW_PLAYING_PREFIX)).isTrue();
+        assertThat(browseTree.getCurrentAddressedPlayer()).isEqualTo(browseTree.mNowPlayingNode);
+    }
+
+    @Test
+    public void indicateCoverArtUsedAndUnused() {
+        BrowseTree browseTree = new BrowseTree(mTestDevice);
+        assertThat(browseTree.getNodesUsingCoverArt(TEST_HANDLE)).isEmpty();
+
+        browseTree.indicateCoverArtUsed(TEST_NODE_ID, TEST_HANDLE);
+
+        assertThat(browseTree.getNodesUsingCoverArt(TEST_HANDLE).get(0)).isEqualTo(TEST_NODE_ID);
+
+        browseTree.indicateCoverArtUnused(TEST_NODE_ID, TEST_HANDLE);
+
+        assertThat(browseTree.getNodesUsingCoverArt(TEST_HANDLE)).isEmpty();
+        assertThat(browseTree.getAndClearUnusedCoverArt().get(0)).isEqualTo(TEST_HANDLE);
+    }
+
+    @Test
+    public void notifyImageDownload() {
+        BrowseTree browseTree = new BrowseTree(null);
+        String testDeviceId = BrowseTree.PLAYER_PREFIX + mTestDevice.getAddress();
+
+        browseTree.onConnected(mTestDevice);
+        browseTree.indicateCoverArtUsed(TEST_NODE_ID, TEST_HANDLE);
+        browseTree.indicateCoverArtUsed(testDeviceId, TEST_HANDLE);
+        Set<BrowseTree.BrowseNode> parents = browseTree.notifyImageDownload(TEST_HANDLE, null);
+
+        assertThat(parents.contains(browseTree.mRootNode)).isTrue();
+    }
+
+    @Test
+    public void getEldestChild_whenNodesAreNotAncestorDescendantRelation() {
+        BrowseTree browseTree = new BrowseTree(null);
+
+        browseTree.onConnected(mTestDevice);
+
+        assertThat(BrowseTree.getEldestChild(browseTree.mNowPlayingNode,
+                browseTree.mRootNode)).isNull();
+    }
+
+    @Test
+    public void getEldestChild_whenNodesAreAncestorDescendantRelation() {
+        BrowseTree browseTree = new BrowseTree(null);
+
+        browseTree.onConnected(mTestDevice);
+
+        assertThat(BrowseTree.getEldestChild(browseTree.mRootNode,
+                browseTree.mRootNode.getChild(0))).isEqualTo(browseTree.mRootNode.getChild(0));
+    }
+
+    @Test
+    public void getNextStepFolder() {
+        BrowseTree browseTree = new BrowseTree(null);
+        BrowseNode nodeOutOfMap = browseTree.new BrowseNode(new AvrcpItem.Builder()
+                .setUuid(ILLEGAL_ID).setTitle(ILLEGAL_ID).setBrowsable(true).build());
+
+        browseTree.onConnected(mTestDevice);
+
+        assertThat(browseTree.getNextStepToFolder(null)).isNull();
+        assertThat(browseTree.getNextStepToFolder(browseTree.mRootNode)).isEqualTo(
+                browseTree.mRootNode);
+        assertThat(browseTree.getNextStepToFolder(browseTree.mRootNode.getChild(0))).isEqualTo(
+                browseTree.mRootNode.getChild(0));
+        assertThat(browseTree.getNextStepToFolder(nodeOutOfMap)).isNull();
+
+        browseTree.setCurrentBrowsedPlayer(BrowseTree.NOW_PLAYING_PREFIX, 2, 1);
+        assertThat(browseTree.getNextStepToFolder(browseTree.mRootNode.getChild(0))).isEqualTo(
+                browseTree.mNavigateUpNode);
+    }
+
+    @Test
+    public void toString_returnsSizeInfo() {
+        BrowseTree browseTree = new BrowseTree(mTestDevice);
+
+        assertThat(browseTree.toString()).isEqualTo("Size: " + browseTree.mBrowseMap.size());
+    }
+}
\ No newline at end of file
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 1671e65..40123b6 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
@@ -16,7 +16,13 @@
 
 package com.android.bluetooth.btservice;
 
-import static org.mockito.Mockito.*;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.isNull;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
 
 import android.bluetooth.BluetoothA2dp;
 import android.bluetooth.BluetoothAdapter;
@@ -48,9 +54,12 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
+import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
 
+import java.util.ArrayList;
 import java.util.List;
+import java.util.Objects;
 
 @MediumTest
 @RunWith(AndroidJUnit4.class)
@@ -62,7 +71,10 @@
     private BluetoothDevice mA2dpHeadsetDevice;
     private BluetoothDevice mHearingAidDevice;
     private BluetoothDevice mLeAudioDevice;
+    private BluetoothDevice mLeHearingAidDevice;
     private BluetoothDevice mSecondaryAudioDevice;
+    private ArrayList<BluetoothDevice> mDeviceConnectionStack;
+    private BluetoothDevice mMostRecentDevice;
     private ActiveDeviceManager mActiveDeviceManager;
     private static final int TIMEOUT_MS = 1000;
 
@@ -85,6 +97,7 @@
         // Set up mocks and test assets
         MockitoAnnotations.initMocks(this);
         TestUtils.setAdapterService(mAdapterService);
+
         when(mAdapterService.getSystemService(Context.AUDIO_SERVICE)).thenReturn(mAudioManager);
         when(mAdapterService.getSystemServiceName(AudioManager.class))
                 .thenReturn(Context.AUDIO_SERVICE);
@@ -93,12 +106,6 @@
         when(mServiceFactory.getHeadsetService()).thenReturn(mHeadsetService);
         when(mServiceFactory.getHearingAidService()).thenReturn(mHearingAidService);
         when(mServiceFactory.getLeAudioService()).thenReturn(mLeAudioService);
-        when(mA2dpService.setActiveDevice(any())).thenReturn(true);
-        when(mHeadsetService.setActiveDevice(any())).thenReturn(true);
-        when(mHearingAidService.setActiveDevice(any())).thenReturn(true);
-        when(mLeAudioService.setActiveDevice(any())).thenReturn(true);
-        when(mDatabaseManager.getMostRecentlyConnectedDevicesInList(any()))
-                .thenReturn(mSecondaryAudioDevice);
 
         mActiveDeviceManager = new ActiveDeviceManager(mAdapterService, mServiceFactory);
         mActiveDeviceManager.start();
@@ -110,7 +117,46 @@
         mA2dpHeadsetDevice = TestUtils.getTestDevice(mAdapter, 2);
         mHearingAidDevice = TestUtils.getTestDevice(mAdapter, 3);
         mLeAudioDevice = TestUtils.getTestDevice(mAdapter, 4);
-        mSecondaryAudioDevice = TestUtils.getTestDevice(mAdapter, 5);
+        mLeHearingAidDevice = TestUtils.getTestDevice(mAdapter, 5);
+        mSecondaryAudioDevice = TestUtils.getTestDevice(mAdapter, 6);
+        mDeviceConnectionStack = new ArrayList<>();
+        mMostRecentDevice = null;
+
+        when(mA2dpService.setActiveDevice(any())).thenReturn(true);
+        when(mHeadsetService.setActiveDevice(any())).thenReturn(true);
+        when(mHearingAidService.setActiveDevice(any())).thenReturn(true);
+        when(mLeAudioService.setActiveDevice(any())).thenReturn(true);
+
+        when(mA2dpService.getFallbackDevice()).thenAnswer(invocation -> {
+            if (!mDeviceConnectionStack.isEmpty() && Objects.equals(mA2dpDevice,
+                    mDeviceConnectionStack.get(mDeviceConnectionStack.size() - 1))) {
+                return mA2dpDevice;
+            }
+            return null;
+        });
+        when(mHeadsetService.getFallbackDevice()).thenAnswer(invocation -> {
+            if (!mDeviceConnectionStack.isEmpty() && Objects.equals(mHeadsetDevice,
+                    mDeviceConnectionStack.get(mDeviceConnectionStack.size() - 1))) {
+                return mHeadsetDevice;
+            }
+            return null;
+        });
+        when(mDatabaseManager.getMostRecentlyConnectedDevicesInList(any())).thenAnswer(
+                invocation -> {
+                    List<BluetoothDevice> devices = invocation.getArgument(0);
+                    if (devices == null || devices.size() == 0) {
+                        return null;
+                    } else if (devices.contains(mLeHearingAidDevice)) {
+                        return mLeHearingAidDevice;
+                    } else if (devices.contains(mHearingAidDevice)) {
+                        return mHearingAidDevice;
+                    } else if (mMostRecentDevice != null && devices.contains(mMostRecentDevice)) {
+                        return mMostRecentDevice;
+                    } else {
+                        return devices.get(0);
+                    }
+                }
+        );
     }
 
     @After
@@ -182,14 +228,15 @@
      */
     @Test
     public void a2dpSecondDeviceDisconnected_fallbackDeviceActive() {
-        a2dpConnected(mSecondaryAudioDevice);
-        verify(mA2dpService, timeout(TIMEOUT_MS)).setActiveDevice(mSecondaryAudioDevice);
-
         a2dpConnected(mA2dpDevice);
         verify(mA2dpService, timeout(TIMEOUT_MS)).setActiveDevice(mA2dpDevice);
 
-        a2dpDisconnected(mA2dpDevice);
+        a2dpConnected(mSecondaryAudioDevice);
         verify(mA2dpService, timeout(TIMEOUT_MS)).setActiveDevice(mSecondaryAudioDevice);
+
+        Mockito.clearInvocations(mA2dpService);
+        a2dpDisconnected(mSecondaryAudioDevice);
+        verify(mA2dpService, timeout(TIMEOUT_MS)).setActiveDevice(mA2dpDevice);
     }
 
     /**
@@ -197,6 +244,8 @@
      */
     @Test
     public void onlyHeadsetConnected_setHeadsetActive() {
+        when(mAudioManager.getMode()).thenReturn(AudioManager.MODE_IN_CALL);
+
         headsetConnected(mHeadsetDevice);
         verify(mHeadsetService, timeout(TIMEOUT_MS)).setActiveDevice(mHeadsetDevice);
     }
@@ -206,6 +255,8 @@
      */
     @Test
     public void secondHeadsetConnected_setSecondHeadsetActive() {
+        when(mAudioManager.getMode()).thenReturn(AudioManager.MODE_IN_CALL);
+
         headsetConnected(mHeadsetDevice);
         verify(mHeadsetService, timeout(TIMEOUT_MS)).setActiveDevice(mHeadsetDevice);
 
@@ -218,6 +269,8 @@
      */
     @Test
     public void lastHeadsetDisconnected_clearHeadsetActive() {
+        when(mAudioManager.getMode()).thenReturn(AudioManager.MODE_IN_CALL);
+
         headsetConnected(mHeadsetDevice);
         verify(mHeadsetService, timeout(TIMEOUT_MS)).setActiveDevice(mHeadsetDevice);
 
@@ -230,6 +283,8 @@
      */
     @Test
     public void headsetActiveDeviceSelected_setActive() {
+        when(mAudioManager.getMode()).thenReturn(AudioManager.MODE_IN_CALL);
+
         headsetConnected(mHeadsetDevice);
         verify(mHeadsetService, timeout(TIMEOUT_MS)).setActiveDevice(mHeadsetDevice);
 
@@ -243,21 +298,23 @@
         Assert.assertEquals(mHeadsetDevice, mActiveDeviceManager.getHfpActiveDevice());
     }
 
-
     /**
      * Two Headsets are connected and the current active is then disconnected.
      * Should then set active device to fallback device.
      */
     @Test
     public void headsetSecondDeviceDisconnected_fallbackDeviceActive() {
-        headsetConnected(mSecondaryAudioDevice);
-        verify(mHeadsetService, timeout(TIMEOUT_MS)).setActiveDevice(mSecondaryAudioDevice);
+        when(mAudioManager.getMode()).thenReturn(AudioManager.MODE_IN_CALL);
 
         headsetConnected(mHeadsetDevice);
         verify(mHeadsetService, timeout(TIMEOUT_MS)).setActiveDevice(mHeadsetDevice);
 
-        headsetDisconnected(mHeadsetDevice);
+        headsetConnected(mSecondaryAudioDevice);
         verify(mHeadsetService, timeout(TIMEOUT_MS)).setActiveDevice(mSecondaryAudioDevice);
+
+        Mockito.clearInvocations(mHeadsetService);
+        headsetDisconnected(mSecondaryAudioDevice);
+        verify(mHeadsetService, timeout(TIMEOUT_MS)).setActiveDevice(mHeadsetDevice);
     }
 
     /**
@@ -392,14 +449,15 @@
      */
     @Test
     public void leAudioSecondDeviceDisconnected_fallbackDeviceActive() {
-        leAudioConnected(mSecondaryAudioDevice);
-        verify(mLeAudioService, timeout(TIMEOUT_MS)).setActiveDevice(mSecondaryAudioDevice);
-
         leAudioConnected(mLeAudioDevice);
         verify(mLeAudioService, timeout(TIMEOUT_MS)).setActiveDevice(mLeAudioDevice);
 
-        leAudioDisconnected(mLeAudioDevice);
+        leAudioConnected(mSecondaryAudioDevice);
         verify(mLeAudioService, timeout(TIMEOUT_MS)).setActiveDevice(mSecondaryAudioDevice);
+
+        Mockito.clearInvocations(mLeAudioService);
+        leAudioDisconnected(mSecondaryAudioDevice);
+        verify(mLeAudioService, timeout(TIMEOUT_MS)).setActiveDevice(mLeAudioDevice);
     }
 
     /**
@@ -463,7 +521,7 @@
     public void leAudioActive_setHeadsetActiveExplicitly() {
         Assume.assumeTrue("Ignore test when LeAudioService is not enabled",
                 LeAudioService.isEnabled());
-
+        when(mAudioManager.getMode()).thenReturn(AudioManager.MODE_IN_CALL);
         leAudioActiveDeviceChanged(mLeAudioDevice);
         headsetConnected(mA2dpHeadsetDevice);
         headsetActiveDeviceChanged(mA2dpHeadsetDevice);
@@ -481,23 +539,18 @@
      */
     @Test
     public void leAudioAndA2dpConnectedThenA2dpDisconnected_fallbackToLeAudio() {
+        when(mAudioManager.getMode()).thenReturn(AudioManager.MODE_NORMAL);
+
         leAudioConnected(mLeAudioDevice);
         verify(mLeAudioService, timeout(TIMEOUT_MS)).setActiveDevice(mLeAudioDevice);
 
         a2dpConnected(mA2dpDevice);
         verify(mA2dpService, timeout(TIMEOUT_MS)).setActiveDevice(mA2dpDevice);
 
-        when(mAudioManager.getMode()).thenReturn(AudioManager.MODE_NORMAL);
-        when(mDatabaseManager.getMostRecentlyConnectedDevicesInList(any())).thenAnswer(
-                invocation -> {
-                    List<BluetoothDevice> devices = invocation.getArgument(0);
-                    return (devices != null && devices.contains(mLeAudioDevice))
-                            ? mLeAudioDevice : null;
-                }
-        );
+        Mockito.clearInvocations(mLeAudioService);
         a2dpDisconnected(mA2dpDevice);
         verify(mA2dpService, timeout(TIMEOUT_MS).atLeast(1)).setActiveDevice(isNull());
-        verify(mLeAudioService, timeout(TIMEOUT_MS).times(2)).setActiveDevice(mLeAudioDevice);
+        verify(mLeAudioService, timeout(TIMEOUT_MS)).setActiveDevice(mLeAudioDevice);
     }
 
     /**
@@ -506,23 +559,18 @@
      */
     @Test
     public void a2dpAndLeAudioConnectedThenLeAudioDisconnected_fallbackToA2dp() {
+        when(mAudioManager.getMode()).thenReturn(AudioManager.MODE_NORMAL);
+
         a2dpConnected(mA2dpDevice);
         verify(mA2dpService, timeout(TIMEOUT_MS)).setActiveDevice(mA2dpDevice);
 
         leAudioConnected(mLeAudioDevice);
         verify(mLeAudioService, timeout(TIMEOUT_MS)).setActiveDevice(mLeAudioDevice);
 
-        when(mAudioManager.getMode()).thenReturn(AudioManager.MODE_NORMAL);
-        when(mA2dpService.getFallbackDevice()).thenReturn(mA2dpDevice);
-        when(mDatabaseManager.getMostRecentlyConnectedDevicesInList(any())).thenAnswer(
-                invocation -> {
-                    List<BluetoothDevice> devices = invocation.getArgument(0);
-                    return (devices != null && devices.contains(mA2dpDevice)) ? mA2dpDevice : null;
-                }
-        );
+        Mockito.clearInvocations(mA2dpService);
         leAudioDisconnected(mLeAudioDevice);
         verify(mLeAudioService, timeout(TIMEOUT_MS).atLeast(1)).setActiveDevice(isNull());
-        verify(mA2dpService, timeout(TIMEOUT_MS).times(2)).setActiveDevice(mA2dpDevice);
+        verify(mA2dpService, timeout(TIMEOUT_MS)).setActiveDevice(mA2dpDevice);
     }
 
     /**
@@ -531,14 +579,15 @@
      */
     @Test
     public void hearingAidSecondDeviceDisconnected_fallbackDeviceActive() {
-        hearingAidConnected(mSecondaryAudioDevice);
-        verify(mHearingAidService, timeout(TIMEOUT_MS)).setActiveDevice(mSecondaryAudioDevice);
-
         hearingAidConnected(mHearingAidDevice);
         verify(mHearingAidService, timeout(TIMEOUT_MS)).setActiveDevice(mHearingAidDevice);
 
-        leAudioDisconnected(mHearingAidDevice);
+        hearingAidConnected(mSecondaryAudioDevice);
         verify(mHearingAidService, timeout(TIMEOUT_MS)).setActiveDevice(mSecondaryAudioDevice);
+
+        Mockito.clearInvocations(mHearingAidService);
+        hearingAidDisconnected(mSecondaryAudioDevice);
+        verify(mHearingAidService, timeout(TIMEOUT_MS)).setActiveDevice(mHearingAidDevice);
     }
 
     /**
@@ -547,6 +596,8 @@
      */
     @Test
     public void activeDeviceDisconnected_fallbackToHearingAid() {
+        when(mAudioManager.getMode()).thenReturn(AudioManager.MODE_NORMAL);
+
         hearingAidConnected(mHearingAidDevice);
         verify(mHearingAidService, timeout(TIMEOUT_MS)).setActiveDevice(mHearingAidDevice);
 
@@ -560,25 +611,8 @@
         verify(mLeAudioService, never()).setActiveDevice(mLeAudioDevice);
         verify(mA2dpService, never()).setActiveDevice(mA2dpDevice);
 
-        when(mAudioManager.getMode()).thenReturn(AudioManager.MODE_NORMAL);
-        when(mDatabaseManager.getMostRecentlyConnectedDevicesInList(any())).thenAnswer(
-                invocation -> {
-                    List<BluetoothDevice> devices = invocation.getArgument(0);
-                    if (devices == null) {
-                        return null;
-                    } else if (devices.contains(mA2dpDevice)) {
-                        return mA2dpDevice;
-                    } else if (devices.contains(mLeAudioDevice)) {
-                        return mLeAudioDevice;
-                    } else if (devices.contains(mHearingAidDevice)) {
-                        return mHearingAidDevice;
-                    } else {
-                        return devices.get(0);
-                    }
-                }
-        );
         a2dpDisconnected(mA2dpDevice);
-        verify(mA2dpService, timeout(TIMEOUT_MS)).setActiveDevice(isNull());
+        verify(mA2dpService, timeout(TIMEOUT_MS).atLeast(1)).setActiveDevice(isNull());
         verify(mHearingAidService, timeout(TIMEOUT_MS).times(2))
                 .setActiveDevice(mHearingAidDevice);
     }
@@ -587,9 +621,13 @@
      * One LE Hearing Aid is connected.
      */
     @Test
-    public void onlyLeHearingAIdConnected_setHeadsetActive() {
-        leAudioConnected(mLeAudioDevice);
-        verify(mLeAudioService, timeout(TIMEOUT_MS)).setActiveDevice(mLeAudioDevice);
+    public void onlyLeHearingAidConnected_setLeAudioActive() {
+        leHearingAidConnected(mLeHearingAidDevice);
+        TestUtils.waitForLooperToFinishScheduledTask(mActiveDeviceManager.getHandlerLooper());
+        verify(mLeAudioService, never()).setActiveDevice(mLeHearingAidDevice);
+
+        leAudioConnected(mLeHearingAidDevice);
+        verify(mLeAudioService, timeout(TIMEOUT_MS)).setActiveDevice(mLeHearingAidDevice);
     }
 
     /**
@@ -598,8 +636,9 @@
      */
     @Test
     public void leAudioConnectedAfterLeHearingAid_setLeAudioActiveShouldNotBeCalled() {
-        leHearingAidConnected(mSecondaryAudioDevice);
-        verify(mLeAudioService, timeout(TIMEOUT_MS)).setActiveDevice(mSecondaryAudioDevice);
+        leHearingAidConnected(mLeHearingAidDevice);
+        leAudioConnected(mLeHearingAidDevice);
+        verify(mLeAudioService, timeout(TIMEOUT_MS)).setActiveDevice(mLeHearingAidDevice);
 
         leAudioConnected(mLeAudioDevice);
         TestUtils.waitForLooperToFinishScheduledTask(mActiveDeviceManager.getHandlerLooper());
@@ -607,6 +646,36 @@
     }
 
     /**
+     * Test connect/disconnect of devices.
+     * Hearing Aid, LE Hearing Aid, A2DP connected, then LE hearing Aid and hearing aid
+     * disconnected.
+     */
+    @Test
+    public void activeDeviceChange_withHearingAidLeHearingAidAndA2dpDevices() {
+        when(mAudioManager.getMode()).thenReturn(AudioManager.MODE_NORMAL);
+
+        hearingAidConnected(mHearingAidDevice);
+        verify(mHearingAidService, timeout(TIMEOUT_MS)).setActiveDevice(mHearingAidDevice);
+
+        leHearingAidConnected(mLeHearingAidDevice);
+        leAudioConnected(mLeHearingAidDevice);
+        verify(mLeAudioService, timeout(TIMEOUT_MS)).setActiveDevice(mLeHearingAidDevice);
+
+        a2dpConnected(mA2dpDevice);
+        TestUtils.waitForLooperToFinishScheduledTask(mActiveDeviceManager.getHandlerLooper());
+        verify(mA2dpService, never()).setActiveDevice(mA2dpDevice);
+
+        Mockito.clearInvocations(mHearingAidService, mA2dpService);
+        leHearingAidDisconnected(mLeHearingAidDevice);
+        leAudioDisconnected(mLeHearingAidDevice);
+        verify(mHearingAidService, timeout(TIMEOUT_MS)).setActiveDevice(mHearingAidDevice);
+        verify(mA2dpService, timeout(TIMEOUT_MS)).setActiveDevice(isNull());
+
+        hearingAidDisconnected(mHearingAidDevice);
+        verify(mA2dpService, timeout(TIMEOUT_MS)).setActiveDevice(mA2dpDevice);
+    }
+
+    /**
      * A wired audio device is connected. Then all active devices are set to null.
      */
     @Test
@@ -626,6 +695,9 @@
      * Helper to indicate A2dp connected for a device.
      */
     private void a2dpConnected(BluetoothDevice device) {
+        mDeviceConnectionStack.add(device);
+        mMostRecentDevice = device;
+
         Intent intent = new Intent(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED);
         intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
         intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, BluetoothProfile.STATE_DISCONNECTED);
@@ -637,6 +709,10 @@
      * Helper to indicate A2dp disconnected for a device.
      */
     private void a2dpDisconnected(BluetoothDevice device) {
+        mDeviceConnectionStack.remove(device);
+        mMostRecentDevice = (mDeviceConnectionStack.size() > 0)
+                ? mDeviceConnectionStack.get(mDeviceConnectionStack.size() - 1) : null;
+
         Intent intent = new Intent(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED);
         intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
         intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, BluetoothProfile.STATE_CONNECTED);
@@ -648,6 +724,10 @@
      * Helper to indicate A2dp active device changed for a device.
      */
     private void a2dpActiveDeviceChanged(BluetoothDevice device) {
+        mDeviceConnectionStack.remove(device);
+        mDeviceConnectionStack.add(device);
+        mMostRecentDevice = device;
+
         Intent intent = new Intent(BluetoothA2dp.ACTION_ACTIVE_DEVICE_CHANGED);
         intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
         mActiveDeviceManager.getBroadcastReceiver().onReceive(mContext, intent);
@@ -657,6 +737,9 @@
      * Helper to indicate Headset connected for a device.
      */
     private void headsetConnected(BluetoothDevice device) {
+        mDeviceConnectionStack.add(device);
+        mMostRecentDevice = device;
+
         Intent intent = new Intent(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED);
         intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
         intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, BluetoothProfile.STATE_DISCONNECTED);
@@ -668,6 +751,10 @@
      * Helper to indicate Headset disconnected for a device.
      */
     private void headsetDisconnected(BluetoothDevice device) {
+        mDeviceConnectionStack.remove(device);
+        mMostRecentDevice = (mDeviceConnectionStack.size() > 0)
+                ? mDeviceConnectionStack.get(mDeviceConnectionStack.size() - 1) : null;
+
         Intent intent = new Intent(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED);
         intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
         intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, BluetoothProfile.STATE_CONNECTED);
@@ -679,6 +766,10 @@
      * Helper to indicate Headset active device changed for a device.
      */
     private void headsetActiveDeviceChanged(BluetoothDevice device) {
+        mDeviceConnectionStack.remove(device);
+        mDeviceConnectionStack.add(device);
+        mMostRecentDevice = device;
+
         Intent intent = new Intent(BluetoothHeadset.ACTION_ACTIVE_DEVICE_CHANGED);
         intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
         mActiveDeviceManager.getBroadcastReceiver().onReceive(mContext, intent);
@@ -688,6 +779,8 @@
      * Helper to indicate Hearing Aid connected for a device.
      */
     private void hearingAidConnected(BluetoothDevice device) {
+        mMostRecentDevice = device;
+
         Intent intent = new Intent(BluetoothHearingAid.ACTION_CONNECTION_STATE_CHANGED);
         intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
         intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, BluetoothProfile.STATE_DISCONNECTED);
@@ -699,6 +792,10 @@
      * Helper to indicate Hearing Aid disconnected for a device.
      */
     private void hearingAidDisconnected(BluetoothDevice device) {
+        mDeviceConnectionStack.remove(device);
+        mMostRecentDevice = (mDeviceConnectionStack.size() > 0)
+                ? mDeviceConnectionStack.get(mDeviceConnectionStack.size() - 1) : null;
+
         Intent intent = new Intent(BluetoothHearingAid.ACTION_CONNECTION_STATE_CHANGED);
         intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
         intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, BluetoothProfile.STATE_CONNECTED);
@@ -710,15 +807,21 @@
      * Helper to indicate Hearing Aid active device changed for a device.
      */
     private void hearingAidActiveDeviceChanged(BluetoothDevice device) {
+        mMostRecentDevice = device;
+
         Intent intent = new Intent(BluetoothHearingAid.ACTION_ACTIVE_DEVICE_CHANGED);
         intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
         mActiveDeviceManager.getBroadcastReceiver().onReceive(mContext, intent);
+        mDeviceConnectionStack.remove(device);
+        mDeviceConnectionStack.add(device);
     }
 
     /**
      * Helper to indicate LE Audio connected for a device.
      */
     private void leAudioConnected(BluetoothDevice device) {
+        mMostRecentDevice = device;
+
         Intent intent = new Intent(BluetoothLeAudio.ACTION_LE_AUDIO_CONNECTION_STATE_CHANGED);
         intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
         intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, BluetoothProfile.STATE_DISCONNECTED);
@@ -730,6 +833,10 @@
      * Helper to indicate LE Audio disconnected for a device.
      */
     private void leAudioDisconnected(BluetoothDevice device) {
+        mDeviceConnectionStack.remove(device);
+        mMostRecentDevice = (mDeviceConnectionStack.size() > 0)
+                ? mDeviceConnectionStack.get(mDeviceConnectionStack.size() - 1) : null;
+
         Intent intent = new Intent(BluetoothLeAudio.ACTION_LE_AUDIO_CONNECTION_STATE_CHANGED);
         intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
         intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, BluetoothProfile.STATE_CONNECTED);
@@ -741,6 +848,10 @@
      * Helper to indicate LE Audio active device changed for a device.
      */
     private void leAudioActiveDeviceChanged(BluetoothDevice device) {
+        mDeviceConnectionStack.remove(device);
+        mDeviceConnectionStack.add(device);
+        mMostRecentDevice = device;
+
         Intent intent = new Intent(BluetoothLeAudio.ACTION_LE_AUDIO_ACTIVE_DEVICE_CHANGED);
         intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
         mActiveDeviceManager.getBroadcastReceiver().onReceive(mContext, intent);
@@ -750,6 +861,9 @@
      * Helper to indicate LE Hearing Aid connected for a device.
      */
     private void leHearingAidConnected(BluetoothDevice device) {
+        mDeviceConnectionStack.add(device);
+        mMostRecentDevice = device;
+
         Intent intent = new Intent(BluetoothHapClient.ACTION_HAP_CONNECTION_STATE_CHANGED);
         intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
         intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, BluetoothProfile.STATE_DISCONNECTED);
@@ -761,6 +875,10 @@
      * Helper to indicate LE Hearing Aid disconnected for a device.
      */
     private void leHearingAidDisconnected(BluetoothDevice device) {
+        mDeviceConnectionStack.remove(device);
+        mMostRecentDevice = (mDeviceConnectionStack.size() > 0)
+                ? mDeviceConnectionStack.get(mDeviceConnectionStack.size() - 1) : null;
+
         Intent intent = new Intent(BluetoothHapClient.ACTION_HAP_CONNECTION_STATE_CHANGED);
         intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
         intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, BluetoothProfile.STATE_CONNECTED);
@@ -772,6 +890,10 @@
      * Helper to indicate LE Audio Hearing Aid device changed for a device.
      */
     private void leHearingAidActiveDeviceChanged(BluetoothDevice device) {
+        mDeviceConnectionStack.remove(device);
+        mDeviceConnectionStack.add(device);
+        mMostRecentDevice = device;
+
         Intent intent = new Intent(BluetoothHapClient.ACTION_HAP_DEVICE_AVAILABLE);
         intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
         mActiveDeviceManager.getBroadcastReceiver().onReceive(mContext, intent);
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 042480d..7902b5f 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
@@ -87,6 +87,7 @@
 import libcore.util.HexEncoding;
 
 import org.junit.After;
+import org.junit.AfterClass;
 import org.junit.Assert;
 import org.junit.Before;
 import org.junit.BeforeClass;
@@ -214,6 +215,8 @@
         AsyncTask.setDefaultExecutor((r) -> {
             InstrumentationRegistry.getInstrumentation().runOnMainSync(r);
         });
+        InstrumentationRegistry.getInstrumentation().getUiAutomation()
+                .adoptShellPermissionIdentity();
 
         InstrumentationRegistry.getInstrumentation().runOnMainSync(
                 () -> mAdapterService = new AdapterService());
@@ -238,6 +241,8 @@
         mPermissionManager = InstrumentationRegistry.getTargetContext()
                 .getSystemService(PermissionManager.class);
 
+        when(mMockContext.getCacheDir()).thenReturn(InstrumentationRegistry.getTargetContext()
+                .getCacheDir());
         when(mMockContext.getApplicationInfo()).thenReturn(mMockApplicationInfo);
         when(mMockContext.getContentResolver()).thenReturn(mMockContentResolver);
         when(mMockContext.getApplicationContext()).thenReturn(mMockContext);
@@ -328,6 +333,11 @@
         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)
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
new file mode 100644
index 0000000..3206f6f
--- /dev/null
+++ b/android/app/tests/unit/src/com/android/bluetooth/btservice/DataMigrationTest.java
@@ -0,0 +1,523 @@
+/*
+ * 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 static android.bluetooth.BluetoothA2dp.OPTIONAL_CODECS_NOT_SUPPORTED;
+import static android.bluetooth.BluetoothA2dp.OPTIONAL_CODECS_PREF_DISABLED;
+import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.anyString;
+import static org.mockito.Mockito.when;
+
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothProfile;
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Bundle;
+import android.test.mock.MockContentProvider;
+import android.test.mock.MockContentResolver;
+import android.test.mock.MockCursor;
+import android.util.Log;
+import android.util.Pair;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.bluetooth.btservice.storage.Metadata;
+import com.android.bluetooth.btservice.storage.MetadataDatabase;
+import com.android.bluetooth.opp.BluetoothShare;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class DataMigrationTest {
+    private static final String TAG = "DataMigrationTest";
+
+    private static final String AUTHORITY = "bluetooth_legacy.provider";
+
+    private MockContentResolver mMockContentResolver;
+
+    private Context mTargetContext;
+    private SharedPreferences mPrefs;
+
+    @Mock private Context mMockContext;
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+
+        mTargetContext = InstrumentationRegistry.getTargetContext();
+        mPrefs = mTargetContext.getSharedPreferences("TestPref", Context.MODE_PRIVATE);
+
+        mMockContentResolver = new MockContentResolver(mTargetContext);
+        when(mMockContext.getContentResolver()).thenReturn(mMockContentResolver);
+        when(mMockContext.getCacheDir()).thenReturn(mTargetContext.getCacheDir());
+
+        when(mMockContext.getSharedPreferences(anyString(), anyInt())).thenReturn(mPrefs);
+
+        mTargetContext.deleteSharedPreferences("TestPref");
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        mTargetContext.deleteSharedPreferences("TestPref");
+        mTargetContext.deleteDatabase("TestBluetoothDb");
+        mTargetContext.deleteDatabase("TestOppDb");
+    }
+
+    private void assertRunStatus(int status) {
+        assertThat(DataMigration.run(mMockContext)).isEqualTo(status);
+        assertThat(DataMigration.migrationStatus(mMockContext)).isEqualTo(status);
+    }
+
+    /**
+     * Test: execute Empty migration
+     */
+    @Test
+    public void testEmptyMigration() {
+        BluetoothLegacyContentProvider fakeContentProvider =
+                new BluetoothLegacyContentProvider(mMockContext);
+        mMockContentResolver.addProvider(AUTHORITY, fakeContentProvider);
+
+        final int nCallCount = DataMigration.sharedPreferencesKeys.length
+                + 1; // +1 for default preferences
+        final int nBundleCount = 2; // `bluetooth_db` && `btopp.db`
+
+        assertRunStatus(DataMigration.MIGRATION_STATUS_COMPLETED);
+        assertThat(fakeContentProvider.mCallCount).isEqualTo(nCallCount);
+        assertThat(fakeContentProvider.mBundleCount).isEqualTo(nBundleCount);
+
+        // run it twice to trigger an already completed migration
+        assertRunStatus(DataMigration.MIGRATION_STATUS_COMPLETED);
+        // ContentProvider should not have any more calls made than previously
+        assertThat(fakeContentProvider.mCallCount).isEqualTo(nCallCount);
+        assertThat(fakeContentProvider.mBundleCount).isEqualTo(nBundleCount);
+    }
+
+    private static class BluetoothLegacyContentProvider extends MockContentProvider {
+        BluetoothLegacyContentProvider(Context ctx) {
+            super(ctx);
+        }
+        int mCallCount = 0;
+        int mBundleCount = 0;
+        @Override
+        public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
+                String sortOrder) {
+            mBundleCount++;
+            return null;
+        }
+        @Override
+        public Bundle call(String method, String arg, Bundle extras) {
+            mCallCount++;
+            return null;
+        }
+    }
+
+    /**
+     * Test: execute migration without having a content provided registered
+     */
+    @Test
+    public void testMissingProvider() {
+        assertThat(DataMigration.isMigrationApkInstalled(mMockContext)).isFalse();
+
+        assertRunStatus(DataMigration.MIGRATION_STATUS_MISSING_APK);
+
+        mMockContentResolver.addProvider(AUTHORITY, new MockContentProvider(mMockContext));
+        assertThat(DataMigration.isMigrationApkInstalled(mMockContext)).isTrue();
+    }
+
+    /**
+     * Test: execute migration after too many attempt
+     */
+    @Test
+    public void testTooManyAttempt() {
+        SharedPreferences pref = mMockContext
+                .getSharedPreferences(DataMigration.BLUETOOTH_CONFIG,
+                        Context.MODE_PRIVATE);
+
+        assertThat(pref.getInt(DataMigration.MIGRATION_ATTEMPT_PROPERTY, -1))
+            .isEqualTo(-1);
+
+        for (int i = 0; i < DataMigration.MAX_ATTEMPT; i++) {
+            assertThat(DataMigration.incrementeMigrationAttempt(mMockContext))
+                .isTrue();
+            assertThat(pref.getInt(DataMigration.MIGRATION_ATTEMPT_PROPERTY, -1))
+                .isEqualTo(i + 1);
+        }
+        assertThat(DataMigration.incrementeMigrationAttempt(mMockContext))
+            .isFalse();
+        assertThat(pref.getInt(DataMigration.MIGRATION_ATTEMPT_PROPERTY, -1))
+            .isEqualTo(DataMigration.MAX_ATTEMPT + 1);
+
+        mMockContentResolver.addProvider(AUTHORITY, new MockContentProvider(mMockContext));
+        assertRunStatus(DataMigration.MIGRATION_STATUS_MAX_ATTEMPT);
+    }
+
+    /**
+     * Test: execute migration of SharedPreferences
+     */
+    @Test
+    public void testSharedPreferencesMigration() {
+        BluetoothLegacySharedPreferencesContentProvider fakeContentProvider =
+                new BluetoothLegacySharedPreferencesContentProvider(mMockContext);
+        mMockContentResolver.addProvider(AUTHORITY, fakeContentProvider);
+
+        assertThat(DataMigration.sharedPreferencesMigration("Boolean", mMockContext)).isTrue();
+        assertThat(mPrefs.getBoolean("keyBoolean", false)).isTrue();
+        assertThat(fakeContentProvider.mCallCount).isEqualTo(2);
+
+        assertThat(DataMigration.sharedPreferencesMigration("Long", mMockContext)).isTrue();
+        assertThat(mPrefs.getLong("keyLong", -1)).isEqualTo(42);
+        assertThat(fakeContentProvider.mCallCount).isEqualTo(4);
+
+        assertThat(DataMigration.sharedPreferencesMigration("Int", mMockContext)).isTrue();
+        assertThat(mPrefs.getInt("keyInt", -1)).isEqualTo(42);
+        assertThat(fakeContentProvider.mCallCount).isEqualTo(6);
+
+        assertThat(DataMigration.sharedPreferencesMigration("String", mMockContext)).isTrue();
+        assertThat(mPrefs.getString("keyString", "Not42")).isEqualTo("42");
+        assertThat(fakeContentProvider.mCallCount).isEqualTo(8);
+
+        // Check not overriding an existing value:
+        mPrefs.edit().putString("keyString2", "already 42").apply();
+        assertThat(DataMigration.sharedPreferencesMigration("String2", mMockContext)).isTrue();
+        assertThat(mPrefs.getString("keyString2", "Not42")).isEqualTo("already 42");
+        assertThat(fakeContentProvider.mCallCount).isEqualTo(10);
+
+        assertThat(DataMigration.sharedPreferencesMigration("Invalid", mMockContext)).isFalse();
+
+        assertThat(DataMigration.sharedPreferencesMigration("null", mMockContext)).isFalse();
+
+        assertThat(DataMigration.sharedPreferencesMigration("empty", mMockContext)).isFalse();
+
+        assertThat(DataMigration
+                .sharedPreferencesMigration("anything else", mMockContext)).isTrue();
+    }
+
+    private static class BluetoothLegacySharedPreferencesContentProvider
+            extends MockContentProvider {
+        BluetoothLegacySharedPreferencesContentProvider(Context ctx) {
+            super(ctx);
+        }
+        String mLastMethod = null;
+        int mCallCount = 0;
+        int mBundleCount = 0;
+        @Override
+        public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
+                String sortOrder) {
+            mBundleCount++;
+            return null;
+        }
+        @Override
+        public Bundle call(String method, String arg, Bundle extras) {
+            mCallCount++;
+            mLastMethod = method;
+            assertThat(method).isNotNull();
+            assertThat(arg).isNotNull();
+            assertThat(extras).isNull();
+            final String key = "key" + arg;
+            Bundle b = new Bundle();
+            b.putStringArrayList(DataMigration.KEY_LIST, new ArrayList<String>(Arrays.asList(key)));
+            switch(arg) {
+                case "Boolean":
+                    b.putBoolean(key, true);
+                    break;
+                case "Long":
+                    b.putLong(key, Long.valueOf(42));
+                    break;
+                case "Int":
+                    b.putInt(key, 42);
+                    break;
+                case "String":
+                    b.putString(key, "42");
+                    break;
+                case "String2":
+                    b.putString(key, "42");
+                    break;
+                case "null":
+                    b.putObject(key, null);
+                    break;
+                case "Invalid":
+                     // Put anything different from Boolean/Long/Integer/String
+                    b.putFloat(key, 42f);
+                    break;
+                case "empty":
+                    // Do not put anything in the bundle and remove the key
+                    b = new Bundle();
+                    break;
+                default:
+                    return null;
+            }
+            return b;
+        }
+    }
+
+    /**
+     * Test: execute migration of BLUETOOTH_DATABASE and OPP_DATABASE without correct data
+     */
+    @Test
+    public void testIncompleteDbMigration() {
+        when(mMockContext.getDatabasePath("btopp.db"))
+            .thenReturn(mTargetContext.getDatabasePath("TestOppDb"));
+        when(mMockContext.getDatabasePath("bluetooth_db"))
+            .thenReturn(mTargetContext.getDatabasePath("TestBluetoothDb"));
+
+        BluetoothLegacyDbContentProvider fakeContentProvider =
+                new BluetoothLegacyDbContentProvider(mMockContext);
+        mMockContentResolver.addProvider(AUTHORITY, fakeContentProvider);
+
+        fakeContentProvider.mCursor = new FakeCursor(FAKE_SAMPLE);
+        assertThat(DataMigration.bluetoothDatabaseMigration(mMockContext)).isFalse();
+
+        fakeContentProvider.mCursor = new FakeCursor(FAKE_SAMPLE);
+        assertThat(DataMigration.oppDatabaseMigration(mMockContext)).isFalse();
+    }
+
+    private static final List<Pair<String, Object>> FAKE_SAMPLE =
+            Arrays.asList(
+                    new Pair("wrong_key", "wrong_content")
+    );
+
+    /**
+     * Test: execute migration of BLUETOOTH_DATABASE
+     */
+    @Test
+    public void testBluetoothDbMigration() {
+        when(mMockContext.getDatabasePath("bluetooth_db"))
+            .thenReturn(mTargetContext.getDatabasePath("TestBluetoothDb"));
+
+        BluetoothLegacyDbContentProvider fakeContentProvider =
+                new BluetoothLegacyDbContentProvider(mMockContext);
+        mMockContentResolver.addProvider(AUTHORITY, fakeContentProvider);
+
+        Cursor c = new FakeCursor(BLUETOOTH_DATABASE_SAMPLE);
+        fakeContentProvider.mCursor = c;
+        assertThat(DataMigration.bluetoothDatabaseMigration(mMockContext)).isTrue();
+
+        MetadataDatabase database = MetadataDatabase.createDatabaseWithoutMigration(mMockContext);
+        Metadata metadata = database.load().get(0);
+
+        Log.d(TAG, "Metadata migrated: " + metadata);
+
+        assertWithMessage("Address mismatch")
+            .that(metadata.getAddress()).isEqualTo("my_address");
+        assertWithMessage("Connection policy mismatch")
+            .that(metadata.getProfileConnectionPolicy(BluetoothProfile.A2DP))
+            .isEqualTo(CONNECTION_POLICY_FORBIDDEN);
+        assertWithMessage("Custom metadata mismatch")
+            .that(metadata.getCustomizedMeta(BluetoothDevice.METADATA_UNTETHERED_LEFT_CHARGING))
+            .isEqualTo(CUSTOM_META);
+    }
+
+    private static final byte[] CUSTOM_META =  new byte[]{ 42, 43, 44};
+
+    private static final List<Pair<String, Object>> BLUETOOTH_DATABASE_SAMPLE =
+            Arrays.asList(
+                    new Pair("address", "my_address"),
+                    new Pair("migrated", 1),
+                    new Pair("a2dpSupportsOptionalCodecs", OPTIONAL_CODECS_NOT_SUPPORTED),
+                    new Pair("a2dpOptionalCodecsEnabled", OPTIONAL_CODECS_PREF_DISABLED),
+                    new Pair("last_active_time", 42),
+                    new Pair("is_active_a2dp_device", 1),
+
+                    // connection_policy
+                    new Pair("a2dp_connection_policy", CONNECTION_POLICY_FORBIDDEN),
+                    new Pair("a2dp_sink_connection_policy", CONNECTION_POLICY_FORBIDDEN),
+                    new Pair("hfp_connection_policy", CONNECTION_POLICY_FORBIDDEN),
+                    new Pair("hfp_client_connection_policy", CONNECTION_POLICY_FORBIDDEN),
+                    new Pair("hid_host_connection_policy", CONNECTION_POLICY_FORBIDDEN),
+                    new Pair("pan_connection_policy", CONNECTION_POLICY_FORBIDDEN),
+                    new Pair("pbap_connection_policy", CONNECTION_POLICY_FORBIDDEN),
+                    new Pair("pbap_client_connection_policy", CONNECTION_POLICY_FORBIDDEN),
+                    new Pair("map_connection_policy", CONNECTION_POLICY_FORBIDDEN),
+                    new Pair("sap_connection_policy", CONNECTION_POLICY_FORBIDDEN),
+                    new Pair("hearing_aid_connection_policy", CONNECTION_POLICY_FORBIDDEN),
+                    new Pair("hap_client_connection_policy", CONNECTION_POLICY_FORBIDDEN),
+                    new Pair("map_client_connection_policy", CONNECTION_POLICY_FORBIDDEN),
+                    new Pair("le_audio_connection_policy", CONNECTION_POLICY_FORBIDDEN),
+                    new Pair("volume_control_connection_policy", CONNECTION_POLICY_FORBIDDEN),
+                    new Pair("csip_set_coordinator_connection_policy", CONNECTION_POLICY_FORBIDDEN),
+                    new Pair("le_call_control_connection_policy", CONNECTION_POLICY_FORBIDDEN),
+                    new Pair("bass_client_connection_policy", CONNECTION_POLICY_FORBIDDEN),
+                    new Pair("battery_connection_policy", CONNECTION_POLICY_FORBIDDEN),
+
+                    // Custom meta-data
+                    new Pair("manufacturer_name", CUSTOM_META),
+                    new Pair("model_name", CUSTOM_META),
+                    new Pair("software_version", CUSTOM_META),
+                    new Pair("hardware_version", CUSTOM_META),
+                    new Pair("companion_app", CUSTOM_META),
+                    new Pair("main_icon", CUSTOM_META),
+                    new Pair("is_untethered_headset", CUSTOM_META),
+                    new Pair("untethered_left_icon", CUSTOM_META),
+                    new Pair("untethered_right_icon", CUSTOM_META),
+                    new Pair("untethered_case_icon", CUSTOM_META),
+                    new Pair("untethered_left_battery", CUSTOM_META),
+                    new Pair("untethered_right_battery", CUSTOM_META),
+                    new Pair("untethered_case_battery", CUSTOM_META),
+                    new Pair("untethered_left_charging", CUSTOM_META),
+                    new Pair("untethered_right_charging", CUSTOM_META),
+                    new Pair("untethered_case_charging", CUSTOM_META),
+                    new Pair("enhanced_settings_ui_uri", CUSTOM_META),
+                    new Pair("device_type", CUSTOM_META),
+                    new Pair("main_battery", CUSTOM_META),
+                    new Pair("main_charging", CUSTOM_META),
+                    new Pair("main_low_battery_threshold", CUSTOM_META),
+                    new Pair("untethered_left_low_battery_threshold", CUSTOM_META),
+                    new Pair("untethered_right_low_battery_threshold", CUSTOM_META),
+                    new Pair("untethered_case_low_battery_threshold", CUSTOM_META),
+                    new Pair("spatial_audio", CUSTOM_META),
+                    new Pair("fastpair_customized", CUSTOM_META)
+    );
+
+    /**
+     * Test: execute migration of OPP_DATABASE
+     */
+    @Test
+    public void testOppDbMigration() {
+        when(mMockContext.getDatabasePath("btopp.db"))
+            .thenReturn(mTargetContext.getDatabasePath("TestOppDb"));
+
+        BluetoothLegacyDbContentProvider fakeContentProvider =
+                new BluetoothLegacyDbContentProvider(mMockContext);
+        mMockContentResolver.addProvider(AUTHORITY, fakeContentProvider);
+
+        Cursor c = new FakeCursor(OPP_DATABASE_SAMPLE);
+        fakeContentProvider.mCursor = c;
+        assertThat(DataMigration.oppDatabaseMigration(mMockContext)).isTrue();
+    }
+
+    private static final List<Pair<String, Object>> OPP_DATABASE_SAMPLE =
+            Arrays.asList(
+                    // String
+                    new Pair(BluetoothShare.URI, "content"),
+                    new Pair(BluetoothShare.FILENAME_HINT, "content"),
+                    new Pair(BluetoothShare.MIMETYPE, "content"),
+                    new Pair(BluetoothShare.DESTINATION, "content"),
+
+                    // Int
+                    new Pair(BluetoothShare.VISIBILITY, 42),
+                    new Pair(BluetoothShare.USER_CONFIRMATION, 42),
+                    new Pair(BluetoothShare.DIRECTION, 42),
+                    new Pair(BluetoothShare.STATUS, 42),
+                    new Pair("scanned" /* Constants.MEDIA_SCANNED */, 42),
+
+                    // Long
+                    new Pair(BluetoothShare.TOTAL_BYTES, 42L),
+                    new Pair(BluetoothShare.TIMESTAMP, 42L)
+    );
+
+    private static class BluetoothLegacyDbContentProvider extends MockContentProvider {
+        BluetoothLegacyDbContentProvider(Context ctx) {
+            super(ctx);
+        }
+        String mLastMethod = null;
+        Cursor mCursor = null;
+        int mCallCount = 0;
+        int mBundleCount = 0;
+        @Override
+        public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
+                String sortOrder) {
+            mBundleCount++;
+            return mCursor;
+        }
+        @Override
+        public Bundle call(String method, String arg, Bundle extras) {
+            mCallCount++;
+            return null;
+        }
+    }
+
+    private static class FakeCursor extends MockCursor {
+        int mNumItem = 1;
+        List<Pair<String, Object>> mRows;
+
+        FakeCursor(List<Pair<String, Object>> rows) {
+            mRows = rows;
+        }
+
+        @Override
+        public String getString(int columnIndex) {
+            return (String) (mRows.get(columnIndex).second);
+        }
+
+        @Override
+        public byte[] getBlob(int columnIndex) {
+            return (byte[]) (mRows.get(columnIndex).second);
+        }
+
+        @Override
+        public int getInt(int columnIndex) {
+            return (int) (mRows.get(columnIndex).second);
+        }
+
+        @Override
+        public long getLong(int columnIndex) {
+            return (long) (mRows.get(columnIndex).second);
+        }
+
+        @Override
+        public boolean moveToNext() {
+            return mNumItem-- > 0;
+        }
+
+        @Override
+        public int getCount() {
+            return 1;
+        }
+
+        @Override
+        public int getColumnIndexOrThrow(String columnName) {
+            for (int i = 0; i < mRows.size(); i++) {
+                if (columnName.equals(mRows.get(i).first)) {
+                    return i;
+                }
+            }
+            throw new IllegalArgumentException("No such column: " + columnName);
+        }
+
+        @Override
+        public int getColumnIndex(String columnName) {
+            for (int i = 0; i < mRows.size(); i++) {
+                if (columnName.equals(mRows.get(i).first)) {
+                    return i;
+                }
+            }
+            return -1;
+        }
+
+        @Override
+        public void close() {}
+    }
+}
diff --git a/android/app/tests/unit/src/com/android/bluetooth/hearingaid/HearingAidServiceTest.java b/android/app/tests/unit/src/com/android/bluetooth/hearingaid/HearingAidServiceTest.java
index 8ab98d3..cf74dfd 100644
--- a/android/app/tests/unit/src/com/android/bluetooth/hearingaid/HearingAidServiceTest.java
+++ b/android/app/tests/unit/src/com/android/bluetooth/hearingaid/HearingAidServiceTest.java
@@ -180,6 +180,11 @@
         Assert.assertEquals(newState, intent.getIntExtra(BluetoothProfile.EXTRA_STATE, -1));
         Assert.assertEquals(prevState, intent.getIntExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE,
                 -1));
+        if (newState == BluetoothProfile.STATE_CONNECTED) {
+            // ActiveDeviceManager calls setActiveDevice when connected.
+            mService.setActiveDevice(device);
+        }
+
     }
 
     private void verifyNoConnectionStateIntent(int timeoutMs, BluetoothDevice device) {
diff --git a/android/app/tests/unit/src/com/android/bluetooth/le_audio/LeAudioBroadcastServiceTest.java b/android/app/tests/unit/src/com/android/bluetooth/le_audio/LeAudioBroadcastServiceTest.java
index d74b622..0219258 100644
--- a/android/app/tests/unit/src/com/android/bluetooth/le_audio/LeAudioBroadcastServiceTest.java
+++ b/android/app/tests/unit/src/com/android/bluetooth/le_audio/LeAudioBroadcastServiceTest.java
@@ -172,6 +172,8 @@
         doReturn(mDatabaseManager).when(mAdapterService).getDatabase();
         doReturn(true, false).when(mAdapterService).isStartedProfile(anyString());
         doReturn(true).when(mAdapterService).isLeAudioBroadcastSourceSupported();
+        doReturn((long)(1 << BluetoothProfile.LE_AUDIO_BROADCAST) | (1 << BluetoothProfile.LE_AUDIO))
+                .when(mAdapterService).getSupportedProfilesBitMask();
 
         mAdapter = BluetoothAdapter.getDefaultAdapter();
 
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 a11ee69..a77b3fd 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
@@ -180,8 +180,8 @@
         doAnswer(invocation -> mBondedDevices.toArray(new BluetoothDevice[]{})).when(
                 mAdapterService).getBondedDevices();
 
+        LeAudioNativeInterface.setInstance(mNativeInterface);
         startService();
-        mService.mLeAudioNativeInterface = mNativeInterface;
         mService.mAudioManager = mAudioManager;
         mService.mVolumeControlService = mVolumeControlService;
 
@@ -217,6 +217,8 @@
                 .getBondState(any(BluetoothDevice.class));
         doReturn(new ParcelUuid[]{BluetoothUuid.LE_AUDIO}).when(mAdapterService)
                 .getRemoteUuids(any(BluetoothDevice.class));
+
+        verify(mNativeInterface, timeout(3000).times(1)).init(any());
     }
 
     @After
@@ -231,6 +233,7 @@
         mTargetContext.unregisterReceiver(mLeAudioIntentReceiver);
         mDeviceQueueMap.clear();
         TestUtils.clearAdapterService(mAdapterService);
+        LeAudioNativeInterface.setInstance(null);
     }
 
     private void startService() throws TimeoutException {
@@ -1034,7 +1037,7 @@
     @Test
     public void testGetActiveDevices() {
         int groupId = 1;
-        int direction = 1;
+        int direction = 2;
         int snkAudioLocation = 3;
         int srcAudioLocation = 4;
         int availableContexts = 5;
@@ -1085,8 +1088,7 @@
         mService.messageFromNative(groupStatusChangedEvent);
     }
 
-    private void injectAudioConfChanged(int groupId, Integer availableContexts) {
-        int direction = 1;
+    private void injectAudioConfChanged(int groupId, Integer availableContexts, int direction) {
         int snkAudioLocation = 3;
         int srcAudioLocation = 4;
         int eventType = LeAudioStackEvent.EVENT_TYPE_AUDIO_CONF_CHANGED;
@@ -1109,7 +1111,7 @@
         doReturn(true).when(mNativeInterface).connectLeAudio(any(BluetoothDevice.class));
         connectTestDevice(mSingleDevice, testGroupId);
         injectAudioConfChanged(testGroupId, BluetoothLeAudio.CONTEXT_TYPE_MEDIA |
-                         BluetoothLeAudio.CONTEXT_TYPE_CONVERSATIONAL);
+                         BluetoothLeAudio.CONTEXT_TYPE_CONVERSATIONAL, 3);
         injectGroupStatusChange(testGroupId, BluetoothLeAudio.GROUP_STATUS_ACTIVE);
 
         String action = BluetoothLeAudio.ACTION_LE_AUDIO_ACTIVE_DEVICE_CHANGED;
@@ -1131,7 +1133,7 @@
         String action = BluetoothLeAudio.ACTION_LE_AUDIO_ACTIVE_DEVICE_CHANGED;
         Integer contexts = BluetoothLeAudio.CONTEXT_TYPE_MEDIA |
         BluetoothLeAudio.CONTEXT_TYPE_CONVERSATIONAL;
-        injectAudioConfChanged(testGroupId, contexts);
+        injectAudioConfChanged(testGroupId, contexts, 3);
 
         Intent intent = TestUtils.waitForNoIntent(TIMEOUT_MS, mDeviceQueueMap.get(mSingleDevice));
         assertThat(intent).isNull();
@@ -1146,7 +1148,7 @@
 
         String action = BluetoothLeAudio.ACTION_LE_AUDIO_ACTIVE_DEVICE_CHANGED;
 
-        injectAudioConfChanged(testGroupId, 0);
+        injectAudioConfChanged(testGroupId, 0, 3);
         Intent intent = TestUtils.waitForNoIntent(TIMEOUT_MS, mDeviceQueueMap.get(mSingleDevice));
         assertThat(intent).isNull();
     }
@@ -1192,7 +1194,7 @@
         connectTestDevice(mSingleDevice, testGroupId);
 
         injectAudioConfChanged(testGroupId, BluetoothLeAudio.CONTEXT_TYPE_MEDIA |
-                                 BluetoothLeAudio.CONTEXT_TYPE_CONVERSATIONAL);
+                                 BluetoothLeAudio.CONTEXT_TYPE_CONVERSATIONAL, 3);
 
         sendEventAndVerifyIntentForGroupStatusChanged(testGroupId, LeAudioStackEvent.GROUP_STATUS_ACTIVE);
         sendEventAndVerifyIntentForGroupStatusChanged(testGroupId, LeAudioStackEvent.GROUP_STATUS_INACTIVE);
@@ -1285,7 +1287,7 @@
     @Test
     public void testLeadGroupDeviceDisconnects() {
         int groupId = 1;
-        int direction = 1;
+        int direction = 2;
         int snkAudioLocation = 3;
         int srcAudioLocation = 4;
         int availableContexts = 5;
@@ -1350,7 +1352,7 @@
     @Test
     public void testLeadGroupDeviceReconnects() {
         int groupId = 1;
-        int direction = 1;
+        int direction = 2;
         int snkAudioLocation = 3;
         int srcAudioLocation = 4;
         int availableContexts = 5;
@@ -1417,6 +1419,7 @@
     public void testVolumeCache() {
         int groupId = 1;
         int volume = 100;
+        int direction = 2;
         int availableContexts = 4;
 
         doReturn(true).when(mNativeInterface).connectLeAudio(any(BluetoothDevice.class));
@@ -1429,7 +1432,7 @@
                         ArgumentCaptor.forClass(BluetoothProfileConnectionInfo.class);
 
         //Add location support.
-        injectAudioConfChanged(groupId, availableContexts);
+        injectAudioConfChanged(groupId, availableContexts, direction);
 
         doReturn(-1).when(mVolumeControlService).getAudioDeviceGroupVolume(groupId);
         //Set group and device as active.
diff --git a/android/app/tests/unit/src/com/android/bluetooth/map/BluetoothMapAppParamsTest.java b/android/app/tests/unit/src/com/android/bluetooth/map/BluetoothMapAppParamsTest.java
new file mode 100644
index 0000000..f3d9361
--- /dev/null
+++ b/android/app/tests/unit/src/com/android/bluetooth/map/BluetoothMapAppParamsTest.java
@@ -0,0 +1,296 @@
+/*
+ * 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.map;
+
+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 com.android.bluetooth.SignedLongLong;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.nio.ByteBuffer;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class BluetoothMapAppParamsTest {
+    public static final long TEST_PARAMETER_MASK = 1;
+    public static final int TEST_MAX_LIST_COUNT = 3;
+    public static final int TEST_START_OFFSET = 1;
+    public static final int TEST_FILTER_MESSAGE_TYPE = 1;
+    public static final int TEST_FILTER_PRIORITY = 1;
+    public static final int TEST_ATTACHMENT = 1;
+    public static final int TEST_CHARSET = 1;
+    public static final int TEST_CHAT_STATE = 1;
+    public static final long TEST_ID_HIGH = 1;
+    public static final long TEST_ID_LOW = 1;
+    public static final int TEST_CONVO_LISTING_SIZE = 1;
+    public static final long TEST_COUNT_LOW = 1;
+    public static final long TEST_COUNT_HIGH = 1;
+    public static final long TEST_CONVO_PARAMETER_MASK = 1;
+    public static final String TEST_FILTER_CONVO_ID = "1111";
+    public static final long TEST_FILTER_LAST_ACTIVITY_BEGIN = 0;
+    public static final long TEST_FILTER_LAST_ACTIVITY_END = 0;
+    public static final String TEST_FILTER_MSG_HANDLE = "1";
+    public static final String TEST_FILTER_ORIGINATOR = "test_filter_originator";
+    public static final long TEST_FILTER_PERIOD_BEGIN = 0;
+    public static final long TEST_FILTER_PERIOD_END = 0;
+    public static final int TEST_FILTER_PRESENCE = 1;
+    public static final int TEST_FILTER_READ_STATUS = 1;
+    public static final String TEST_FILTER_RECIPIENT = "test_filter_recipient";
+    public static final int TEST_FOLDER_LISTING_SIZE = 1;
+    public static final int TEST_FILTER_UID_PRESENT = 1;
+    public static final int TEST_FRACTION_DELIVER = 1;
+    public static final int TEST_FRACTION_REQUEST = 1;
+    public static final long TEST_LAST_ACTIVITY = 0;
+    public static final int TEST_MAS_INSTANCE_ID = 1;
+    public static final int TEST_MESSAGE_LISTING_SIZE = 1;
+    public static final long TEST_MSE_TIME = 0;
+    public static final int TEST_NEW_MESSAGE = 1;
+    public static final long TEST_NOTIFICATION_FILTER = 1;
+    public static final int TEST_NOTIFICATION_STATUS = 1;
+    public static final int TEST_PRESENCE_AVAILABILITY = 1;
+    public static final String TEST_PRESENCE_STATUS = "test_presence_status";
+    public static final int TEST_RETRY = 1;
+    public static final int TEST_STATUS_INDICATOR = 1;
+    public static final int TEST_STATUS_VALUE = 1;
+    public static final int TEST_SUBJECT_LENGTH = 1;
+    public static final int TEST_TRANSPARENT = 1;
+
+    @Test
+    public void settersAndGetters() throws Exception {
+        ByteBuffer ret = ByteBuffer.allocate(16);
+        ret.putLong(TEST_COUNT_HIGH);
+        ret.putLong(TEST_COUNT_LOW);
+
+        BluetoothMapAppParams appParams = new BluetoothMapAppParams();
+        appParams.setParameterMask(TEST_PARAMETER_MASK);
+        appParams.setMaxListCount(TEST_MAX_LIST_COUNT);
+        appParams.setStartOffset(TEST_START_OFFSET);
+        appParams.setFilterMessageType(TEST_FILTER_MESSAGE_TYPE);
+        appParams.setFilterPriority(TEST_FILTER_PRIORITY);
+        appParams.setAttachment(TEST_ATTACHMENT);
+        appParams.setCharset(TEST_CHARSET);
+        appParams.setChatState(TEST_CHAT_STATE);
+        appParams.setChatStateConvoId(TEST_ID_HIGH, TEST_ID_LOW);
+        appParams.setConvoListingSize(TEST_CONVO_LISTING_SIZE);
+        appParams.setConvoListingVerCounter(TEST_COUNT_LOW, TEST_COUNT_HIGH);
+        appParams.setConvoParameterMask(TEST_CONVO_PARAMETER_MASK);
+        appParams.setDatabaseIdentifier(TEST_ID_HIGH, TEST_ID_LOW);
+        appParams.setFilterConvoId(TEST_FILTER_CONVO_ID);
+        appParams.setFilterMsgHandle(TEST_FILTER_MSG_HANDLE);
+        appParams.setFilterOriginator(TEST_FILTER_ORIGINATOR);
+        appParams.setFilterPresence(TEST_FILTER_PRESENCE);
+        appParams.setFilterReadStatus(TEST_FILTER_READ_STATUS);
+        appParams.setFilterRecipient(TEST_FILTER_RECIPIENT);
+        appParams.setFolderListingSize(TEST_FOLDER_LISTING_SIZE);
+        appParams.setFilterUidPresent(TEST_FILTER_UID_PRESENT);
+        appParams.setFolderVerCounter(TEST_COUNT_LOW, TEST_COUNT_HIGH);
+        appParams.setFractionDeliver(TEST_FRACTION_DELIVER);
+        appParams.setFractionRequest(TEST_FRACTION_REQUEST);
+        appParams.setMasInstanceId(TEST_MAS_INSTANCE_ID);
+        appParams.setMessageListingSize(TEST_MESSAGE_LISTING_SIZE);
+        appParams.setNewMessage(TEST_NEW_MESSAGE);
+        appParams.setNotificationFilter(TEST_NOTIFICATION_FILTER);
+        appParams.setNotificationStatus(TEST_NOTIFICATION_STATUS);
+        appParams.setPresenceAvailability(TEST_PRESENCE_AVAILABILITY);
+        appParams.setPresenceStatus(TEST_PRESENCE_STATUS);
+        appParams.setRetry(TEST_RETRY);
+        appParams.setStatusIndicator(TEST_STATUS_INDICATOR);
+        appParams.setStatusValue(TEST_STATUS_VALUE);
+        appParams.setSubjectLength(TEST_SUBJECT_LENGTH);
+        appParams.setTransparent(TEST_TRANSPARENT);
+
+        assertThat(appParams.getParameterMask()).isEqualTo(TEST_PARAMETER_MASK);
+        assertThat(appParams.getMaxListCount()).isEqualTo(TEST_MAX_LIST_COUNT);
+        assertThat(appParams.getStartOffset()).isEqualTo(TEST_START_OFFSET);
+        assertThat(appParams.getFilterMessageType()).isEqualTo(TEST_FILTER_MESSAGE_TYPE);
+        assertThat(appParams.getFilterPriority()).isEqualTo(TEST_FILTER_PRIORITY);
+        assertThat(appParams.getAttachment()).isEqualTo(TEST_ATTACHMENT);
+        assertThat(appParams.getCharset()).isEqualTo(TEST_CHARSET);
+        assertThat(appParams.getChatState()).isEqualTo(TEST_CHAT_STATE);
+        assertThat(appParams.getChatStateConvoId()).isEqualTo(new SignedLongLong(
+                TEST_ID_HIGH, TEST_ID_LOW));
+        assertThat(appParams.getChatStateConvoIdByteArray()).isEqualTo(ret.array());
+        assertThat(appParams.getChatStateConvoIdString()).isEqualTo(new String(ret.array()));
+        assertThat(appParams.getConvoListingSize()).isEqualTo(TEST_CONVO_LISTING_SIZE);
+        assertThat(appParams.getConvoListingVerCounter()).isEqualTo(
+                ret.array());
+        assertThat(appParams.getConvoParameterMask()).isEqualTo(TEST_CONVO_PARAMETER_MASK);
+        assertThat(appParams.getDatabaseIdentifier()).isEqualTo(ret.array());
+        assertThat(appParams.getFilterConvoId()).isEqualTo(
+                SignedLongLong.fromString(TEST_FILTER_CONVO_ID));
+        assertThat(appParams.getFilterConvoIdString()).isEqualTo(BluetoothMapUtils.getLongAsString(
+                SignedLongLong.fromString(TEST_FILTER_CONVO_ID).getLeastSignificantBits()));
+        assertThat(appParams.getFilterMsgHandle()).isEqualTo(
+                BluetoothMapUtils.getLongFromString(TEST_FILTER_MSG_HANDLE));
+        assertThat(appParams.getFilterMsgHandleString()).isEqualTo(
+                BluetoothMapUtils.getLongAsString(appParams.getFilterMsgHandle()));
+        assertThat(appParams.getFilterOriginator()).isEqualTo(TEST_FILTER_ORIGINATOR);
+        assertThat(appParams.getFilterPresence()).isEqualTo(TEST_FILTER_PRESENCE);
+        assertThat(appParams.getFilterReadStatus()).isEqualTo(TEST_FILTER_READ_STATUS);
+        assertThat(appParams.getFilterRecipient()).isEqualTo(TEST_FILTER_RECIPIENT);
+        assertThat(appParams.getFolderListingSize()).isEqualTo(TEST_FOLDER_LISTING_SIZE);
+        assertThat(appParams.getFilterUidPresent()).isEqualTo(TEST_FILTER_UID_PRESENT);
+        assertThat(appParams.getFolderVerCounter()).isEqualTo(ret.array());
+        assertThat(appParams.getFractionDeliver()).isEqualTo(TEST_FRACTION_DELIVER);
+        assertThat(appParams.getFractionRequest()).isEqualTo(TEST_FRACTION_REQUEST);
+        assertThat(appParams.getMasInstanceId()).isEqualTo(TEST_MAS_INSTANCE_ID);
+        assertThat(appParams.getMessageListingSize()).isEqualTo(TEST_MESSAGE_LISTING_SIZE);
+        assertThat(appParams.getNewMessage()).isEqualTo(TEST_NEW_MESSAGE);
+        assertThat(appParams.getNotificationFilter()).isEqualTo(TEST_NOTIFICATION_FILTER);
+        assertThat(appParams.getNotificationStatus()).isEqualTo(TEST_NOTIFICATION_STATUS);
+        assertThat(appParams.getPresenceAvailability()).isEqualTo(TEST_PRESENCE_AVAILABILITY);
+        assertThat(appParams.getPresenceStatus()).isEqualTo(TEST_PRESENCE_STATUS);
+        assertThat(appParams.getRetry()).isEqualTo(TEST_RETRY);
+        assertThat(appParams.getStatusIndicator()).isEqualTo(TEST_STATUS_INDICATOR);
+        assertThat(appParams.getStatusValue()).isEqualTo(TEST_STATUS_VALUE);
+        assertThat(appParams.getSubjectLength()).isEqualTo(TEST_SUBJECT_LENGTH);
+        assertThat(appParams.getTransparent()).isEqualTo(TEST_TRANSPARENT);
+    }
+
+    @Test
+    public void setAndGetFilterLastActivity_withString() throws Exception {
+        BluetoothMapAppParams appParams = new BluetoothMapAppParams();
+        appParams.setFilterLastActivityBegin(TEST_FILTER_LAST_ACTIVITY_BEGIN);
+        appParams.setFilterLastActivityEnd(TEST_FILTER_LAST_ACTIVITY_END);
+        String lastActivityBeginString = appParams.getFilterLastActivityBeginString();
+        String lastActivityEndString = appParams.getFilterLastActivityEndString();
+
+        appParams.setFilterLastActivityBegin(lastActivityBeginString);
+        appParams.setFilterLastActivityEnd(lastActivityEndString);
+
+        assertThat(appParams.getFilterLastActivityBegin()).isEqualTo(
+                TEST_FILTER_LAST_ACTIVITY_BEGIN);
+        assertThat(appParams.getFilterLastActivityBegin()).isEqualTo(TEST_FILTER_LAST_ACTIVITY_END);
+    }
+
+    @Test
+    public void setAndGetLastActivity_withString() throws Exception {
+        BluetoothMapAppParams appParams = new BluetoothMapAppParams();
+        appParams.setLastActivity(TEST_LAST_ACTIVITY);
+        String lastActivityString = appParams.getLastActivityString();
+
+        appParams.setLastActivity(lastActivityString);
+
+        assertThat(appParams.getLastActivity()).isEqualTo(TEST_LAST_ACTIVITY);
+    }
+
+    @Test
+    public void setAndGetFilterPeriod_withString() throws Exception {
+        BluetoothMapAppParams appParams = new BluetoothMapAppParams();
+        appParams.setFilterPeriodBegin(TEST_FILTER_PERIOD_BEGIN);
+        appParams.setFilterPeriodEnd(TEST_FILTER_PERIOD_END);
+        String filterPeriodBeginString = appParams.getFilterPeriodBeginString();
+        String filterPeriodEndString = appParams.getFilterPeriodEndString();
+
+        appParams.setFilterPeriodBegin(filterPeriodBeginString);
+        appParams.setFilterPeriodEnd(filterPeriodEndString);
+
+        assertThat(appParams.getFilterPeriodBegin()).isEqualTo(TEST_FILTER_PERIOD_BEGIN);
+        assertThat(appParams.getFilterPeriodEnd()).isEqualTo(TEST_FILTER_PERIOD_END);
+    }
+
+    @Test
+    public void setAndGetMseTime_withString() throws Exception {
+        BluetoothMapAppParams appParams = new BluetoothMapAppParams();
+        appParams.setMseTime(TEST_MSE_TIME);
+        String mseTimeString = appParams.getMseTimeString();
+
+        appParams.setMseTime(mseTimeString);
+
+        assertThat(appParams.getMseTime()).isEqualTo(TEST_MSE_TIME);
+    }
+
+    @Test
+    public void setters_withIllegalArguments() {
+        BluetoothMapAppParams appParams = new BluetoothMapAppParams();
+        int ILLEGAL_PARAMETER_INT = -2;
+        long ILLEGAL_PARAMETER_LONG = -2;
+
+        assertThrows(IllegalArgumentException.class,
+                () -> appParams.setAttachment(ILLEGAL_PARAMETER_INT));
+        assertThrows(IllegalArgumentException.class,
+                () -> appParams.setCharset(ILLEGAL_PARAMETER_INT));
+        assertThrows(IllegalArgumentException.class,
+                () -> appParams.setChatState(ILLEGAL_PARAMETER_INT));
+        assertThrows(IllegalArgumentException.class,
+                () -> appParams.setConvoListingSize(ILLEGAL_PARAMETER_INT));
+        assertThrows(IllegalArgumentException.class,
+                () -> appParams.setConvoParameterMask(ILLEGAL_PARAMETER_LONG));
+        assertThrows(IllegalArgumentException.class,
+                () -> appParams.setFilterMessageType(ILLEGAL_PARAMETER_INT));
+        assertThrows(IllegalArgumentException.class,
+                () -> appParams.setFilterPresence(ILLEGAL_PARAMETER_INT));
+        assertThrows(IllegalArgumentException.class,
+                () -> appParams.setFilterPriority(ILLEGAL_PARAMETER_INT));
+        assertThrows(IllegalArgumentException.class,
+                () -> appParams.setFilterReadStatus(ILLEGAL_PARAMETER_INT));
+        assertThrows(IllegalArgumentException.class,
+                () -> appParams.setFilterUidPresent(ILLEGAL_PARAMETER_INT));
+        assertThrows(IllegalArgumentException.class,
+                () -> appParams.setFolderListingSize(ILLEGAL_PARAMETER_INT));
+        assertThrows(IllegalArgumentException.class,
+                () -> appParams.setFractionDeliver(ILLEGAL_PARAMETER_INT));
+        assertThrows(IllegalArgumentException.class,
+                () -> appParams.setFractionRequest(ILLEGAL_PARAMETER_INT));
+        assertThrows(IllegalArgumentException.class,
+                () -> appParams.setMasInstanceId(ILLEGAL_PARAMETER_INT));
+        assertThrows(IllegalArgumentException.class,
+                () -> appParams.setMaxListCount(ILLEGAL_PARAMETER_INT));
+        assertThrows(IllegalArgumentException.class,
+                () -> appParams.setMessageListingSize(ILLEGAL_PARAMETER_INT));
+        assertThrows(IllegalArgumentException.class,
+                () -> appParams.setNewMessage(ILLEGAL_PARAMETER_INT));
+        assertThrows(IllegalArgumentException.class,
+                () -> appParams.setNotificationFilter(ILLEGAL_PARAMETER_LONG));
+        assertThrows(IllegalArgumentException.class,
+                () -> appParams.setNotificationStatus(ILLEGAL_PARAMETER_INT));
+        assertThrows(IllegalArgumentException.class,
+                () -> appParams.setParameterMask(ILLEGAL_PARAMETER_LONG));
+        assertThrows(IllegalArgumentException.class,
+                () -> appParams.setPresenceAvailability(ILLEGAL_PARAMETER_INT));
+        assertThrows(IllegalArgumentException.class,
+                () -> appParams.setRetry(ILLEGAL_PARAMETER_INT));
+        assertThrows(IllegalArgumentException.class,
+                () -> appParams.setStartOffset(ILLEGAL_PARAMETER_INT));
+        assertThrows(IllegalArgumentException.class,
+                () -> appParams.setStatusIndicator(ILLEGAL_PARAMETER_INT));
+        assertThrows(IllegalArgumentException.class,
+                () -> appParams.setStatusValue(ILLEGAL_PARAMETER_INT));
+        assertThrows(IllegalArgumentException.class,
+                () -> appParams.setSubjectLength(ILLEGAL_PARAMETER_INT));
+        assertThrows(IllegalArgumentException.class,
+                () -> appParams.setTransparent(ILLEGAL_PARAMETER_INT));
+    }
+
+    @Test
+    public void setters_withIllegalStrings() {
+        BluetoothMapAppParams appParams = new BluetoothMapAppParams();
+
+        appParams.setFilterConvoId(" ");
+        appParams.setFilterMsgHandle("=");
+
+        assertThat(appParams.getFilterConvoId()).isNull();
+        assertThat(appParams.getFilterMsgHandle()).isEqualTo(-1);
+    }
+}
\ No newline at end of file
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
new file mode 100644
index 0000000..8f78989
--- /dev/null
+++ b/android/app/tests/unit/src/com/android/bluetooth/map/BluetoothMapContentTest.java
@@ -0,0 +1,123 @@
+/*
+ * 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.map;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import android.content.ContentResolver;
+import android.database.Cursor;
+import android.provider.ContactsContract;
+import android.provider.Telephony;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.bluetooth.BluetoothMethodProxy;
+
+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;
+
+@RunWith(AndroidJUnit4.class)
+public class BluetoothMapContentTest {
+    private static final String TEST_TEXT = "text";
+
+    @Mock
+    private ContentResolver mContentResolver;
+    @Spy
+    private BluetoothMethodProxy mMapMethodProxy = BluetoothMethodProxy.getInstance();
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        BluetoothMethodProxy.setInstanceForTesting(mMapMethodProxy);
+    }
+
+    @After
+    public void tearDown() {
+        BluetoothMethodProxy.setInstanceForTesting(null);
+    }
+
+    @Test
+    public void getTextPartsMms() {
+        final long id = 1111;
+        Cursor cursor = mock(Cursor.class);
+        when(cursor.moveToFirst()).thenReturn(true);
+        when(cursor.getColumnIndex("ct")).thenReturn(1);
+        when(cursor.getString(1)).thenReturn("text/plain");
+        when(cursor.getColumnIndex("text")).thenReturn(2);
+        when(cursor.getString(2)).thenReturn(TEST_TEXT);
+        doReturn(cursor).when(mMapMethodProxy).contentResolverQuery(any(), any(), any(), any(),
+                any(), any());
+
+        assertThat(BluetoothMapContent.getTextPartsMms(mContentResolver, id)).isEqualTo(TEST_TEXT);
+    }
+
+    @Test
+    public void getContactNameFromPhone() {
+        String phoneName = "testPhone";
+        Cursor cursor = mock(Cursor.class);
+        when(cursor.getColumnIndex(ContactsContract.Contacts.DISPLAY_NAME)).thenReturn(1);
+        when(cursor.getCount()).thenReturn(1);
+        when(cursor.getString(1)).thenReturn(TEST_TEXT);
+        doReturn(cursor).when(mMapMethodProxy).contentResolverQuery(any(), any(), any(), any(),
+                any(), any());
+
+        assertThat(
+                BluetoothMapContent.getContactNameFromPhone(phoneName, mContentResolver)).isEqualTo(
+                TEST_TEXT);
+    }
+
+    @Test
+    public void getCanonicalAddressSms() {
+        int threadId = 0;
+        Cursor cursor = mock(Cursor.class);
+        when(cursor.moveToFirst()).thenReturn(true);
+        when(cursor.getString(0)).thenReturn("recipientIdOne recipientIdTwo");
+        when(cursor.getColumnIndex(Telephony.CanonicalAddressesColumns.ADDRESS)).thenReturn(1);
+        when(cursor.getString(1)).thenReturn("recipientAddress");
+        doReturn(cursor).when(mMapMethodProxy).contentResolverQuery(any(), any(), any(), any(),
+                any(), any());
+
+        assertThat(
+                BluetoothMapContent.getCanonicalAddressSms(mContentResolver, threadId)).isEqualTo(
+                "recipientAddress");
+    }
+
+    @Test
+    public void getAddressMms() {
+        long id = 1111;
+        int type = 0;
+        Cursor cursor = mock(Cursor.class);
+        when(cursor.moveToFirst()).thenReturn(true);
+        when(cursor.getColumnIndex(Telephony.Mms.Addr.ADDRESS)).thenReturn(1);
+        when(cursor.getString(1)).thenReturn(TEST_TEXT);
+        doReturn(cursor).when(mMapMethodProxy).contentResolverQuery(any(), any(), any(), any(),
+                any(), any());
+
+        assertThat(BluetoothMapContent.getAddressMms(mContentResolver, id, type)).isEqualTo(
+                TEST_TEXT);
+    }
+}
\ No newline at end of file
diff --git a/android/app/tests/unit/src/com/android/bluetooth/map/BluetoothMapSmsPduTest.java b/android/app/tests/unit/src/com/android/bluetooth/map/BluetoothMapSmsPduTest.java
new file mode 100644
index 0000000..56c791c
--- /dev/null
+++ b/android/app/tests/unit/src/com/android/bluetooth/map/BluetoothMapSmsPduTest.java
@@ -0,0 +1,236 @@
+/*
+ * 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.map;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertThrows;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.telephony.SmsMessage;
+import android.telephony.TelephonyManager;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.bluetooth.map.BluetoothMapSmsPdu.SmsPdu;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+import java.util.ArrayList;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class BluetoothMapSmsPduTest {
+    private static final String TEST_TEXT = "test";
+    // Text below size 160 only need one SMS part
+    private static final String TEST_TEXT_WITH_TWO_SMS_PARTS = "a".repeat(161);
+    private static final String TEST_DESTINATION_ADDRESS = "12";
+    private static final int TEST_TYPE = BluetoothMapSmsPdu.SMS_TYPE_GSM;
+    private static final long TEST_DATE = 1;
+
+    private byte[] TEST_DATA;
+    private int TEST_ENCODING;
+    private int TEST_LANGUAGE_TABLE;
+
+    @Mock
+    private Context mTargetContext;
+    @Mock
+    private TelephonyManager mTelephonyManager;
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+        when(mTargetContext.getSystemServiceName(TelephonyManager.class)).thenReturn(
+                "TELEPHONY_SERVICE");
+        when(mTargetContext.getSystemService("TELEPHONY_SERVICE")).thenReturn(mTelephonyManager);
+
+        int[] ted = SmsMessage.calculateLength((CharSequence) TEST_TEXT, false);
+        TEST_ENCODING = ted[3];
+        TEST_LANGUAGE_TABLE = ted[4];
+        TEST_DATA = SmsMessage.getSubmitPdu(null, TEST_DESTINATION_ADDRESS, TEST_TEXT,
+                false).encodedMessage;
+    }
+
+    @Test
+    public void constructor_withDataAndType() {
+        SmsPdu smsPdu = new SmsPdu(TEST_DATA, TEST_TYPE);
+        int offsetExpected = 2 + ((TEST_DATA[2] + 1) & 0xff) / 2 + 5;
+
+        assertThat(smsPdu.getData()).isEqualTo(TEST_DATA);
+        assertThat(smsPdu.getEncoding()).isEqualTo(-1);
+        assertThat(smsPdu.getLanguageTable()).isEqualTo(-1);
+        assertThat(smsPdu.getLanguageShiftTable()).isEqualTo(-1);
+        assertThat(smsPdu.getUserDataMsgOffset()).isEqualTo(offsetExpected);
+        assertThat(smsPdu.getUserDataMsgSize()).isEqualTo(TEST_DATA.length - (offsetExpected));
+    }
+
+    @Test
+    public void constructor_withAllParameters() {
+        SmsPdu smsPdu = new SmsPdu(TEST_DATA, TEST_ENCODING, TEST_TYPE, TEST_LANGUAGE_TABLE);
+
+        assertThat(smsPdu.getData()).isEqualTo(TEST_DATA);
+        assertThat(smsPdu.getEncoding()).isEqualTo(TEST_ENCODING);
+        assertThat(smsPdu.getType()).isEqualTo(TEST_TYPE);
+        assertThat(smsPdu.getLanguageTable()).isEqualTo(TEST_LANGUAGE_TABLE);
+    }
+
+    @Test
+    public void getSubmitPdus_withTypeGSM_whenMsgCountIsMoreThanOne() throws Exception {
+        when(mTelephonyManager.getCurrentPhoneType()).thenReturn(TelephonyManager.PHONE_TYPE_GSM);
+
+        ArrayList<SmsPdu> pdus = BluetoothMapSmsPdu.getSubmitPdus(mTargetContext,
+                TEST_TEXT_WITH_TWO_SMS_PARTS, null);
+
+        assertThat(pdus.size()).isEqualTo(2);
+        assertThat(pdus.get(0).getType()).isEqualTo(BluetoothMapSmsPdu.SMS_TYPE_GSM);
+
+        BluetoothMapbMessageSms messageSmsToEncode = new BluetoothMapbMessageSms();
+        messageSmsToEncode.setType(BluetoothMapUtils.TYPE.SMS_GSM);
+        messageSmsToEncode.setFolder("placeholder");
+        messageSmsToEncode.setStatus(true);
+        messageSmsToEncode.setSmsBodyPdus(pdus);
+
+        byte[] encodedMessageSms = messageSmsToEncode.encode();
+        InputStream inputStream = new ByteArrayInputStream(encodedMessageSms);
+        BluetoothMapbMessage messageParsed = BluetoothMapbMessage.parse(inputStream,
+                BluetoothMapAppParams.CHARSET_NATIVE);
+
+        assertThat(messageParsed).isInstanceOf(BluetoothMapbMessageSms.class);
+        BluetoothMapbMessageSms messageSmsParsed = (BluetoothMapbMessageSms) messageParsed;
+        assertThat(messageSmsParsed.getSmsBody()).isEqualTo(TEST_TEXT_WITH_TWO_SMS_PARTS);
+    }
+
+    @Test
+    public void getSubmitPdus_withTypeCDMA() throws Exception {
+        when(mTelephonyManager.getCurrentPhoneType()).thenReturn(TelephonyManager.PHONE_TYPE_CDMA);
+
+        ArrayList<SmsPdu> pdus = BluetoothMapSmsPdu.getSubmitPdus(mTargetContext, TEST_TEXT, null);
+
+        assertThat(pdus.size()).isEqualTo(1);
+        assertThat(pdus.get(0).getType()).isEqualTo(BluetoothMapSmsPdu.SMS_TYPE_CDMA);
+
+        BluetoothMapbMessageSms messageSmsToEncode = new BluetoothMapbMessageSms();
+        messageSmsToEncode.setType(BluetoothMapUtils.TYPE.SMS_CDMA);
+        messageSmsToEncode.setFolder("placeholder");
+        messageSmsToEncode.setStatus(true);
+        messageSmsToEncode.setSmsBodyPdus(pdus);
+
+        byte[] encodedMessageSms = messageSmsToEncode.encode();
+        InputStream inputStream = new ByteArrayInputStream(encodedMessageSms);
+        BluetoothMapbMessage messageParsed = BluetoothMapbMessage.parse(inputStream,
+                BluetoothMapAppParams.CHARSET_NATIVE);
+
+        assertThat(messageParsed).isInstanceOf(BluetoothMapbMessageSms.class);
+    }
+
+    @Test
+    public void getDeliverPdus_withTypeGSM() throws Exception {
+        when(mTelephonyManager.getCurrentPhoneType()).thenReturn(TelephonyManager.PHONE_TYPE_GSM);
+
+        ArrayList<SmsPdu> pdus = BluetoothMapSmsPdu.getDeliverPdus(mTargetContext, TEST_TEXT,
+                TEST_DESTINATION_ADDRESS, TEST_DATE);
+
+        assertThat(pdus.size()).isEqualTo(1);
+        assertThat(pdus.get(0).getType()).isEqualTo(BluetoothMapSmsPdu.SMS_TYPE_GSM);
+
+        BluetoothMapbMessageSms messageSmsToEncode = new BluetoothMapbMessageSms();
+        messageSmsToEncode.setType(BluetoothMapUtils.TYPE.SMS_GSM);
+        messageSmsToEncode.setFolder("placeholder");
+        messageSmsToEncode.setStatus(true);
+        messageSmsToEncode.setSmsBodyPdus(pdus);
+
+        byte[] encodedMessageSms = messageSmsToEncode.encode();
+        InputStream inputStream = new ByteArrayInputStream(encodedMessageSms);
+
+        assertThrows(IllegalArgumentException.class, () -> BluetoothMapbMessage.parse(inputStream,
+                BluetoothMapAppParams.CHARSET_NATIVE));
+    }
+
+    @Test
+    public void getDeliverPdus_withTypeCDMA() throws Exception {
+        when(mTelephonyManager.getCurrentPhoneType()).thenReturn(TelephonyManager.PHONE_TYPE_CDMA);
+
+        ArrayList<SmsPdu> pdus = BluetoothMapSmsPdu.getDeliverPdus(mTargetContext, TEST_TEXT,
+                TEST_DESTINATION_ADDRESS, TEST_DATE);
+
+        assertThat(pdus.size()).isEqualTo(1);
+        assertThat(pdus.get(0).getType()).isEqualTo(BluetoothMapSmsPdu.SMS_TYPE_CDMA);
+
+        BluetoothMapbMessageSms messageSmsToEncode = new BluetoothMapbMessageSms();
+        messageSmsToEncode.setType(BluetoothMapUtils.TYPE.SMS_CDMA);
+        messageSmsToEncode.setFolder("placeholder");
+        messageSmsToEncode.setStatus(true);
+        messageSmsToEncode.setSmsBodyPdus(pdus);
+
+        byte[] encodedMessageSms = messageSmsToEncode.encode();
+        InputStream inputStream = new ByteArrayInputStream(encodedMessageSms);
+
+        assertThrows(IllegalArgumentException.class, () -> BluetoothMapbMessage.parse(inputStream,
+                BluetoothMapAppParams.CHARSET_NATIVE));
+    }
+
+    @Test
+    public void getEncodingString() {
+        SmsPdu smsPduGsm7bitWithLanguageTableZero = new SmsPdu(TEST_DATA, SmsMessage.ENCODING_7BIT,
+                BluetoothMapSmsPdu.SMS_TYPE_GSM, 0);
+        assertThat(smsPduGsm7bitWithLanguageTableZero.getEncodingString()).isEqualTo("G-7BIT");
+
+        SmsPdu smsPduGsm7bitWithLanguageTableOne = new SmsPdu(TEST_DATA, SmsMessage.ENCODING_7BIT,
+                BluetoothMapSmsPdu.SMS_TYPE_GSM, 1);
+        assertThat(smsPduGsm7bitWithLanguageTableOne.getEncodingString()).isEqualTo("G-7BITEXT");
+
+        SmsPdu smsPduGsm8bit = new SmsPdu(TEST_DATA, SmsMessage.ENCODING_8BIT,
+                BluetoothMapSmsPdu.SMS_TYPE_GSM, 0);
+        assertThat(smsPduGsm8bit.getEncodingString()).isEqualTo("G-8BIT");
+
+        SmsPdu smsPduGsm16bit = new SmsPdu(TEST_DATA, SmsMessage.ENCODING_16BIT,
+                BluetoothMapSmsPdu.SMS_TYPE_GSM, 0);
+        assertThat(smsPduGsm16bit.getEncodingString()).isEqualTo("G-16BIT");
+
+        SmsPdu smsPduGsmUnknown = new SmsPdu(TEST_DATA, SmsMessage.ENCODING_UNKNOWN,
+                BluetoothMapSmsPdu.SMS_TYPE_GSM, 0);
+        assertThat(smsPduGsmUnknown.getEncodingString()).isEqualTo("");
+
+        SmsPdu smsPduCdma7bit = new SmsPdu(TEST_DATA, SmsMessage.ENCODING_7BIT,
+                BluetoothMapSmsPdu.SMS_TYPE_CDMA, 0);
+        assertThat(smsPduCdma7bit.getEncodingString()).isEqualTo("C-7ASCII");
+
+        SmsPdu smsPduCdma8bit = new SmsPdu(TEST_DATA, SmsMessage.ENCODING_8BIT,
+                BluetoothMapSmsPdu.SMS_TYPE_CDMA, 0);
+        assertThat(smsPduCdma8bit.getEncodingString()).isEqualTo("C-8BIT");
+
+        SmsPdu smsPduCdma16bit = new SmsPdu(TEST_DATA, SmsMessage.ENCODING_16BIT,
+                BluetoothMapSmsPdu.SMS_TYPE_CDMA, 0);
+        assertThat(smsPduCdma16bit.getEncodingString()).isEqualTo("C-UNICODE");
+
+        SmsPdu smsPduCdmaKsc5601 = new SmsPdu(TEST_DATA, SmsMessage.ENCODING_KSC5601,
+                BluetoothMapSmsPdu.SMS_TYPE_CDMA, 0);
+        assertThat(smsPduCdmaKsc5601.getEncodingString()).isEqualTo("C-KOREAN");
+
+        SmsPdu smsPduCdmaUnknown = new SmsPdu(TEST_DATA, SmsMessage.ENCODING_UNKNOWN,
+                BluetoothMapSmsPdu.SMS_TYPE_CDMA, 0);
+        assertThat(smsPduCdmaUnknown.getEncodingString()).isEqualTo("");
+    }
+}
\ No newline at end of file
diff --git a/android/app/tests/unit/src/com/android/bluetooth/map/BluetoothMapbMessageEmailTest.java b/android/app/tests/unit/src/com/android/bluetooth/map/BluetoothMapbMessageEmailTest.java
new file mode 100644
index 0000000..e2a5cb4
--- /dev/null
+++ b/android/app/tests/unit/src/com/android/bluetooth/map/BluetoothMapbMessageEmailTest.java
@@ -0,0 +1,78 @@
+/*
+ * 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.map;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.bluetooth.map.BluetoothMapUtils.TYPE;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class BluetoothMapbMessageEmailTest {
+    public static final String TEST_EMAIL_BODY = "test_email_body";
+
+    @Test
+    public void setAndGetEmailBody() {
+        BluetoothMapbMessageEmail messageEmail = new BluetoothMapbMessageEmail();
+        messageEmail.setEmailBody(TEST_EMAIL_BODY);
+        assertThat(messageEmail.getEmailBody()).isEqualTo(TEST_EMAIL_BODY);
+    }
+
+    @Test
+    public void encodeToByteArray_thenCreateByParsing() throws Exception {
+        BluetoothMapbMessageEmail messageEmailToEncode = new BluetoothMapbMessageEmail();
+        messageEmailToEncode.setType(TYPE.EMAIL);
+        messageEmailToEncode.setFolder("placeholder");
+        messageEmailToEncode.setStatus(true);
+        messageEmailToEncode.setEmailBody(TEST_EMAIL_BODY);
+
+        byte[] encodedMessageEmail = messageEmailToEncode.encode();
+        InputStream inputStream = new ByteArrayInputStream(encodedMessageEmail);
+
+        BluetoothMapbMessage messageParsed = BluetoothMapbMessage.parse(inputStream,
+                BluetoothMapAppParams.CHARSET_UTF8);
+        assertThat(messageParsed).isInstanceOf(BluetoothMapbMessageEmail.class);
+        BluetoothMapbMessageEmail messageEmailParsed = (BluetoothMapbMessageEmail) messageParsed;
+        assertThat(messageEmailParsed.getEmailBody()).isEqualTo(TEST_EMAIL_BODY);
+    }
+
+    @Test
+    public void encodeToByteArray_withEmptyBody_thenCreateByParsing() throws Exception {
+        BluetoothMapbMessageEmail messageEmailToEncode = new BluetoothMapbMessageEmail();
+        messageEmailToEncode.setType(TYPE.EMAIL);
+        messageEmailToEncode.setFolder("placeholder");
+        messageEmailToEncode.setStatus(true);
+
+        byte[] encodedMessageEmail = messageEmailToEncode.encode();
+        InputStream inputStream = new ByteArrayInputStream(encodedMessageEmail);
+
+        BluetoothMapbMessage messageParsed = BluetoothMapbMessage.parse(inputStream,
+                BluetoothMapAppParams.CHARSET_UTF8);
+        assertThat(messageParsed).isInstanceOf(BluetoothMapbMessageEmail.class);
+        BluetoothMapbMessageEmail messageEmailParsed = (BluetoothMapbMessageEmail) messageParsed;
+        assertThat(messageEmailParsed.getEmailBody()).isEqualTo("");
+    }
+}
\ No newline at end of file
diff --git a/android/app/tests/unit/src/com/android/bluetooth/map/BluetoothMapbMessageSmsTest.java b/android/app/tests/unit/src/com/android/bluetooth/map/BluetoothMapbMessageSmsTest.java
new file mode 100644
index 0000000..40607b2
--- /dev/null
+++ b/android/app/tests/unit/src/com/android/bluetooth/map/BluetoothMapbMessageSmsTest.java
@@ -0,0 +1,105 @@
+/*
+ * 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.map;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Context;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.bluetooth.map.BluetoothMapSmsPdu.SmsPdu;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+import java.util.ArrayList;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class BluetoothMapbMessageSmsTest {
+    private static final String TEST_SMS_BODY = "test_sms_body";
+    private static final String TEST_MESSAGE = "test";
+    private static final String TEST_ADDRESS = "12";
+
+    private Context mTargetContext;
+    private ArrayList<SmsPdu> TEST_SMS_BODY_PDUS;
+
+    @Before
+    public void setUp() throws Exception {
+        mTargetContext = InstrumentationRegistry.getTargetContext();
+        TEST_SMS_BODY_PDUS = BluetoothMapSmsPdu.getSubmitPdus(mTargetContext, TEST_MESSAGE,
+                TEST_ADDRESS);
+    }
+
+    @Test
+    public void settersAndGetters() {
+        BluetoothMapbMessageSms messageSms = new BluetoothMapbMessageSms();
+        messageSms.setSmsBody(TEST_SMS_BODY);
+        messageSms.setSmsBodyPdus(TEST_SMS_BODY_PDUS);
+
+        assertThat(messageSms.getSmsBody()).isEqualTo(TEST_SMS_BODY);
+        assertThat(messageSms.mEncoding).isEqualTo(TEST_SMS_BODY_PDUS.get(0).getEncodingString());
+    }
+
+    @Test
+    public void parseMsgInit() {
+        BluetoothMapbMessageSms messageSms = new BluetoothMapbMessageSms();
+        messageSms.parseMsgInit();
+        assertThat(messageSms.getSmsBody()).isEqualTo("");
+    }
+
+    @Test
+    public void encodeToByteArray_thenAddByParsing() throws Exception {
+        BluetoothMapbMessageSms messageSmsToEncode = new BluetoothMapbMessageSms();
+        messageSmsToEncode.setType(BluetoothMapUtils.TYPE.SMS_GSM);
+        messageSmsToEncode.setFolder("placeholder");
+        messageSmsToEncode.setStatus(true);
+        messageSmsToEncode.setSmsBodyPdus(TEST_SMS_BODY_PDUS);
+
+        byte[] encodedMessageSms = messageSmsToEncode.encode();
+        InputStream inputStream = new ByteArrayInputStream(encodedMessageSms);
+
+        BluetoothMapbMessage messageParsed = BluetoothMapbMessage.parse(inputStream,
+                BluetoothMapAppParams.CHARSET_NATIVE);
+        assertThat(messageParsed).isInstanceOf(BluetoothMapbMessageSms.class);
+        BluetoothMapbMessageSms messageSmsParsed = (BluetoothMapbMessageSms) messageParsed;
+        assertThat(messageSmsParsed.getSmsBody()).isEqualTo(TEST_MESSAGE);
+    }
+
+    @Test
+    public void encodeToByteArray_withEmptyMessage_thenAddByParsing() throws Exception {
+        BluetoothMapbMessageSms messageSmsToEncode = new BluetoothMapbMessageSms();
+        messageSmsToEncode.setType(BluetoothMapUtils.TYPE.SMS_GSM);
+        messageSmsToEncode.setFolder("placeholder");
+        messageSmsToEncode.setStatus(true);
+
+        byte[] encodedMessageSms = messageSmsToEncode.encode();
+        InputStream inputStream = new ByteArrayInputStream(encodedMessageSms);
+
+        BluetoothMapbMessage messageParsed = BluetoothMapbMessage.parse(inputStream,
+                BluetoothMapAppParams.CHARSET_UTF8);
+        assertThat(messageParsed).isInstanceOf(BluetoothMapbMessageSms.class);
+        BluetoothMapbMessageSms messageSmsParsed = (BluetoothMapbMessageSms) messageParsed;
+        assertThat(messageSmsParsed.getSmsBody()).isEqualTo("");
+    }
+}
\ No newline at end of file
diff --git a/android/app/tests/unit/src/com/android/bluetooth/opp/BluetoothOppBatchTest.java b/android/app/tests/unit/src/com/android/bluetooth/opp/BluetoothOppBatchTest.java
index 93d6e82..a37d049 100644
--- a/android/app/tests/unit/src/com/android/bluetooth/opp/BluetoothOppBatchTest.java
+++ b/android/app/tests/unit/src/com/android/bluetooth/opp/BluetoothOppBatchTest.java
@@ -17,6 +17,9 @@
 package com.android.bluetooth.opp;
 
 import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.spy;
 
 import android.content.Context;
 
@@ -24,9 +27,12 @@
 import androidx.test.platform.app.InstrumentationRegistry;
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.bluetooth.BluetoothMethodProxy;
+
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.Mockito;
 
 @MediumTest
 @RunWith(AndroidJUnit4.class)
@@ -85,37 +91,18 @@
     }
 
     @Test
-    public void cancelBatch_throwUnknownUri() {
-        // Array can be access and edit by the inner class
-        final boolean[] batchCancelCalled = {false};
-        mBluetoothOppBatch.registerListener(new BluetoothOppBatch.BluetoothOppBatchListener() {
-            @Override
-            public void onShareAdded(int id) {
-            }
+    public void cancelBatch_cancelSuccessfully() {
 
-            @Override
-            public void onShareDeleted(int id) {
-            }
-
-            @Override
-            public void onBatchCanceled() {
-                batchCancelCalled[0] = true;
-            }
-        });
+        BluetoothMethodProxy proxy = spy(BluetoothMethodProxy.getInstance());
+        BluetoothMethodProxy.setInstanceForTesting(proxy);
+        doReturn(0).when(proxy).contentResolverDelete(any(), any(), any(), any());
+        doReturn(0).when(proxy).contentResolverUpdate(any(), any(), any(), any(), any());
 
         assertThat(mBluetoothOppBatch.getPendingShare()).isEqualTo(mInitShareInfo);
-        try {
-            mBluetoothOppBatch.cancelBatch();
-            assertThat(mBluetoothOppBatch.isEmpty()).isTrue();
-            assertThat(batchCancelCalled[0]).isTrue();
-        } catch (IllegalArgumentException e) {
-            // the id for BluetoothOppShareInfo id is made up, so the link is invalid,
-            // leading to IllegalArgumentException. In this case, cancelBatch() failed
-            assertThat(e).hasMessageThat().isEqualTo(
-                    "Unknown URI content://com.android.bluetooth.opp/btopp/0");
-            assertThat(mBluetoothOppBatch.isEmpty()).isFalse();
-            assertThat(batchCancelCalled[0]).isFalse();
-        }
 
+        mBluetoothOppBatch.cancelBatch();
+        assertThat(mBluetoothOppBatch.isEmpty()).isTrue();
+
+        BluetoothMethodProxy.setInstanceForTesting(null);
     }
 }
diff --git a/android/app/tests/unit/src/com/android/bluetooth/opp/BluetoothOppServiceTest.java b/android/app/tests/unit/src/com/android/bluetooth/opp/BluetoothOppServiceTest.java
index e1f67aa..d011a6d 100644
--- a/android/app/tests/unit/src/com/android/bluetooth/opp/BluetoothOppServiceTest.java
+++ b/android/app/tests/unit/src/com/android/bluetooth/opp/BluetoothOppServiceTest.java
@@ -45,9 +45,11 @@
     private BluetoothOppService mService = null;
     private BluetoothAdapter mAdapter = null;
 
-    @Rule public final ServiceTestRule mServiceRule = new ServiceTestRule();
+    @Rule
+    public final ServiceTestRule mServiceRule = new ServiceTestRule();
 
-    @Mock private AdapterService mAdapterService;
+    @Mock
+    private AdapterService mAdapterService;
 
     @Before
     public void setUp() throws Exception {
diff --git a/android/app/tests/unit/src/com/android/bluetooth/opp/BluetoothOppTestUtils.java b/android/app/tests/unit/src/com/android/bluetooth/opp/BluetoothOppTestUtils.java
new file mode 100644
index 0000000..915ff0e
--- /dev/null
+++ b/android/app/tests/unit/src/com/android/bluetooth/opp/BluetoothOppTestUtils.java
@@ -0,0 +1,110 @@
+/*
+ * 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.opp;
+
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doReturn;
+
+import android.database.Cursor;
+
+import org.mockito.internal.util.MockUtil;
+
+import java.util.List;
+import java.util.Objects;
+
+public class BluetoothOppTestUtils {
+
+    /**
+     * A class containing the data to be return by a cursor. Intended to be use with setUpMockCursor
+     *
+     * @attr columnName is name of column to be used as a parameter in cursor.getColumnIndexOrThrow
+     * @attr mIndex should be returned from cursor.getColumnIndexOrThrow
+     * @attr mValue should be returned from cursor.getInt() or cursor.getString() or
+     * cursor.getLong()
+     */
+    public static class CursorMockData {
+        public final String mColumnName;
+        public final int mColumnIndex;
+        public final Object mValue;
+
+        public CursorMockData(String columnName, int index, Object value) {
+            mColumnName = columnName;
+            mColumnIndex = index;
+            mValue = value;
+        }
+    }
+
+    /**
+     * Set up a mock single-row Cursor that work for common use cases in the OPP package.
+     * It mocks the database column index and value of the cell in that column of the current row
+     *
+     * <pre>
+     *  cursorMockDataList.add(
+     *     new CursorMockData(BluetoothShare.DIRECTION, 2, BluetoothShare.DIRECTION_INBOUND
+     *     );
+     *     ...
+     *  setUpMockCursor(cursor, cursorMockDataList);
+     *  // This will return 2
+     *  int index = cursor.getColumnIndexOrThrow(BluetoothShare.DIRECTION);
+     *  int direction = cursor.getInt(index); // This will return BluetoothShare.DIRECTION_INBOUND
+     * </pre>
+     *
+     * @param cursor a mock/spy cursor to be setup
+     * @param cursorMockDataList a list representing what cursor will return
+     */
+    public static void setUpMockCursor(
+            Cursor cursor, List<CursorMockData> cursorMockDataList) {
+        assert(MockUtil.isMock(cursor));
+
+        doAnswer(invocation -> {
+            String name = invocation.getArgument(0);
+            return cursorMockDataList.stream().filter(
+                    mockCursorData -> Objects.equals(mockCursorData.mColumnName, name)
+            ).findFirst().orElse(new CursorMockData("", -1, null)).mColumnIndex;
+        }).when(cursor).getColumnIndexOrThrow(anyString());
+
+        doAnswer(invocation -> {
+            int index = invocation.getArgument(0);
+            return cursorMockDataList.stream().filter(
+                    mockCursorData -> mockCursorData.mColumnIndex == index
+            ).findFirst().orElse(new CursorMockData("", -1, -1)).mValue;
+        }).when(cursor).getInt(anyInt());
+
+        doAnswer(invocation -> {
+            int index = invocation.getArgument(0);
+            return cursorMockDataList.stream().filter(
+                    mockCursorData -> mockCursorData.mColumnIndex == index
+            ).findFirst().orElse(new CursorMockData("", -1, -1)).mValue;
+        }).when(cursor).getLong(anyInt());
+
+        doAnswer(invocation -> {
+            int index = invocation.getArgument(0);
+            return cursorMockDataList.stream().filter(
+                    mockCursorData -> mockCursorData.mColumnIndex == index
+            ).findFirst().orElse(new CursorMockData("", -1, null)).mValue;
+        }).when(cursor).getString(anyInt());
+
+        doReturn(true).when(cursor).moveToFirst();
+        doReturn(true).when(cursor).moveToLast();
+        doReturn(true).when(cursor).moveToNext();
+        doReturn(true).when(cursor).moveToPrevious();
+        doReturn(true).when(cursor).moveToPosition(anyInt());
+    }
+}
+
diff --git a/android/app/tests/unit/src/com/android/bluetooth/opp/BluetoothOppTransferActivityTest.java b/android/app/tests/unit/src/com/android/bluetooth/opp/BluetoothOppTransferActivityTest.java
new file mode 100644
index 0000000..23ae99d
--- /dev/null
+++ b/android/app/tests/unit/src/com/android/bluetooth/opp/BluetoothOppTransferActivityTest.java
@@ -0,0 +1,250 @@
+/*
+ * 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.opp;
+
+
+import static android.content.pm.PackageManager.DONT_KILL_APP;
+import static android.service.pm.PackageProto.UserInfoProto.COMPONENT_ENABLED_STATE_DEFAULT;
+import static android.service.pm.PackageProto.UserInfoProto.COMPONENT_ENABLED_STATE_ENABLED;
+
+import static com.android.bluetooth.opp.BluetoothOppTestUtils.CursorMockData;
+import static com.android.bluetooth.opp.BluetoothOppTransferActivity.DIALOG_RECEIVE_COMPLETE_FAIL;
+import static com.android.bluetooth.opp.BluetoothOppTransferActivity.DIALOG_RECEIVE_COMPLETE_SUCCESS;
+import static com.android.bluetooth.opp.BluetoothOppTransferActivity.DIALOG_RECEIVE_ONGOING;
+import static com.android.bluetooth.opp.BluetoothOppTransferActivity.DIALOG_SEND_COMPLETE_FAIL;
+import static com.android.bluetooth.opp.BluetoothOppTransferActivity.DIALOG_SEND_COMPLETE_SUCCESS;
+import static com.android.bluetooth.opp.BluetoothOppTransferActivity.DIALOG_SEND_ONGOING;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.eq;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.database.Cursor;
+import android.net.Uri;
+
+import androidx.test.core.app.ActivityScenario;
+import androidx.test.filters.MediumTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.bluetooth.BluetoothMethodProxy;
+import com.android.bluetooth.pbap.BluetoothPbapActivity;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+import org.mockito.Spy;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@MediumTest
+@RunWith(AndroidJUnit4.class)
+public class BluetoothOppTransferActivityTest {
+  @Mock
+  Cursor mCursor;
+  @Spy
+  BluetoothMethodProxy mBluetoothMethodProxy;
+
+  List<CursorMockData> mCursorMockDataList;
+
+  Intent mIntent;
+  Context mTargetContext;
+
+  @Before
+  public void setUp() {
+    MockitoAnnotations.initMocks(this);
+    mBluetoothMethodProxy = Mockito.spy(BluetoothMethodProxy.getInstance());
+    BluetoothMethodProxy.setInstanceForTesting(mBluetoothMethodProxy);
+
+    Uri dataUrl = Uri.parse("content://com.android.bluetooth.opp.test/random");
+
+    mTargetContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
+
+    mIntent = new Intent();
+    mIntent.setClass(mTargetContext, BluetoothOppTransferActivity.class);
+    mIntent.setData(dataUrl);
+
+    doReturn(mCursor).when(mBluetoothMethodProxy).contentResolverQuery(any(), eq(dataUrl),
+            eq(null), eq(null),
+            eq(null), eq(null));
+
+    doReturn(1).when(mBluetoothMethodProxy).contentResolverUpdate(any(), eq(dataUrl),
+            any(), eq(null), eq(null));
+
+    int idValue = 1234;
+    Long timestampValue = 123456789L;
+    String destinationValue = "AA:BB:CC:00:11:22";
+    String fileTypeValue = "text/plain";
+
+    mCursorMockDataList = new ArrayList<>(List.of(
+            new CursorMockData(BluetoothShare._ID, 0, idValue),
+            new CursorMockData(BluetoothShare.MIMETYPE, 5, fileTypeValue),
+            new CursorMockData(BluetoothShare.TIMESTAMP, 6, timestampValue),
+            new CursorMockData(BluetoothShare.DESTINATION, 7, destinationValue),
+            new CursorMockData(BluetoothShare._DATA, 8, null),
+            new CursorMockData(BluetoothShare.FILENAME_HINT, 9, null),
+            new CursorMockData(BluetoothShare.URI, 10, "content://textfile.txt"),
+            new CursorMockData(BluetoothShare.USER_CONFIRMATION, 11,
+                    BluetoothShare.USER_CONFIRMATION_HANDOVER_CONFIRMED)
+    ));
+
+    enableActivity(true);
+  }
+
+  @After
+  public void tearDown() {
+    BluetoothMethodProxy.setInstanceForTesting(null);
+    enableActivity(false);
+  }
+
+  @Test
+  public void onCreate_showSendOnGoingDialog() {
+    mCursorMockDataList.add(
+            new CursorMockData(BluetoothShare.STATUS, 1, BluetoothShare.STATUS_PENDING));
+    mCursorMockDataList.add(
+            new CursorMockData(BluetoothShare.DIRECTION, 2, BluetoothShare.DIRECTION_OUTBOUND)
+    );
+    mCursorMockDataList.add(new CursorMockData(BluetoothShare.TOTAL_BYTES, 3, 100));
+    mCursorMockDataList.add(new CursorMockData(BluetoothShare.CURRENT_BYTES, 4, 0));
+    BluetoothOppTestUtils.setUpMockCursor(mCursor, mCursorMockDataList);
+
+    ActivityScenario<BluetoothOppTransferActivity> activityScenario = ActivityScenario.launch(
+            mIntent);
+
+    activityScenario.onActivity(activity -> {
+      assertThat(activity.mWhichDialog).isEqualTo(DIALOG_SEND_ONGOING);
+    });
+  }
+
+  @Test
+  public void onCreate_showSendCompleteSuccessDialog() {
+    mCursorMockDataList.add(
+            new CursorMockData(BluetoothShare.STATUS, 1, BluetoothShare.STATUS_SUCCESS)
+    );
+    mCursorMockDataList.add(
+            new CursorMockData(BluetoothShare.DIRECTION, 2, BluetoothShare.DIRECTION_OUTBOUND)
+    );
+    mCursorMockDataList.add(new CursorMockData(BluetoothShare.TOTAL_BYTES, 3, 100));
+    mCursorMockDataList.add(
+            new CursorMockData(BluetoothShare.CURRENT_BYTES, 4, 100));
+    BluetoothOppTestUtils.setUpMockCursor(mCursor, mCursorMockDataList);
+
+    ActivityScenario<BluetoothOppTransferActivity> activityScenario = ActivityScenario.launch(
+            mIntent);
+
+    activityScenario.onActivity(activity -> {
+      assertThat(activity.mWhichDialog).isEqualTo(DIALOG_SEND_COMPLETE_SUCCESS);
+    });
+  }
+
+  @Test
+  public void onCreate_showSendCompleteFailDialog() {
+    mCursorMockDataList.add(
+            new CursorMockData(BluetoothShare.STATUS, 1, BluetoothShare.STATUS_FORBIDDEN));
+    mCursorMockDataList.add(
+            new CursorMockData(BluetoothShare.DIRECTION, 2, BluetoothShare.DIRECTION_OUTBOUND)
+    );
+    mCursorMockDataList.add(new CursorMockData(BluetoothShare.TOTAL_BYTES, 3, 100));
+    mCursorMockDataList.add(new CursorMockData(BluetoothShare.CURRENT_BYTES, 4, 42));
+    BluetoothOppTestUtils.setUpMockCursor(mCursor, mCursorMockDataList);
+
+    ActivityScenario<BluetoothOppTransferActivity> activityScenario = ActivityScenario.launch(
+            mIntent);
+
+    activityScenario.onActivity(
+            activity -> assertThat(activity.mWhichDialog).isEqualTo(DIALOG_SEND_COMPLETE_FAIL));
+  }
+
+  @Test
+  public void onCreate_showReceiveOnGoingDialog() {
+    mCursorMockDataList.add(
+            new CursorMockData(BluetoothShare.STATUS, 1, BluetoothShare.STATUS_PENDING));
+    mCursorMockDataList.add(
+            new CursorMockData(BluetoothShare.DIRECTION, 2, BluetoothShare.DIRECTION_INBOUND)
+    );
+    mCursorMockDataList.add(new CursorMockData(BluetoothShare.TOTAL_BYTES, 3, 100));
+    mCursorMockDataList.add(new CursorMockData(BluetoothShare.CURRENT_BYTES, 4, 0));
+    BluetoothOppTestUtils.setUpMockCursor(mCursor, mCursorMockDataList);
+
+    ActivityScenario<BluetoothOppTransferActivity> activityScenario = ActivityScenario.launch(
+            mIntent);
+
+    activityScenario.onActivity(
+            activity -> assertThat(activity.mWhichDialog).isEqualTo(DIALOG_RECEIVE_ONGOING));
+  }
+
+  @Test
+  public void onCreate_showReceiveCompleteSuccessDialog() {
+    mCursorMockDataList.add(
+            new CursorMockData(BluetoothShare.STATUS, 1, BluetoothShare.STATUS_SUCCESS));
+    mCursorMockDataList.add(
+            new CursorMockData(BluetoothShare.DIRECTION, 2, BluetoothShare.DIRECTION_INBOUND)
+    );
+    mCursorMockDataList.add(new CursorMockData(BluetoothShare.TOTAL_BYTES, 3, 100));
+    mCursorMockDataList.add(
+            new CursorMockData(BluetoothShare.CURRENT_BYTES, 4, 100)
+    );
+
+    BluetoothOppTestUtils.setUpMockCursor(mCursor, mCursorMockDataList);
+    ActivityScenario<BluetoothOppTransferActivity> activityScenario = ActivityScenario.launch(
+            mIntent);
+
+    activityScenario.onActivity(activity -> assertThat(activity.mWhichDialog).isEqualTo(
+            DIALOG_RECEIVE_COMPLETE_SUCCESS));
+  }
+
+  @Test
+  public void onCreate_showReceiveCompleteFailDialog() {
+    mCursorMockDataList.add(
+            new CursorMockData(BluetoothShare.STATUS, 1, BluetoothShare.STATUS_FORBIDDEN));
+    mCursorMockDataList.add(
+            new CursorMockData(BluetoothShare.DIRECTION, 2, BluetoothShare.DIRECTION_INBOUND)
+    );
+    mCursorMockDataList.add(new CursorMockData(BluetoothShare.TOTAL_BYTES, 3, 100));
+    mCursorMockDataList.add(new CursorMockData(BluetoothShare.CURRENT_BYTES, 4, 42));
+
+    BluetoothOppTestUtils.setUpMockCursor(mCursor, mCursorMockDataList);
+    ActivityScenario<BluetoothOppTransferActivity> activityScenario = ActivityScenario.launch(
+            mIntent);
+
+    activityScenario.onActivity(activity -> assertThat(activity.mWhichDialog).isEqualTo(
+            DIALOG_RECEIVE_COMPLETE_FAIL));
+  }
+
+  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,
+            BluetoothOppTransferActivity.class);
+    mTargetContext.getPackageManager().setComponentEnabledSetting(
+            activityName, enabledState, DONT_KILL_APP);
+  }
+}
diff --git a/android/app/tests/unit/src/com/android/bluetooth/opp/BluetoothOppUtilityTest.java b/android/app/tests/unit/src/com/android/bluetooth/opp/BluetoothOppUtilityTest.java
new file mode 100644
index 0000000..9703245
--- /dev/null
+++ b/android/app/tests/unit/src/com/android/bluetooth/opp/BluetoothOppUtilityTest.java
@@ -0,0 +1,270 @@
+/*
+ * 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.opp;
+
+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.anyInt;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.eq;
+
+import android.content.Context;
+import android.database.Cursor;
+import android.net.Uri;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.bluetooth.BluetoothMethodProxy;
+import com.android.bluetooth.opp.BluetoothOppTestUtils.CursorMockData;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.mockito.Spy;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicInteger;
+
+public class BluetoothOppUtilityTest {
+
+    private static final Uri CORRECT_FORMAT_BUT_INVALID_FILE_URI = Uri.parse(
+            "content://com.android.bluetooth.opp/btopp/0123455343467");
+    private static final Uri INCORRECT_FORMAT_URI = Uri.parse("www.google.com");
+
+    Context mContext;
+    @Mock
+    Cursor mCursor;
+
+    @Spy
+    BluetoothMethodProxy mCallProxy = BluetoothMethodProxy.getInstance();
+
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        mContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
+        BluetoothMethodProxy.setInstanceForTesting(mCallProxy);
+    }
+
+    @After
+    public void tearDown() {
+        BluetoothMethodProxy.setInstanceForTesting(null);
+    }
+
+    @Test
+    public void isBluetoothShareUri_correctlyCheckUri() {
+        assertThat(BluetoothOppUtility.isBluetoothShareUri(INCORRECT_FORMAT_URI)).isFalse();
+        assertThat(BluetoothOppUtility.isBluetoothShareUri(CORRECT_FORMAT_BUT_INVALID_FILE_URI))
+                .isTrue();
+    }
+
+    @Test
+    public void queryRecord_withInvalidFileUrl_returnsNull() {
+        doReturn(null).when(mCallProxy).contentResolverQuery(any(),
+                eq(CORRECT_FORMAT_BUT_INVALID_FILE_URI), eq(null), eq(null),
+                eq(null), eq(null));
+        assertThat(BluetoothOppUtility.queryRecord(mContext,
+                CORRECT_FORMAT_BUT_INVALID_FILE_URI)).isNull();
+    }
+
+    @Test
+    public void queryRecord_mockCursor_returnsInstance() {
+        String destinationValue = "AA:BB:CC:00:11:22";
+
+        doReturn(mCursor).when(mCallProxy).contentResolverQuery(any(),
+                eq(CORRECT_FORMAT_BUT_INVALID_FILE_URI), eq(null), eq(null),
+                eq(null), eq(null));
+        doReturn(true).when(mCursor).moveToFirst();
+        doReturn(destinationValue).when(mCursor).getString(anyInt());
+        assertThat(BluetoothOppUtility.queryRecord(mContext,
+                CORRECT_FORMAT_BUT_INVALID_FILE_URI)).isInstanceOf(BluetoothOppTransferInfo.class);
+    }
+
+    @Test
+    public void queryTransfersInBatch_returnsCorrectUrlArrayList() {
+        long timestampValue = 123456;
+        String where = BluetoothShare.TIMESTAMP + " == " + timestampValue;
+        AtomicInteger cnt = new AtomicInteger(1);
+
+        doReturn(mCursor).when(mCallProxy).contentResolverQuery(any(),
+                eq(BluetoothShare.CONTENT_URI), eq(new String[]{
+                        BluetoothShare._DATA
+                }), eq(where), eq(null), eq(BluetoothShare._ID));
+
+
+        doAnswer(invocation -> cnt.incrementAndGet() > 5).when(mCursor).isAfterLast();
+        doReturn(CORRECT_FORMAT_BUT_INVALID_FILE_URI.toString()).when(mCursor)
+                .getString(0);
+
+        ArrayList<String> answer = BluetoothOppUtility.queryTransfersInBatch(mContext,
+                timestampValue);
+        for (String url : answer) {
+            assertThat(url).isEqualTo(CORRECT_FORMAT_BUT_INVALID_FILE_URI.toString());
+        }
+    }
+
+    @Test
+    public void fillRecord_filledAllProperties() {
+        int idValue = 1234;
+        int directionValue = BluetoothShare.DIRECTION_OUTBOUND;
+        long totalBytesValue = 10;
+        long currentBytesValue = 1;
+        int statusValue = BluetoothShare.STATUS_PENDING;
+        Long timestampValue = 123456789L;
+        String destinationValue = "AA:BB:CC:00:11:22";
+        String fileNameValue = "Unknown file";
+        String deviceNameValue = "Unknown device"; // bt device name
+        String fileTypeValue = "text/plain";
+
+        List<CursorMockData> cursorMockDataList = List.of(
+            new CursorMockData(BluetoothShare._ID, 0, idValue),
+            new CursorMockData(BluetoothShare.STATUS, 1, statusValue),
+            new CursorMockData(BluetoothShare.DIRECTION, 2, directionValue),
+            new CursorMockData(BluetoothShare.TOTAL_BYTES, 3, totalBytesValue),
+            new CursorMockData(BluetoothShare.CURRENT_BYTES, 4, currentBytesValue),
+            new CursorMockData(BluetoothShare.TIMESTAMP, 5, timestampValue),
+            new CursorMockData(BluetoothShare.DESTINATION, 6, destinationValue),
+            new CursorMockData(BluetoothShare._DATA, 7, null),
+            new CursorMockData(BluetoothShare.FILENAME_HINT, 8, null),
+            new CursorMockData(BluetoothShare.MIMETYPE, 9, fileTypeValue)
+        );
+
+        BluetoothOppTestUtils.setUpMockCursor(mCursor, cursorMockDataList);
+
+        BluetoothOppTransferInfo info = new BluetoothOppTransferInfo();
+        BluetoothOppUtility.fillRecord(mContext, mCursor, info);
+
+        assertThat(info.mID).isEqualTo(idValue);
+        assertThat(info.mStatus).isEqualTo(statusValue);
+        assertThat(info.mDirection).isEqualTo(directionValue);
+        assertThat(info.mTotalBytes).isEqualTo(totalBytesValue);
+        assertThat(info.mCurrentBytes).isEqualTo(currentBytesValue);
+        assertThat(info.mTimeStamp).isEqualTo(timestampValue);
+        assertThat(info.mDestAddr).isEqualTo(destinationValue);
+        assertThat(info.mFileUri).isEqualTo(null);
+        assertThat(info.mFileType).isEqualTo(fileTypeValue);
+        assertThat(info.mDeviceName).isEqualTo(deviceNameValue);
+        assertThat(info.mHandoverInitiated).isEqualTo(false);
+        assertThat(info.mFileName).isEqualTo(fileNameValue);
+    }
+
+    @Test
+    public void fileExists_returnFalse() {
+        assertThat(
+                BluetoothOppUtility.fileExists(mContext, CORRECT_FORMAT_BUT_INVALID_FILE_URI)
+        ).isFalse();
+    }
+
+    @Test
+    public void isRecognizedFileType_withWrongFileUriAndMimeType_returnFalse() {
+        assertThat(
+                BluetoothOppUtility.isRecognizedFileType(mContext,
+                        CORRECT_FORMAT_BUT_INVALID_FILE_URI,
+                        "aWrongMimeType")
+        ).isFalse();
+    }
+
+    @Test
+    public void formatProgressText() {
+        assertThat(BluetoothOppUtility.formatProgressText(100, 42)).isEqualTo("42%");
+    }
+
+    @Test
+    public void formatResultText() {
+        assertThat(BluetoothOppUtility.formatResultText(1, 2, mContext)).isEqualTo(
+                "1 successful, 2 unsuccessful.");
+    }
+
+    @Test
+    public void getStatusDescription_returnCorrectString() {
+        String deviceName = "randomName";
+        assertThat(BluetoothOppUtility.getStatusDescription(mContext,
+                BluetoothShare.STATUS_PENDING, deviceName)).isEqualTo(
+                "File transfer not started yet.");
+        assertThat(BluetoothOppUtility.getStatusDescription(mContext,
+                BluetoothShare.STATUS_RUNNING, deviceName)).isEqualTo(
+                "File transfer is ongoing.");
+        assertThat(BluetoothOppUtility.getStatusDescription(mContext,
+                BluetoothShare.STATUS_SUCCESS, deviceName)).isEqualTo(
+                "File transfer completed successfully.");
+        assertThat(BluetoothOppUtility.getStatusDescription(mContext,
+                BluetoothShare.STATUS_NOT_ACCEPTABLE, deviceName)).isEqualTo(
+                "Content isn\'t supported.");
+        assertThat(BluetoothOppUtility.getStatusDescription(mContext,
+                BluetoothShare.STATUS_FORBIDDEN, deviceName)).isEqualTo(
+                "Transfer forbidden by target device.");
+        assertThat(BluetoothOppUtility.getStatusDescription(mContext,
+                BluetoothShare.STATUS_CANCELED, deviceName)).isEqualTo(
+                "Transfer canceled by user.");
+        assertThat(BluetoothOppUtility.getStatusDescription(mContext,
+                BluetoothShare.STATUS_FILE_ERROR, deviceName)).isEqualTo("Storage issue.");
+        assertThat(BluetoothOppUtility.getStatusDescription(mContext,
+                BluetoothShare.STATUS_CONNECTION_ERROR, deviceName)).isEqualTo(
+                "Connection unsuccessful.");
+        assertThat(BluetoothOppUtility.getStatusDescription(mContext,
+                BluetoothShare.STATUS_ERROR_NO_SDCARD, deviceName)).isEqualTo(
+                BluetoothOppUtility.deviceHasNoSdCard() ?
+                        "No USB storage." :
+                        "No SD card. Insert an SD card to save transferred files."
+        );
+        assertThat(BluetoothOppUtility.getStatusDescription(mContext,
+                BluetoothShare.STATUS_ERROR_SDCARD_FULL, deviceName)).isEqualTo(
+                BluetoothOppUtility.deviceHasNoSdCard() ?
+                        "There isn\'t enough space in USB storage to save the file." :
+                        "There isn\'t enough space on the SD card to save the file."
+        );
+        assertThat(BluetoothOppUtility.getStatusDescription(mContext,
+                BluetoothShare.STATUS_BAD_REQUEST, deviceName)).isEqualTo(
+                "Request can\'t be handled correctly.");
+        assertThat(BluetoothOppUtility.getStatusDescription(mContext, 12345465,
+                deviceName)).isEqualTo("Unknown error.");
+    }
+
+    @Test
+    public void originalUri_trimBeforeAt() {
+        Uri originalUri = Uri.parse("com.android.bluetooth.opp.BluetoothOppSendFileInfo");
+        Uri uri = Uri.parse("com.android.bluetooth.opp.BluetoothOppSendFileInfo@dfe15a6");
+        assertThat(BluetoothOppUtility.originalUri(uri)).isEqualTo(originalUri);
+    }
+
+    @Test
+    public void fileInfo_testFileInfoFunctions() {
+        assertThat(
+                BluetoothOppUtility.getSendFileInfo(CORRECT_FORMAT_BUT_INVALID_FILE_URI)
+        ).isEqualTo(
+                BluetoothOppSendFileInfo.SEND_FILE_INFO_ERROR
+        );
+        assertThat(BluetoothOppUtility.generateUri(CORRECT_FORMAT_BUT_INVALID_FILE_URI,
+                BluetoothOppSendFileInfo.SEND_FILE_INFO_ERROR).toString()
+        ).contains(
+                CORRECT_FORMAT_BUT_INVALID_FILE_URI.toString());
+        try {
+            BluetoothOppUtility.putSendFileInfo(CORRECT_FORMAT_BUT_INVALID_FILE_URI,
+                    BluetoothOppSendFileInfo.SEND_FILE_INFO_ERROR);
+            BluetoothOppUtility.closeSendFileInfo(CORRECT_FORMAT_BUT_INVALID_FILE_URI);
+        } catch (Exception e) {
+            assertWithMessage("Exception should not happen.").fail();
+        }
+    }
+
+}
diff --git a/android/app/tests/unit/src/com/android/bluetooth/sap/SapServerTest.java b/android/app/tests/unit/src/com/android/bluetooth/sap/SapServerTest.java
new file mode 100644
index 0000000..09ac038
--- /dev/null
+++ b/android/app/tests/unit/src/com/android/bluetooth/sap/SapServerTest.java
@@ -0,0 +1,714 @@
+/*
+ * 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.sap;
+
+import static com.android.bluetooth.sap.SapMessage.CON_STATUS_ERROR_CONNECTION;
+import static com.android.bluetooth.sap.SapMessage.CON_STATUS_OK;
+import static com.android.bluetooth.sap.SapMessage.CON_STATUS_OK_ONGOING_CALL;
+import static com.android.bluetooth.sap.SapMessage.DISC_GRACEFULL;
+import static com.android.bluetooth.sap.SapMessage.ID_CONNECT_REQ;
+import static com.android.bluetooth.sap.SapMessage.ID_CONNECT_RESP;
+import static com.android.bluetooth.sap.SapMessage.ID_DISCONNECT_IND;
+import static com.android.bluetooth.sap.SapMessage.ID_DISCONNECT_RESP;
+import static com.android.bluetooth.sap.SapMessage.ID_ERROR_RESP;
+import static com.android.bluetooth.sap.SapMessage.ID_RIL_UNSOL_DISCONNECT_IND;
+import static com.android.bluetooth.sap.SapMessage.ID_STATUS_IND;
+import static com.android.bluetooth.sap.SapMessage.TEST_MODE_ENABLE;
+import static com.android.bluetooth.sap.SapServer.SAP_MSG_RFC_REPLY;
+import static com.android.bluetooth.sap.SapServer.SAP_MSG_RIL_CONNECT;
+import static com.android.bluetooth.sap.SapServer.SAP_MSG_RIL_IND;
+import static com.android.bluetooth.sap.SapServer.SAP_MSG_RIL_REQ;
+import static com.android.bluetooth.sap.SapServer.SAP_PROXY_DEAD;
+import static com.android.bluetooth.sap.SapServer.SAP_RIL_SOCK_CLOSED;
+
+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.anyInt;
+import static org.mockito.Mockito.anyLong;
+import static org.mockito.Mockito.argThat;
+import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.AlarmManager;
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.content.Intent;
+import android.hardware.radio.V1_0.ISap;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Message;
+import android.os.RemoteException;
+import android.telephony.TelephonyManager;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.ArgumentMatcher;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.mockito.Spy;
+
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.concurrent.atomic.AtomicLong;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class SapServerTest {
+    private static final long TIMEOUT_MS = 1_000;
+
+    private HandlerThread mHandlerThread;
+    private Handler mHandler;
+
+    @Spy
+    private Context mTargetContext =
+            new ContextWrapper(InstrumentationRegistry.getInstrumentation().getTargetContext());
+
+    @Spy
+    private TestHandlerCallback mCallback = new TestHandlerCallback();
+
+    @Mock
+    private InputStream mInputStream;
+
+    @Mock
+    private OutputStream mOutputStream;
+
+    private SapServer mSapServer;
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+
+        mHandlerThread = new HandlerThread("SapServerTest");
+        mHandlerThread.start();
+
+        mHandler = new Handler(mHandlerThread.getLooper(), mCallback);
+        mSapServer = spy(new SapServer(mHandler, mTargetContext, mInputStream, mOutputStream));
+    }
+
+    @After
+    public void tearDown() {
+        mHandlerThread.quit();
+    }
+
+    @Test
+    public void setNotification() {
+        NotificationManager notificationManager = mock(NotificationManager.class);
+        when(mTargetContext.getSystemService(NotificationManager.class))
+                .thenReturn(notificationManager);
+
+        ArgumentCaptor<Notification> captor = ArgumentCaptor.forClass(Notification.class);
+        int type = DISC_GRACEFULL;
+        int flags = PendingIntent.FLAG_CANCEL_CURRENT;
+        mSapServer.setNotification(type, flags);
+
+        verify(notificationManager).notify(eq(SapServer.NOTIFICATION_ID), captor.capture());
+        Notification notification = captor.getValue();
+        assertThat(notification.getChannelId()).isEqualTo(SapServer.SAP_NOTIFICATION_CHANNEL);
+    }
+
+    @Test
+    public void clearNotification() {
+        NotificationManager notificationManager = mock(NotificationManager.class);
+        when(mTargetContext.getSystemService(NotificationManager.class))
+                .thenReturn(notificationManager);
+
+        mSapServer.clearNotification();
+
+        verify(notificationManager).cancel(SapServer.NOTIFICATION_ID);
+    }
+
+    @Test
+    public void setTestMode() {
+        int testMode = TEST_MODE_ENABLE;
+        mSapServer.setTestMode(testMode);
+
+        assertThat(mSapServer.mTestMode).isEqualTo(testMode);
+    }
+
+    @Test
+    public void onConnectRequest_whenStateIsConnecting_callsSendRilMessage() {
+        SapRilReceiver mockReceiver = mock(SapRilReceiver.class);
+        ISap mockSapProxy = mock(ISap.class);
+        Object lock = new Object();
+        when(mockReceiver.getSapProxyLock()).thenReturn(lock);
+        when(mockReceiver.getSapProxy()).thenReturn(mockSapProxy);
+        mSapServer.mRilBtReceiver = mockReceiver;
+        mSapServer.mSapHandler = mHandler;
+
+        mSapServer.changeState(SapServer.SAP_STATE.CONNECTING);
+        SapMessage msg = new SapMessage(ID_STATUS_IND);
+        mSapServer.onConnectRequest(msg);
+
+        verify(mSapServer).sendRilMessage(msg);
+    }
+
+    @Test
+    public void onConnectRequest_whenStateIsConnected_sendsErrorConnectionClientMessage() {
+        mSapServer.mSapHandler = mHandler;
+
+        mSapServer.changeState(SapServer.SAP_STATE.CONNECTED);
+        mSapServer.onConnectRequest(mock(SapMessage.class));
+
+        verify(mSapServer).sendClientMessage(argThat(
+                sapMsg -> sapMsg.getMsgType() == ID_CONNECT_RESP
+                        && sapMsg.getConnectionStatus() == CON_STATUS_ERROR_CONNECTION));
+    }
+
+    @Test
+    public void onConnectRequest_whenStateIsCallOngoing_sendsErrorConnectionClientMessage() {
+        mSapServer.mSapHandler = mHandler;
+
+        mSapServer.changeState(SapServer.SAP_STATE.CONNECTING_CALL_ONGOING);
+        mSapServer.onConnectRequest(mock(SapMessage.class));
+
+        verify(mSapServer, atLeastOnce()).sendClientMessage(argThat(
+                sapMsg -> sapMsg.getMsgType() == ID_CONNECT_RESP
+                        && sapMsg.getConnectionStatus() == CON_STATUS_ERROR_CONNECTION));
+    }
+
+    @Test
+    public void getMessageName() {
+        assertThat(SapServer.getMessageName(SAP_MSG_RFC_REPLY)).isEqualTo("SAP_MSG_REPLY");
+        assertThat(SapServer.getMessageName(SAP_MSG_RIL_CONNECT)).isEqualTo("SAP_MSG_RIL_CONNECT");
+        assertThat(SapServer.getMessageName(SAP_MSG_RIL_REQ)).isEqualTo("SAP_MSG_RIL_REQ");
+        assertThat(SapServer.getMessageName(SAP_MSG_RIL_IND)).isEqualTo("SAP_MSG_RIL_IND");
+        assertThat(SapServer.getMessageName(-1)).isEqualTo("Unknown message ID");
+    }
+
+    @Test
+    public void sendReply() throws Exception {
+        SapMessage msg = mock(SapMessage.class);
+        mSapServer.sendReply(msg);
+
+        verify(msg).write(any(OutputStream.class));
+    }
+
+    @Test
+    public void sendRilMessage_success() throws Exception {
+        SapRilReceiver mockReceiver = mock(SapRilReceiver.class);
+        ISap mockSapProxy = mock(ISap.class);
+        Object lock = new Object();
+        when(mockReceiver.getSapProxyLock()).thenReturn(lock);
+        when(mockReceiver.getSapProxy()).thenReturn(mockSapProxy);
+        mSapServer.mRilBtReceiver = mockReceiver;
+        mSapServer.mSapHandler = mHandler;
+
+        SapMessage msg = mock(SapMessage.class);
+        mSapServer.sendRilMessage(msg);
+
+        verify(msg).send(mockSapProxy);
+    }
+
+    @Test
+    public void sendRilMessage_whenSapProxyIsNull_sendsErrorClientMessage() throws Exception {
+        SapRilReceiver mockReceiver = mock(SapRilReceiver.class);
+        Object lock = new Object();
+        when(mockReceiver.getSapProxyLock()).thenReturn(lock);
+        when(mockReceiver.getSapProxy()).thenReturn(null);
+        mSapServer.mRilBtReceiver = mockReceiver;
+        mSapServer.mSapHandler = mHandler;
+
+        SapMessage msg = mock(SapMessage.class);
+        mSapServer.sendRilMessage(msg);
+
+        verify(mSapServer).sendClientMessage(
+                argThat(sapMsg -> sapMsg.getMsgType() == ID_ERROR_RESP));
+    }
+
+    @Test
+    public void sendRilMessage_whenIAEIsThrown_sendsErrorClientMessage() throws Exception {
+        SapRilReceiver mockReceiver = mock(SapRilReceiver.class);
+        Object lock = new Object();
+        ISap mockSapProxy = mock(ISap.class);
+        when(mockReceiver.getSapProxyLock()).thenReturn(lock);
+        when(mockReceiver.getSapProxy()).thenReturn(mockSapProxy);
+        mSapServer.mRilBtReceiver = mockReceiver;
+        mSapServer.mSapHandler = mHandler;
+
+        SapMessage msg = mock(SapMessage.class);
+        doThrow(new IllegalArgumentException()).when(msg).send(any());
+        mSapServer.sendRilMessage(msg);
+
+        verify(mSapServer).sendClientMessage(
+                argThat(sapMsg -> sapMsg.getMsgType() == ID_ERROR_RESP));
+    }
+
+    @Test
+    public void sendRilMessage_whenRemoteExceptionIsThrown_sendsErrorClientMessage()
+            throws Exception {
+        SapRilReceiver mockReceiver = mock(SapRilReceiver.class);
+        Object lock = new Object();
+        ISap mockSapProxy = mock(ISap.class);
+        when(mockReceiver.getSapProxyLock()).thenReturn(lock);
+        when(mockReceiver.getSapProxy()).thenReturn(mockSapProxy);
+        mSapServer.mRilBtReceiver = mockReceiver;
+        mSapServer.mSapHandler = mHandler;
+
+        SapMessage msg = mock(SapMessage.class);
+        doThrow(new RemoteException()).when(msg).send(any());
+        mSapServer.sendRilMessage(msg);
+
+        verify(mSapServer).sendClientMessage(
+                argThat(sapMsg -> sapMsg.getMsgType() == ID_ERROR_RESP));
+        verify(mockReceiver).notifyShutdown();
+        verify(mockReceiver).resetSapProxy();
+    }
+
+    @Test
+    public void handleRilInd_whenMessageIsNull() {
+        try {
+            mSapServer.handleRilInd(null);
+        } catch (Exception e) {
+            assertWithMessage("Exception should not happen.").fail();
+        }
+    }
+
+    @Test
+    public void handleRilInd_whenStateIsConnected_callsSendClientMessage() {
+        int disconnectionType = DISC_GRACEFULL;
+        SapMessage msg = mock(SapMessage.class);
+        when(msg.getMsgType()).thenReturn(ID_RIL_UNSOL_DISCONNECT_IND);
+        when(msg.getDisconnectionType()).thenReturn(disconnectionType);
+        mSapServer.mSapHandler = mHandler;
+
+        mSapServer.changeState(SapServer.SAP_STATE.CONNECTED);
+        mSapServer.handleRilInd(msg);
+
+        verify(mSapServer).sendClientMessage(argThat(
+                sapMsg -> sapMsg.getMsgType() == ID_DISCONNECT_IND
+                        && sapMsg.getDisconnectionType() == disconnectionType));
+    }
+
+    @Test
+    public void handleRilInd_whenStateIsDisconnected_callsSendDisconnectInd() {
+        int disconnectionType = DISC_GRACEFULL;
+        NotificationManager notificationManager = mock(NotificationManager.class);
+        when(mTargetContext.getSystemService(NotificationManager.class))
+                .thenReturn(notificationManager);
+        SapMessage msg = mock(SapMessage.class);
+        when(msg.getMsgType()).thenReturn(ID_RIL_UNSOL_DISCONNECT_IND);
+        when(msg.getDisconnectionType()).thenReturn(disconnectionType);
+        mSapServer.mSapHandler = mHandler;
+
+        mSapServer.changeState(SapServer.SAP_STATE.DISCONNECTED);
+        mSapServer.handleRilInd(msg);
+
+        verify(mSapServer).sendDisconnectInd(disconnectionType);
+    }
+
+    @Test
+    public void handleRfcommReply_whenMessageIsNull() {
+        try {
+            mSapServer.changeState(SapServer.SAP_STATE.CONNECTED_BUSY);
+            mSapServer.handleRfcommReply(null);
+        } catch (Exception e) {
+            assertWithMessage("Exception should not happen.").fail();
+        }
+    }
+
+    @Test
+    public void handleRfcommReply_connectRespMsg_whenInCallOngoingState() {
+        SapMessage msg = mock(SapMessage.class);
+        when(msg.getMsgType()).thenReturn(ID_CONNECT_RESP);
+
+        mSapServer.changeState(SapServer.SAP_STATE.CONNECTING_CALL_ONGOING);
+        when(msg.getConnectionStatus()).thenReturn(CON_STATUS_OK);
+        mSapServer.handleRfcommReply(msg);
+
+        assertThat(mSapServer.mState).isEqualTo(SapServer.SAP_STATE.CONNECTED);
+    }
+
+    @Test
+    public void handleRfcommReply_connectRespMsg_whenNotInCallOngoingState_okStatus() {
+        SapMessage msg = mock(SapMessage.class);
+        when(msg.getMsgType()).thenReturn(ID_CONNECT_RESP);
+
+        mSapServer.changeState(SapServer.SAP_STATE.CONNECTED);
+        when(msg.getConnectionStatus()).thenReturn(CON_STATUS_OK);
+        mSapServer.handleRfcommReply(msg);
+
+        assertThat(mSapServer.mState).isEqualTo(SapServer.SAP_STATE.CONNECTED);
+    }
+
+    @Test
+    public void handleRfcommReply_connectRespMsg_whenNotInCallOngoingState_ongoingCallStatus() {
+        SapMessage msg = mock(SapMessage.class);
+        when(msg.getMsgType()).thenReturn(ID_CONNECT_RESP);
+
+        mSapServer.changeState(SapServer.SAP_STATE.CONNECTED);
+        when(msg.getConnectionStatus()).thenReturn(CON_STATUS_OK_ONGOING_CALL);
+        mSapServer.handleRfcommReply(msg);
+
+        assertThat(mSapServer.mState).isEqualTo(SapServer.SAP_STATE.CONNECTING_CALL_ONGOING);
+    }
+
+    @Test
+    public void handleRfcommReply_connectRespMsg_whenNotInCallOngoingState_errorStatus() {
+        AlarmManager alarmManager = mock(AlarmManager.class);
+        when(mTargetContext.getSystemService(AlarmManager.class)).thenReturn(alarmManager);
+        SapMessage msg = mock(SapMessage.class);
+        when(msg.getMsgType()).thenReturn(ID_CONNECT_RESP);
+
+        mSapServer.changeState(SapServer.SAP_STATE.CONNECTED);
+        when(msg.getConnectionStatus()).thenReturn(CON_STATUS_ERROR_CONNECTION);
+        mSapServer.handleRfcommReply(msg);
+
+        verify(mSapServer).startDisconnectTimer(anyInt(), anyInt());
+    }
+
+    @Test
+    public void handleRfcommReply_disconnectRespMsg_whenInDisconnectingState() {
+        SapMessage msg = mock(SapMessage.class);
+        when(msg.getMsgType()).thenReturn(ID_DISCONNECT_RESP);
+
+        mSapServer.changeState(SapServer.SAP_STATE.DISCONNECTING);
+        mSapServer.handleRfcommReply(msg);
+
+        assertThat(mSapServer.mState).isEqualTo(SapServer.SAP_STATE.DISCONNECTED);
+    }
+
+    @Test
+    public void handleRfcommReply_disconnectRespMsg_whenInConnectedState_shutDown() {
+        SapMessage msg = mock(SapMessage.class);
+        when(msg.getMsgType()).thenReturn(ID_DISCONNECT_RESP);
+
+        mSapServer.mIsLocalInitDisconnect = true;
+        mSapServer.changeState(SapServer.SAP_STATE.CONNECTED);
+        mSapServer.handleRfcommReply(msg);
+
+        verify(mSapServer).shutdown();
+    }
+
+    @Test
+    public void handleRfcommReply_disconnectRespMsg_whenInConnectedState_startsDisconnectTimer() {
+        AlarmManager alarmManager = mock(AlarmManager.class);
+        when(mTargetContext.getSystemService(AlarmManager.class)).thenReturn(alarmManager);
+        SapMessage msg = mock(SapMessage.class);
+        when(msg.getMsgType()).thenReturn(ID_DISCONNECT_RESP);
+
+        mSapServer.mIsLocalInitDisconnect = false;
+        mSapServer.changeState(SapServer.SAP_STATE.CONNECTED);
+        mSapServer.handleRfcommReply(msg);
+
+        verify(mSapServer).startDisconnectTimer(anyInt(), anyInt());
+    }
+
+    @Test
+    public void handleRfcommReply_statusIndMsg_whenInDisonnectingState_doesNotSendMessage()
+            throws Exception {
+        SapMessage msg = mock(SapMessage.class);
+        when(msg.getMsgType()).thenReturn(ID_STATUS_IND);
+
+        mSapServer.changeState(SapServer.SAP_STATE.DISCONNECTING);
+        mSapServer.handleRfcommReply(msg);
+
+        verify(msg, never()).send(any());
+    }
+
+    @Test
+    public void handleRfcommReply_statusIndMsg_whenInConnectedState_setsNotification() {
+        NotificationManager notificationManager = mock(NotificationManager.class);
+        when(mTargetContext.getSystemService(NotificationManager.class))
+                .thenReturn(notificationManager);
+        SapMessage msg = mock(SapMessage.class);
+        when(msg.getMsgType()).thenReturn(ID_STATUS_IND);
+
+        mSapServer.changeState(SapServer.SAP_STATE.CONNECTED);
+        mSapServer.handleRfcommReply(msg);
+
+        verify(notificationManager).notify(eq(SapServer.NOTIFICATION_ID), any());
+    }
+
+    @Test
+    public void startDisconnectTimer_and_stopDisconnectTimer() {
+        AlarmManager alarmManager = mock(AlarmManager.class);
+        when(mTargetContext.getSystemService(AlarmManager.class)).thenReturn(alarmManager);
+
+        mSapServer.startDisconnectTimer(SapMessage.DISC_FORCED, 1_000);
+        verify(alarmManager).set(anyInt(), anyLong(), any(PendingIntent.class));
+
+        mSapServer.stopDisconnectTimer();
+        verify(alarmManager).cancel(any(PendingIntent.class));
+    }
+
+    @Test
+    public void isCallOngoing() {
+        TelephonyManager telephonyManager = mock(TelephonyManager.class);
+        when(mTargetContext.getSystemService(TelephonyManager.class)).thenReturn(telephonyManager);
+
+        when(telephonyManager.getCallState()).thenReturn(TelephonyManager.CALL_STATE_OFFHOOK);
+        assertThat(mSapServer.isCallOngoing()).isTrue();
+
+        when(telephonyManager.getCallState()).thenReturn(TelephonyManager.CALL_STATE_IDLE);
+        assertThat(mSapServer.isCallOngoing()).isFalse();
+    }
+
+    @Test
+    public void sendRilThreadMessage() {
+        mSapServer.mSapHandler = mHandler;
+
+        SapMessage msg = new SapMessage(ID_STATUS_IND);
+        mSapServer.sendRilThreadMessage(msg);
+
+        verify(mCallback, timeout(TIMEOUT_MS)).receiveMessage(eq(SAP_MSG_RIL_REQ), argThat(
+                new ArgumentMatcher<Object>() {
+                    @Override
+                    public boolean matches(Object arg) {
+                        return msg == arg;
+                    }
+                }
+        ));
+    }
+
+    @Test
+    public void sendClientMessage() {
+        mSapServer.mSapHandler = mHandler;
+
+        SapMessage msg = new SapMessage(ID_STATUS_IND);
+        mSapServer.sendClientMessage(msg);
+
+        verify(mCallback, timeout(TIMEOUT_MS)).receiveMessage(eq(SAP_MSG_RFC_REPLY), argThat(
+                new ArgumentMatcher<Object>() {
+                    @Override
+                    public boolean matches(Object arg) {
+                        return msg == arg;
+                    }
+                }
+        ));
+    }
+
+    // TODO: Find a good way to run() method.
+
+    @Test
+    public void clearPendingRilResponses_whenInConnectedBusyState_setsClearRilQueueAsTrue() {
+        SapMessage msg = mock(SapMessage.class);
+
+        mSapServer.changeState(SapServer.SAP_STATE.CONNECTED_BUSY);
+        mSapServer.clearPendingRilResponses(msg);
+
+        verify(msg).setClearRilQueue(true);
+    }
+
+    @Test
+    public void handleMessage_forRfcReplyMsg_callsHandleRfcommReply() {
+        SapMessage sapMsg = mock(SapMessage.class);
+        when(sapMsg.getMsgType()).thenReturn(ID_CONNECT_RESP);
+        when(sapMsg.getConnectionStatus()).thenReturn(CON_STATUS_OK);
+        mSapServer.changeState(SapServer.SAP_STATE.DISCONNECTED);
+
+        Message message = Message.obtain();
+        message.what = SAP_MSG_RFC_REPLY;
+        message.obj = sapMsg;
+
+        try {
+            mSapServer.handleMessage(message);
+
+            verify(mSapServer).handleRfcommReply(sapMsg);
+        } finally {
+            message.recycle();
+        }
+    }
+
+    @Test
+    public void handleMessage_forRilConnectMsg_callsSendRilMessage() throws Exception {
+        SapRilReceiver mockReceiver = mock(SapRilReceiver.class);
+        Object lock = new Object();
+        when(mockReceiver.getSapProxyLock()).thenReturn(lock);
+        mSapServer.mRilBtReceiver = mockReceiver;
+        mSapServer.mSapHandler = mHandler;
+        mSapServer.setTestMode(TEST_MODE_ENABLE);
+
+        Message message = Message.obtain();
+        message.what = SAP_MSG_RIL_CONNECT;
+
+        try {
+            mSapServer.handleMessage(message);
+
+            verify(mSapServer).sendRilMessage(
+                    argThat(sapMsg -> sapMsg.getMsgType() == ID_CONNECT_REQ));
+        } finally {
+            message.recycle();
+        }
+    }
+
+    @Test
+    public void handleMessage_forRilReqMsg_callsSendRilMessage() throws Exception {
+        SapRilReceiver mockReceiver = mock(SapRilReceiver.class);
+        ISap mockSapProxy = mock(ISap.class);
+        Object lock = new Object();
+        when(mockReceiver.getSapProxyLock()).thenReturn(lock);
+        when(mockReceiver.getSapProxy()).thenReturn(mockSapProxy);
+        mSapServer.mRilBtReceiver = mockReceiver;
+        mSapServer.mSapHandler = mHandler;
+
+        SapMessage sapMsg = mock(SapMessage.class);
+        when(sapMsg.getMsgType()).thenReturn(ID_CONNECT_REQ);
+
+        Message message = Message.obtain();
+        message.what = SAP_MSG_RIL_REQ;
+        message.obj = sapMsg;
+
+        try {
+            mSapServer.handleMessage(message);
+
+            verify(mSapServer).sendRilMessage(sapMsg);
+        } finally {
+            message.recycle();
+        }
+    }
+
+    @Test
+    public void handleMessage_forRilIndMsg_callsHandleRilInd() throws Exception {
+        SapMessage sapMsg = mock(SapMessage.class);
+        when(sapMsg.getMsgType()).thenReturn(ID_RIL_UNSOL_DISCONNECT_IND);
+        when(sapMsg.getDisconnectionType()).thenReturn(DISC_GRACEFULL);
+        mSapServer.changeState(SapServer.SAP_STATE.CONNECTED);
+        mSapServer.mSapHandler = mHandler;
+
+        Message message = Message.obtain();
+        message.what = SAP_MSG_RIL_IND;
+        message.obj = sapMsg;
+
+        try {
+            mSapServer.handleMessage(message);
+
+            verify(mSapServer).handleRilInd(sapMsg);
+        } finally {
+            message.recycle();
+        }
+    }
+
+    @Test
+    public void handleMessage_forRilSocketClosedMsg_startsDisconnectTimer() throws Exception {
+        AlarmManager alarmManager = mock(AlarmManager.class);
+        when(mTargetContext.getSystemService(AlarmManager.class)).thenReturn(alarmManager);
+
+        Message message = Message.obtain();
+        message.what = SAP_RIL_SOCK_CLOSED;
+
+        try {
+            mSapServer.handleMessage(message);
+
+            verify(mSapServer).startDisconnectTimer(anyInt(), anyInt());
+        } finally {
+            message.recycle();
+        }
+    }
+
+    @Test
+    public void handleMessage_forProxyDeadMsg_notifiesShutDown() throws Exception {
+        SapRilReceiver mockReceiver = mock(SapRilReceiver.class);
+        AtomicLong cookie = new AtomicLong(23);
+        when(mockReceiver.getSapProxyCookie()).thenReturn(cookie);
+        mSapServer.mRilBtReceiver = mockReceiver;
+
+        Message message = Message.obtain();
+        message.what = SAP_PROXY_DEAD;
+        message.obj = cookie.get();
+
+        try {
+            mSapServer.handleMessage(message);
+
+            verify(mockReceiver).notifyShutdown();
+            verify(mockReceiver).resetSapProxy();
+        } finally {
+            message.recycle();
+        }
+    }
+
+    @Test
+    public void onReceive_phoneStateChangedAction_whenStateIsCallOngoing_callsOnConnectRequest() {
+        mSapServer.mIntentReceiver = mSapServer.new SapServerBroadcastReceiver();
+        mSapServer.mSapHandler = mHandler;
+        Intent intent = new Intent(TelephonyManager.ACTION_PHONE_STATE_CHANGED);
+        intent.putExtra(TelephonyManager.EXTRA_STATE, TelephonyManager.EXTRA_STATE_IDLE);
+
+        mSapServer.changeState(SapServer.SAP_STATE.CONNECTING_CALL_ONGOING);
+        assertThat(mSapServer.mState).isEqualTo(SapServer.SAP_STATE.CONNECTING_CALL_ONGOING);
+        mSapServer.mIntentReceiver.onReceive(mTargetContext, intent);
+
+        verify(mSapServer).onConnectRequest(
+                argThat(sapMsg -> sapMsg.getMsgType() == ID_CONNECT_REQ));
+    }
+
+    @Test
+    public void onReceive_SapDisconnectedAction_forDiscRfcommType_callsShutDown() {
+        mSapServer.mIntentReceiver = mSapServer.new SapServerBroadcastReceiver();
+
+        int disconnectType = SapMessage.DISC_RFCOMM;
+        Intent intent = new Intent(SapServer.SAP_DISCONNECT_ACTION);
+        intent.putExtra(SapServer.SAP_DISCONNECT_TYPE_EXTRA, disconnectType);
+        mSapServer.mIntentReceiver.onReceive(mTargetContext, intent);
+
+        verify(mSapServer).shutdown();
+    }
+
+    @Test
+    public void onReceive_SapDisconnectedAction_forNonDiscRfcommType_callsSendDisconnectInd() {
+        mSapServer.mIntentReceiver = mSapServer.new SapServerBroadcastReceiver();
+        mSapServer.mSapHandler = mHandler;
+
+        int disconnectType = SapMessage.DISC_GRACEFULL;
+        Intent intent = new Intent(SapServer.SAP_DISCONNECT_ACTION);
+        intent.putExtra(SapServer.SAP_DISCONNECT_TYPE_EXTRA, disconnectType);
+        mSapServer.changeState(SapServer.SAP_STATE.CONNECTED);
+        mSapServer.mIntentReceiver.onReceive(mTargetContext, intent);
+
+        verify(mSapServer).sendDisconnectInd(disconnectType);
+    }
+
+    @Test
+    public void onReceive_unknownAction_doesNothing() {
+        Intent intent = new Intent("random intent action");
+
+        try {
+            mSapServer.mIntentReceiver.onReceive(mTargetContext, intent);
+        } catch (Exception e) {
+            assertWithMessage("Exception should not happen.").fail();
+        }
+    }
+
+    public static class TestHandlerCallback implements Handler.Callback {
+
+        @Override
+        public boolean handleMessage(Message msg) {
+            receiveMessage(msg.what, msg.obj);
+            return true;
+        }
+
+        public void receiveMessage(int what, Object obj) {}
+    }
+}
diff --git a/android/app/tests/unit/src/com/android/bluetooth/tbs/TbsGattTest.java b/android/app/tests/unit/src/com/android/bluetooth/tbs/TbsGattTest.java
index ae817b5..8282b82 100644
--- a/android/app/tests/unit/src/com/android/bluetooth/tbs/TbsGattTest.java
+++ b/android/app/tests/unit/src/com/android/bluetooth/tbs/TbsGattTest.java
@@ -107,6 +107,8 @@
             Looper.prepare();
         }
 
+        getInstrumentation().getUiAutomation().adoptShellPermissionIdentity();
+
         MockitoAnnotations.initMocks(this);
 
         TestUtils.setAdapterService(mAdapterService);
diff --git a/android/app/tests/unit/src/com/android/bluetooth/tbs/TbsGenericTest.java b/android/app/tests/unit/src/com/android/bluetooth/tbs/TbsGenericTest.java
index 8454136..ac0cc79 100644
--- a/android/app/tests/unit/src/com/android/bluetooth/tbs/TbsGenericTest.java
+++ b/android/app/tests/unit/src/com/android/bluetooth/tbs/TbsGenericTest.java
@@ -82,6 +82,8 @@
         mAdapter = BluetoothAdapter.getDefaultAdapter();
         mContext = getInstrumentation().getTargetContext();
 
+        getInstrumentation().getUiAutomation().adoptShellPermissionIdentity();
+
         // Default TbsGatt mock behavior
         doReturn(true).when(mTbsGatt).init(mGtbsCcidCaptor.capture(), mGtbsUciCaptor.capture(),
                 mDefaultGtbsUriSchemesCaptor.capture(), anyBoolean(), anyBoolean(),
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 c187a77..e74b36a 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
@@ -39,6 +39,7 @@
 import android.util.Log;
 
 import androidx.test.core.app.ApplicationProvider;
+import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.MediumTest;
 import androidx.test.rule.ServiceTestRule;
 import androidx.test.runner.AndroidJUnit4;
@@ -120,6 +121,7 @@
     @Before
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
+        InstrumentationRegistry.getInstrumentation().getUiAutomation().adoptShellPermissionIdentity();
 
         // Create the service Intent.
         Intent serviceIntent =
diff --git a/android/app/tests/unit/src/com/android/bluetooth/util/GsmAlphabetTest.java b/android/app/tests/unit/src/com/android/bluetooth/util/GsmAlphabetTest.java
index cb4fd61..8d271e8 100644
--- a/android/app/tests/unit/src/com/android/bluetooth/util/GsmAlphabetTest.java
+++ b/android/app/tests/unit/src/com/android/bluetooth/util/GsmAlphabetTest.java
@@ -18,8 +18,11 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import androidx.test.InstrumentationRegistry;
+
 import com.android.internal.telephony.uicc.IccUtils;
 
+import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.JUnit4;
@@ -29,6 +32,11 @@
 
   private static final String GSM_EXTENDED_CHARS = "{|}\\[~]\f\u20ac";
 
+  @Before
+  public void setUp() throws Exception {
+    InstrumentationRegistry.getInstrumentation().getUiAutomation().adoptShellPermissionIdentity();
+  }
+
   @Test
   public void gsm7BitPackedToString() throws Exception {
     byte[] packed;
diff --git a/android/app/tests/unit/src/com/android/bluetooth/vc/VolumeControlStateMachineTest.java b/android/app/tests/unit/src/com/android/bluetooth/vc/VolumeControlStateMachineTest.java
index b56982f..2082abd 100644
--- a/android/app/tests/unit/src/com/android/bluetooth/vc/VolumeControlStateMachineTest.java
+++ b/android/app/tests/unit/src/com/android/bluetooth/vc/VolumeControlStateMachineTest.java
@@ -68,6 +68,7 @@
     @Before
     public void setUp() throws Exception {
         mTargetContext = InstrumentationRegistry.getTargetContext();
+        InstrumentationRegistry.getInstrumentation().getUiAutomation().adoptShellPermissionIdentity();
         // Set up mocks and test assets
         MockitoAnnotations.initMocks(this);
         TestUtils.setAdapterService(mAdapterService);
diff --git a/android/pandora/avatar_experimental/Android.bp b/android/pandora/avatar_experimental/Android.bp
index 61e7b07..721e764 100644
--- a/android/pandora/avatar_experimental/Android.bp
+++ b/android/pandora/avatar_experimental/Android.bp
@@ -24,7 +24,6 @@
         "avatar/controllers/*.py",
     ],
     libs: [
-        "mobly",
         "pandora_experimental-python",
         "libprotobuf-python",
         "bumble",
diff --git a/android/pandora/mmi2grpc/mmi2grpc/avrcp.py b/android/pandora/mmi2grpc/mmi2grpc/avrcp.py
index 6d1ae39..c63a58a 100644
--- a/android/pandora/mmi2grpc/mmi2grpc/avrcp.py
+++ b/android/pandora/mmi2grpc/mmi2grpc/avrcp.py
@@ -26,6 +26,7 @@
 from pandora_experimental.avrcp_grpc import AVRCP
 from pandora_experimental.host_grpc import Host
 from pandora_experimental.host_pb2 import Connection
+from pandora_experimental.mediaplayer_grpc import MediaPlayer
 
 
 class AVRCPProxy(ProfileProxy):
@@ -44,6 +45,7 @@
         self.host = Host(channel)
         self.a2dp = A2DP(channel)
         self.avrcp = AVRCP(channel)
+        self.mediaplayer = MediaPlayer(channel)
 
     @assert_description
     def TSC_AVDTP_mmi_iut_accept_connect(self, test: str, pts_addr: bytes, **kwargs):
@@ -59,15 +61,13 @@
         the IUT connects to PTS to establish pairing.
 
         """
+        self.connection = self.host.WaitConnection(address=pts_addr).connection
         if ("TG" in test and "TG/VLH" not in test) or "CT/VLH" in test:
-
-            self.connection = self.host.WaitConnection(address=pts_addr).connection
             try:
                 self.source = self.a2dp.WaitSource(connection=self.connection).source
             except RpcError:
                 pass
         else:
-            self.connection = self.host.WaitConnection(address=pts_addr).connection
             try:
                 self.sink = self.a2dp.WaitSink(connection=self.connection).sink
             except RpcError:
@@ -149,8 +149,10 @@
         Take action to disconnect all A2DP and/or AVRCP connections.
 
         """
-        self.a2dp.Close(source=self.source)
-        self.source = None
+        if self.connection is None:
+            self.connection = self.host.GetConnection(address=pts_addr).connection
+        self.host.Disconnect(connection=self.connection)
+
         return "OK"
 
     @assert_description
@@ -221,6 +223,7 @@
         Tester and the L2CAP_ConnectReq from the Lower Tester, the IUT issues an
         L2CAP_ConnectRsp to the Lower Tester.
         """
+
         return "OK"
 
     @assert_description
@@ -362,7 +365,7 @@
         return "OK"
 
     @assert_description
-    def TSC_AVDTP_mmi_iut_initiate_connect(self, pts_addr: bytes, **kwargs):
+    def TSC_AVDTP_mmi_iut_initiate_connect(self, test: str, pts_addr: bytes, **kwargs):
         """
         Create an AVDTP signaling channel.
 
@@ -370,7 +373,9 @@
         connection with PTS.
         """
         self.connection = self.host.Connect(address=pts_addr).connection
-        self.source = self.a2dp.OpenSource(connection=self.connection).source
+        if ("TG" in test and "TG/VLH" not in test) or "CT/VLH" in test:
+            self.source = self.a2dp.OpenSource(connection=self.connection).source
+
         return "OK"
 
     @assert_description
@@ -541,4 +546,201 @@
         continue once the IUT has responded.
         """
 
-        return "OK"
\ No newline at end of file
+        return "OK"
+
+    @assert_description
+    def TSC_AVRCP_mmi_iut_accept_set_browsed_player(self, **kwargs):
+        """
+        Take action to send a valid response to the [Set Browsed Player] command
+        sent by the PTS.
+        """
+
+        return "OK"
+
+    @assert_description
+    def TSC_AVRCP_mmi_iut_accept_get_folder_items_virtual_file_system(self, **kwargs):
+        """
+        Take action to send a valid response to the [Get Folder Items] with the
+        scope <Virtual File System> command sent by the PTS.
+        """
+
+        return "OK"
+
+    @assert_description
+    def TSC_AVRCP_mmi_user_confirm_virtual_file_system(self, **kwargs):
+        """
+        Are the following items found in the current folder?
+    
+        Folder:
+        com.android.pandora
+    
+    
+        Note: Some media elements and folders may not be
+        listed above.
+        """
+
+        return "OK"
+
+    @assert_description
+    def TSC_AVRCP_mmi_iut_accept_change_path_down(self, **kwargs):
+        """
+        Take action to send a valid response to the [Change Path] <Down> command
+        sent by the PTS.
+        """
+
+        return "OK"
+
+    @assert_description
+    def TSC_AVRCP_mmi_iut_accept_change_path_up(self, **kwargs):
+        """
+        Take action to send a valid response to the [Change Path] <Up> command
+        sent by the PTS.
+        """
+
+        return "OK"
+
+    @assert_description
+    def TSC_AVRCP_mmi_user_action_track_playing(self, **kwargs):
+        """
+        Place the IUT into a state where a track is currently playing, then
+        press 'OK' to continue.
+        """
+        self.mediaplayer.Play()
+
+        return "OK"
+
+    @assert_description
+    def TSC_AVRCP_mmi_iut_accept_get_item_attributes(self, **kwargs):
+        """
+        Take action to send a valid response to the [Get Item Attributes]
+        command sent by the PTS.
+        """
+
+        return "OK"
+
+    @assert_description
+    def TSC_AVRCP_mmi_iut_reject_set_addressed_player_invalid_player_id(self, **kwargs):
+        """
+        PTS has sent a Set Addressed Player command with an invalid Player Id.
+        The IUT must respond with the error code: Invalid Player Id (0x11).
+        Description: Verify that the IUT can properly reject a Set Addressed
+        Player command that contains an invalid player id.
+        """
+
+        return "OK"
+
+    @assert_description
+    def TSC_AVRCP_mmi_iut_reject_get_folder_items_out_of_range(self, **kwargs):
+        """
+        PTS has sent a Get Folder Items command with invalid values for Start
+        and End.  The IUT must respond with the error code: Range Out Of Bounds
+        (0x0B).
+    
+        Description: Verify that the IUT can properly reject a Get
+        Folder Items command that contains an invalid start and end index.
+        """
+
+        return "OK"
+
+    @assert_description
+    def TSC_AVRCP_mmi_iut_reject_change_path_down_invalid_uid(self, **kwargs):
+        """
+        PTS has sent a Change Path Down command with an invalid folder UID.  The
+        IUT must respond with the error code: Does Not Exist (0x09).
+        Description: Verify that the IUT can properly reject an Change Path Down
+        command that contains an invalid UID.
+        """
+
+        return "OK"
+
+    @assert_description
+    def TSC_AVRCP_mmi_iut_reject_get_item_attributes_invalid_uid_counter(self, **kwargs):
+        """
+        PTS has sent a Get Item Attributes command with an invalid UID Counter.
+        The IUT must respond with the error code: UID Changed (0x05).
+        Description: Verify that the IUT can properly reject a Get Item
+        Attributes command that contains an invalid UID Counter.
+        """
+
+        return "OK"
+
+    @assert_description
+    def TSC_AVRCP_mmi_iut_accept_play_item(self, **kwargs):
+        """
+        Take action to send a valid response to the [Play Item] command sent by
+        the PTS.
+        """
+
+        return "OK"
+
+    @assert_description
+    def TSC_AVRCP_mmi_iut_reject_play_item_invalid_uid(self, **kwargs):
+        """
+        PTS has sent a Play Item command with an invalid UID.  The IUT must
+        respond with the error code: Does Not Exist (0x09).
+    
+        Description: Verify
+        that the IUT can properly reject a Play Item command that contains an
+        invalid UID.
+        """
+
+        return "OK"
+
+    @assert_description
+    def TSC_AVRCP_mmi_iut_initiate_register_notification_changed_track_changed(self, **kwargs):
+        """
+        Take action to trigger a [Register Notification, Changed] response for
+        <Track Changed> to the PTS from the IUT.  This can be accomplished by
+        changing the currently playing track on the IUT.
+
+        Description: Verify
+        that the Implementation Under Test (IUT) can update database by sending
+        a valid Track Changed Notification to the PTS.
+        """
+
+        self.mediaplayer.Play()
+        self.mediaplayer.Forward()
+
+        return "OK"
+
+    @assert_description
+    def TSC_AVRCP_mmi_user_action_change_track(self, **kwargs):
+        """
+        Take action to change the currently playing track.
+        """
+        self.mediaplayer.Forward()
+
+        return "OK"
+
+    @assert_description
+    def TSC_AVRCP_mmi_iut_reject_set_browsed_player_invalid_player_id(self, **kwargs):
+        """
+        PTS has sent a Set Browsed Player command with an invalid Player Id.
+        The IUT must respond with the error code: Invalid Player Id (0x11).
+        Description: Verify that the IUT can properly reject a Set Browsed
+        Player command that contains an invalid player id.
+        """
+
+        return "OK"
+
+    @assert_description
+    def TSC_AVRCP_mmi_iut_reject_register_notification_notify_invalid_event_id(self, **kwargs):
+        """
+        PTS has sent a Register Notification command with an invalid Event Id.
+        The IUT must respond with the error code: Invalid Parameter (0x01).
+        Description: Verify that the IUT can properly reject a Register
+        Notification command that contains an invalid event Id.
+        """
+
+        return "OK"
+
+    @assert_description
+    def TSC_AVRCP_mmi_user_action_play_large_metadata_media(self, **kwargs):
+        """
+        Start playing a media item with more than 512 bytes worth of metadata,
+        then press 'OK'.
+        """
+
+        self.mediaplayer.SetLargeMetadata()
+
+        return "OK"
diff --git a/android/pandora/mmi2grpc/mmi2grpc/hfp.py b/android/pandora/mmi2grpc/mmi2grpc/hfp.py
index a34e48f..7179f0a 100644
--- a/android/pandora/mmi2grpc/mmi2grpc/hfp.py
+++ b/android/pandora/mmi2grpc/mmi2grpc/hfp.py
@@ -18,17 +18,17 @@
 
 from pandora_experimental.hfp_grpc import HFP
 from pandora_experimental.host_grpc import Host
+from pandora_experimental.security_grpc import Security
 
 import sys
 import threading
+import time
 
 # Standard time to wait before asking for waitConnection
 WAIT_DELAY_BEFORE_CONNECTION = 2
 
 # The tests needs the MMI to accept pairing confirmation request.
-NEEDS_WAIT_CONNECTION_BEFORE_TEST = {
-    'HFP/AG/WBS/BV-01-I',
-}
+NEEDS_WAIT_CONNECTION_BEFORE_TEST = {'HFP/AG/WBS/BV-01-I', 'HFP/AG/SLC/BV-05-I'}
 
 
 class HFPProxy(ProfileProxy):
@@ -37,6 +37,7 @@
         super().__init__(channel)
         self.hfp = HFP(channel)
         self.host = Host(channel)
+        self.security = Security(channel)
 
         self.connection = None
 
@@ -65,7 +66,7 @@
         (IUT), then click Ok.
         """
 
-        self.host.DeletePairing(address=pts_addr)
+        self.security.DeletePairing(address=pts_addr)
         return "OK"
 
     @assert_description
@@ -100,13 +101,29 @@
         return "OK"
 
     @assert_description
+    def TSC_iut_connectable(self, pts_addr: str, test: str, **kwargs):
+        """
+        Make the Implementation Under Test (IUT) connectable, then click Ok.
+        """
+
+        if "HFP/AG/SLC/BV-03-C" in test:
+            self.connection = self.host.WaitConnection(pts_addr).connection
+
+        return "OK"
+
+    @assert_description
     def TSC_iut_disable_slc(self, pts_addr: bytes, **kwargs):
         """
         Click Ok, then disable the service level connection using the
         Implementation Under Test (IUT).
         """
 
-        self.hfp.DisableSlc(connection=self.connection)
+        def go():
+            time.sleep(2)
+            self.hfp.DisableSlc(connection=self.connection)
+
+        threading.Thread(target=go).start()
+
         return "OK"
 
     @assert_description
diff --git a/android/pandora/server/AndroidManifest.xml b/android/pandora/server/AndroidManifest.xml
index a8c5743..089a2a5 100644
--- a/android/pandora/server/AndroidManifest.xml
+++ b/android/pandora/server/AndroidManifest.xml
@@ -20,6 +20,13 @@
 
     <application>
         <uses-library android:name="android.test.runner" />
+
+          <service android:name=".MediaPlayerBrowserService"
+               android:exported="true">
+               <intent-filter>
+                    <action android:name="android.media.browse.MediaBrowserService"/>
+               </intent-filter>
+          </service>
     </application>
 
     <uses-permission android:name="android.permission.INTERNET" />
diff --git a/android/pandora/server/configs/PtsBotTest.xml b/android/pandora/server/configs/PtsBotTest.xml
index 8af3995..600ddc9 100644
--- a/android/pandora/server/configs/PtsBotTest.xml
+++ b/android/pandora/server/configs/PtsBotTest.xml
@@ -37,7 +37,7 @@
         <option name="profile" value="AVRCP" />
         <option name="profile" value="GAP" />
         <option name="profile" value="GATT" />
-        <option name="profile" value="HFP/AG/PSI" />
+        <option name="profile" value="HFP/AG" />
         <option name="profile" value="HID/HOS" />
         <option name="profile" value="HOGP" />
         <option name="profile" value="L2CAP/LE" />
diff --git a/android/pandora/server/configs/PtsBotTestMts.xml b/android/pandora/server/configs/PtsBotTestMts.xml
index 193bd34..df3ad53 100644
--- a/android/pandora/server/configs/PtsBotTestMts.xml
+++ b/android/pandora/server/configs/PtsBotTestMts.xml
@@ -37,7 +37,7 @@
         <option name="profile" value="AVRCP" />
         <option name="profile" value="GAP" />
         <option name="profile" value="GATT" />
-        <option name="profile" value="HFP/AG/PSI" />
+        <option name="profile" value="HFP/AG" />
         <option name="profile" value="HID/HOS" />
         <option name="profile" value="HOGP" />
         <option name="profile" value="L2CAP/LE" />
diff --git a/android/pandora/server/configs/pts_bot_tests_config.json b/android/pandora/server/configs/pts_bot_tests_config.json
index 0b39a04..dcdc90c 100644
--- a/android/pandora/server/configs/pts_bot_tests_config.json
+++ b/android/pandora/server/configs/pts_bot_tests_config.json
@@ -68,6 +68,7 @@
     "AVDTP/SRC/ACP/SIG/SMG/BI-26-C",
     "AVDTP/SRC/ACP/SIG/SMG/BI-33-C",
     "AVDTP/SRC/ACP/SIG/SMG/BV-06-C",
+    "AVDTP/SRC/ACP/SIG/SMG/BV-08-C",
     "AVDTP/SRC/ACP/SIG/SMG/BV-10-C",
     "AVDTP/SRC/ACP/SIG/SMG/BV-12-C",
     "AVDTP/SRC/ACP/SIG/SMG/BV-16-C",
@@ -91,6 +92,7 @@
     "AVDTP/SRC/INT/SIG/SMG/BV-25-C",
     "AVDTP/SRC/INT/SIG/SMG/BV-28-C",
     "AVDTP/SRC/INT/SIG/SMG/BV-31-C",
+    "AVDTP/SRC/INT/SIG/SMG/BI-35-C",
     "AVDTP/SRC/INT/SIG/SYN/BV-05-C",
     "AVDTP/SRC/INT/TRA/BTR/BV-01-C",
     "AVRCP/CT/CEC/BV-02-I",
@@ -105,15 +107,36 @@
     "AVRCP/TG/ICC/BV-02-I",
     "AVRCP/TG/INV/BI-01-C",
     "AVRCP/TG/INV/BI-02-C",
+    "AVRCP/TG/MCN/CB/BI-01-C",
+    "AVRCP/TG/MCN/CB/BI-03-C",
+    "AVRCP/TG/MCN/CB/BI-04-C",
+    "AVRCP/TG/MCN/CB/BI-05-C",
+    "AVRCP/TG/MCN/CB/BV-01-I",
+    "AVRCP/TG/MCN/CB/BV-02-C",
+    "AVRCP/TG/MCN/CB/BV-02-I",
+    "AVRCP/TG/MCN/CB/BV-03-I",
+    "AVRCP/TG/MCN/CB/BV-05-C",
+    "AVRCP/TG/MCN/CB/BV-06-C",
+    "AVRCP/TG/MCN/CB/BV-06-I",
+    "AVRCP/TG/MCN/CB/BV-08-C",
     "AVRCP/TG/MDI/BV-02-C",
     "AVRCP/TG/MDI/BV-04-C",
     "AVRCP/TG/MDI/BV-05-C",
+    "AVRCP/TG/MPS/BI-01-C",
+    "AVRCP/TG/MPS/BI-02-C",
     "AVRCP/TG/MPS/BV-01-I",
     "AVRCP/TG/MPS/BV-02-C",
     "AVRCP/TG/MPS/BV-02-I",
     "AVRCP/TG/MPS/BV-03-I",
+    "AVRCP/TG/MPS/BV-04-C",
+    "AVRCP/TG/MPS/BV-06-C",
+    "AVRCP/TG/MPS/BV-09-C",
+    "AVRCP/TG/NFY/BI-01-C",
+    "AVRCP/TG/NFY/BV-02-C",
     "AVRCP/TG/PTT/BV-01-I",
     "AVRCP/TG/PTT/BV-05-I",
+    "AVRCP/TG/RCR/BV-02-C",
+    "AVRCP/TG/RCR/BV-04-C",
     "GAP/ADV/BV-01-C",
     "GAP/ADV/BV-02-C",
     "GAP/ADV/BV-03-C",
@@ -212,7 +235,13 @@
     "GATT/SR/GAW/BI-12-C",
     "GATT/SR/UNS/BI-01-C",
     "GATT/SR/UNS/BI-02-C",
-    "HFP/AG/PSI/BV-05-I",
+    "HFP/AG/DIS/BV-01-I",
+    "HFP/AG/HFI/BV-02-I",
+    "HFP/AG/PSI/BV-03-C",
+    "HFP/AG/SLC/BV-09-I",
+    "HFP/AG/SLC/BV-10-I",
+    "HFP/AG/TCA/BV-04-I",
+    "HFP/AG/WBS/BV-01-I",
     "HID/HOS/DAT/BV-01-C",
     "HID/HOS/HCE/BV-01-I",
     "HID/HOS/HCE/BV-03-I",
@@ -381,10 +410,8 @@
     "AVDTP/SNK/INT/SIG/SYN/BV-04-C",
     "AVDTP/SRC/ACP/SIG/SMG/BI-11-C",
     "AVDTP/SRC/ACP/SIG/SMG/BI-23-C",
-    "AVDTP/SRC/ACP/SIG/SMG/BV-08-C",
     "AVDTP/SRC/ACP/SIG/SMG/BV-14-C",
     "AVDTP/SRC/ACP/SIG/SMG/ESR05/BV-14-C",
-    "AVDTP/SRC/INT/SIG/SMG/BI-35-C",
     "AVDTP/SRC/INT/SIG/SMG/BV-11-C",
     "AVDTP/SRC/INT/SIG/SMG/BV-13-C",
     "AVDTP/SRC/INT/SIG/SMG/BV-23-C",
@@ -393,20 +420,8 @@
     "AVRCP/CT/CRC/BV-01-I",
     "AVRCP/CT/PTH/BV-01-C",
     "AVRCP/CT/PTT/BV-01-I",
-    "AVRCP/TG/MCN/CB/BI-01-C",
     "AVRCP/TG/MCN/CB/BI-02-C",
-    "AVRCP/TG/MCN/CB/BI-03-C",
-    "AVRCP/TG/MCN/CB/BI-04-C",
-    "AVRCP/TG/MCN/CB/BI-05-C",
-    "AVRCP/TG/MCN/CB/BV-01-I",
-    "AVRCP/TG/MCN/CB/BV-02-C",
-    "AVRCP/TG/MCN/CB/BV-02-I",
-    "AVRCP/TG/MCN/CB/BV-03-I",
     "AVRCP/TG/MCN/CB/BV-04-I",
-    "AVRCP/TG/MCN/CB/BV-05-C",
-    "AVRCP/TG/MCN/CB/BV-06-C",
-    "AVRCP/TG/MCN/CB/BV-06-I",
-    "AVRCP/TG/MCN/CB/BV-08-C",
     "AVRCP/TG/MCN/CB/BV-09-C",
     "AVRCP/TG/MCN/NP/BI-01-C",
     "AVRCP/TG/MCN/NP/BV-01-I",
@@ -417,18 +432,9 @@
     "AVRCP/TG/MCN/NP/BV-06-I",
     "AVRCP/TG/MCN/NP/BV-07-C",
     "AVRCP/TG/MCN/NP/BV-09-C",
-    "AVRCP/TG/MPS/BI-01-C",
-    "AVRCP/TG/MPS/BI-02-C",
-    "AVRCP/TG/MPS/BV-04-C",
-    "AVRCP/TG/MPS/BV-06-C",
-    "AVRCP/TG/MPS/BV-09-C",
-    "AVRCP/TG/NFY/BI-01-C",
-    "AVRCP/TG/NFY/BV-02-C",
     "AVRCP/TG/NFY/BV-04-C",
     "AVRCP/TG/NFY/BV-06-C",
     "AVRCP/TG/NFY/BV-07-C",
-    "AVRCP/TG/RCR/BV-02-C",
-    "AVRCP/TG/RCR/BV-04-C",
     "GAP/BOND/BON/BV-01-C",
     "GAP/BOND/BON/BV-02-C",
     "GAP/BOND/BON/BV-03-C",
@@ -554,13 +560,43 @@
     "GATT/SR/GAW/BV-09-C",
     "GATT/SR/GAW/BV-10-C",
     "GATT/SR/GAW/BV-11-C",
-    "HFP/AG/DIS/BV-01-I",
-    "HFP/AG/HFI/BI-03-I",
-    "HFP/AG/HFI/BV-02-I",
+    "HFP/AG/TRS/BV-01-C",
     "HFP/AG/PSI/BV-01-C",
-    "HFP/AG/PSI/BV-02-C",
     "HFP/AG/PSI/BV-04-I",
-    "HFP/AG/PSI/BV-05-I",
+    "HFP/AG/ACS/BV-04-I",
+    "HFP/AG/ACS/BV-08-I",
+    "HFP/AG/ACS/BV-11-I",
+    "HFP/AG/ACS/BI-14-I",
+    "HFP/AG/ACS/BV-16-I",
+    "HFP/AG/ACR/BV-01-I",
+    "HFP/AG/ACR/BV-02-I",
+    "HFP/AG/CLI/BV-01-I",
+    "HFP/AG/ICA/BV-04-I",
+    "HFP/AG/ICA/BV-07-I",
+    "HFP/AG/ICA/BV-08-I",
+    "HFP/AG/ICA/BV-09-I",
+    "HFP/AG/TCA/BV-01-I",
+    "HFP/AG/TCA/BV-02-I",
+    "HFP/AG/TCA/BV-03-I",
+    "HFP/AG/TCA/BV-05-I",
+    "HFP/AG/ATH/BV-03-I",
+    "HFP/AG/ATH/BV-04-I",
+    "HFP/AG/ATH/BV-06-I",
+    "HFP/AG/ATA/BV-01-I",
+    "HFP/AG/ATA/BV-02-I",
+    "HFP/AG/OCN/BV-01-I",
+    "HFP/AG/OCM/BV-01-I",
+    "HFP/AG/OCM/BV-02-I",
+    "HFP/AG/OCL/BV-01-I",
+    "HFP/AG/OCL/BV-02-I",
+    "HFP/AG/TWC/BV-02-I",
+    "HFP/AG/TWC/BV-03-I",
+    "HFP/AG/TWC/BV-05-I",
+    "HFP/AG/ENO/BV-01-I",
+    "HFP/AG/VRA/BV-01-I",
+    "HFP/AG/TDC/BV-01-I",
+    "HFP/AG/ECS/BV-03-I",
+    "HFP/AG/NUM/BV-01-I",
     "HFP/AG/SLC/BV-01-C",
     "HFP/AG/SLC/BV-02-C",
     "HFP/AG/SLC/BV-03-C",
@@ -568,13 +604,23 @@
     "HFP/AG/SLC/BV-05-I",
     "HFP/AG/SLC/BV-06-I",
     "HFP/AG/SLC/BV-07-I",
-    "HFP/AG/SLC/BV-09-I",
-    "HFP/AG/SLC/BV-10-I",
-    "HFP/AG/TCA/BV-01-I",
-    "HFP/AG/TCA/BV-02-I",
-    "HFP/AG/TCA/BV-03-I",
-    "HFP/AG/TCA/BV-04-I",
-    "HFP/AG/TCA/BV-05-I",
+    "HFP/AG/ACC/BV-08-I",
+    "HFP/AG/ACC/BV-09-I",
+    "HFP/AG/ACC/BV-10-I",
+    "HFP/AG/ACC/BV-11-I",
+    "HFP/AG/ACC/BI-12-I",
+    "HFP/AG/ACC/BI-13-I",
+    "HFP/AG/ACC/BI-14-I",
+    "HFP/AG/ACC/BV-15-I",
+    "HFP/AG/SDP/BV-01-I",
+    "HFP/AG/IIA/BV-01-I",
+    "HFP/AG/IIA/BV-02-I",
+    "HFP/AG/IID/BV-01-I",
+    "HFP/AG/IID/BV-03-I",
+    "HFP/AG/IIC/BV-01-I",
+    "HFP/AG/IIC/BV-02-I",
+    "HFP/AG/IIC/BV-03-I",
+    "HFP/AG/HFI/BI-03-I",
     "HID/HOS/HCR/BV-01-I",
     "L2CAP/LE/CFC/BV-07-C",
     "L2CAP/LE/CFC/BV-11-C",
@@ -1600,7 +1646,9 @@
     "ATT": {},
     "AVCTP": {},
     "AVDTP": {},
-    "AVRCP": {},
+    "AVRCP": {
+      "TSPX_player_feature_bitmask": "0000000000B7010C0A00000000000000"
+    },
     "BNEP": {},
     "DID": {},
     "GAP": {},
diff --git a/android/pandora/server/src/com/android/pandora/A2dp.kt b/android/pandora/server/src/com/android/pandora/A2dp.kt
index b2a082f..72e848f 100644
--- a/android/pandora/server/src/com/android/pandora/A2dp.kt
+++ b/android/pandora/server/src/com/android/pandora/A2dp.kt
@@ -70,28 +70,6 @@
     scope.cancel()
   }
 
-  fun buildAudioTrack(): AudioTrack? {
-    audioTrack =
-      AudioTrack.Builder()
-        .setAudioAttributes(
-          AudioAttributes.Builder()
-            .setUsage(AudioAttributes.USAGE_MEDIA)
-            .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
-            .build()
-        )
-        .setAudioFormat(
-          AudioFormat.Builder()
-            .setEncoding(AudioFormat.ENCODING_PCM_16BIT)
-            .setSampleRate(44100)
-            .setChannelMask(AudioFormat.CHANNEL_OUT_STEREO)
-            .build()
-        )
-        .setTransferMode(AudioTrack.MODE_STREAM)
-        .setBufferSizeInBytes(44100 * 2 * 2)
-        .build()
-    return audioTrack
-  }
-
   override fun openSource(
     request: OpenSourceRequest,
     responseObserver: StreamObserver<OpenSourceResponse>
@@ -332,5 +310,4 @@
         .build()
     }
   }
-
 }
diff --git a/android/pandora/server/src/com/android/pandora/Avrcp.kt b/android/pandora/server/src/com/android/pandora/Avrcp.kt
index bd6d81f..f8cc95b 100644
--- a/android/pandora/server/src/com/android/pandora/Avrcp.kt
+++ b/android/pandora/server/src/com/android/pandora/Avrcp.kt
@@ -18,15 +18,11 @@
 
 import android.bluetooth.BluetoothManager
 import android.content.Context
-
-import pandora.AVRCPGrpc.AVRCPImplBase
-import pandora.AvrcpProto.*
-import com.google.protobuf.Empty
-
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.cancel
-
+import pandora.AVRCPGrpc.AVRCPImplBase
+import pandora.AvrcpProto.*
 
 @kotlinx.coroutines.ExperimentalCoroutinesApi
 class Avrcp(val context: Context) : AVRCPImplBase() {
diff --git a/android/pandora/server/src/com/android/pandora/MediaPlayer.kt b/android/pandora/server/src/com/android/pandora/MediaPlayer.kt
new file mode 100644
index 0000000..7942a3b
--- /dev/null
+++ b/android/pandora/server/src/com/android/pandora/MediaPlayer.kt
@@ -0,0 +1,104 @@
+/*
+ * 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.content.Context
+import android.content.Intent
+import android.media.*
+import com.google.protobuf.Empty
+import io.grpc.stub.StreamObserver
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.cancel
+import pandora.MediaPlayerGrpc.MediaPlayerImplBase
+import pandora.MediaPlayerProto.*
+
+@kotlinx.coroutines.ExperimentalCoroutinesApi
+class MediaPlayer(val context: Context) : MediaPlayerImplBase() {
+  private val TAG = "PandoraMediaPlayer"
+
+  private val scope: CoroutineScope
+
+  init {
+    // Init the CoroutineScope
+    scope = CoroutineScope(Dispatchers.Default)
+    context.startService(Intent(context, MediaPlayerBrowserService::class.java))
+  }
+
+  override fun play(request: Empty, responseObserver: StreamObserver<Empty>) {
+    grpcUnary<Empty>(scope, responseObserver) {
+      MediaPlayerBrowserService.instance.play()
+      Empty.getDefaultInstance()
+    }
+  }
+
+  override fun stop(request: Empty, responseObserver: StreamObserver<Empty>) {
+    grpcUnary<Empty>(scope, responseObserver) {
+      MediaPlayerBrowserService.instance.stop()
+      Empty.getDefaultInstance()
+    }
+  }
+
+  override fun pause(request: Empty, responseObserver: StreamObserver<Empty>) {
+    grpcUnary<Empty>(scope, responseObserver) {
+      MediaPlayerBrowserService.instance.pause()
+      Empty.getDefaultInstance()
+    }
+  }
+
+  override fun rewind(request: Empty, responseObserver: StreamObserver<Empty>) {
+    grpcUnary<Empty>(scope, responseObserver) {
+      MediaPlayerBrowserService.instance.rewind()
+      Empty.getDefaultInstance()
+    }
+  }
+
+  override fun fastForward(request: Empty, responseObserver: StreamObserver<Empty>) {
+    grpcUnary<Empty>(scope, responseObserver) {
+      MediaPlayerBrowserService.instance.fastForward()
+      Empty.getDefaultInstance()
+    }
+  }
+
+  override fun forward(request: Empty, responseObserver: StreamObserver<Empty>) {
+    grpcUnary<Empty>(scope, responseObserver) {
+      MediaPlayerBrowserService.instance.forward()
+      Empty.getDefaultInstance()
+    }
+  }
+
+  override fun backward(request: Empty, responseObserver: StreamObserver<Empty>) {
+    grpcUnary<Empty>(scope, responseObserver) {
+      MediaPlayerBrowserService.instance.backward()
+      Empty.getDefaultInstance()
+    }
+  }
+
+  override fun setLargeMetadata(request: Empty, responseObserver: StreamObserver<Empty>) {
+    grpcUnary<Empty>(scope, responseObserver) {
+      MediaPlayerBrowserService.instance.setLargeMetadata()
+      Empty.getDefaultInstance()
+    }
+  }
+
+  fun deinit() {
+    // Deinit the CoroutineScope
+    scope.cancel()
+    // Stop service
+    context.stopService(Intent(context, MediaPlayerBrowserService::class.java))
+  }
+}
diff --git a/android/pandora/server/src/com/android/pandora/MediaPlayerBrowserService.kt b/android/pandora/server/src/com/android/pandora/MediaPlayerBrowserService.kt
new file mode 100644
index 0000000..8ff3034
--- /dev/null
+++ b/android/pandora/server/src/com/android/pandora/MediaPlayerBrowserService.kt
@@ -0,0 +1,251 @@
+/*
+ * 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.content.Intent
+import android.media.*
+import android.media.browse.MediaBrowser.MediaItem
+import android.media.session.*
+import android.os.Bundle
+import android.service.media.MediaBrowserService
+import android.service.media.MediaBrowserService.BrowserRoot
+import android.util.Log
+
+/* MediaBrowserService to handle MediaButton and Browsing */
+class MediaPlayerBrowserService : MediaBrowserService() {
+  private val TAG = "PandoraMediaPlayerBrowserService"
+
+  private lateinit var mediaSession: MediaSession
+  private lateinit var playbackStateBuilder: PlaybackState.Builder
+  private val alphanumeric = ('A'..'Z') + ('a'..'z') + ('0'..'9')
+  private val mediaIdToChildren = mutableMapOf<String, MutableList<MediaItem>>()
+  private var metadataItems = mutableMapOf<String, MediaMetadata>()
+  private var queue = mutableListOf<MediaSession.QueueItem>()
+  private var currentTrack = -1
+
+  override fun onCreate() {
+    super.onCreate()
+    setupMediaSession()
+    initBrowseFolderList()
+    instance = this
+  }
+
+  fun deinit() {
+    // Releasing MediaSession instance
+    mediaSession.release()
+  }
+
+  private fun setupMediaSession() {
+    mediaSession = MediaSession(this, "MediaSession")
+
+    mediaSession.setFlags(
+      MediaSession.FLAG_HANDLES_MEDIA_BUTTONS or MediaSession.FLAG_HANDLES_TRANSPORT_CONTROLS
+    )
+    mediaSession.setCallback(mSessionCallback)
+    playbackStateBuilder =
+      PlaybackState.Builder()
+        .setState(PlaybackState.STATE_NONE, 0, 1.0f)
+        .setActions(getAvailableActions())
+    mediaSession.setPlaybackState(playbackStateBuilder.build())
+    mediaSession.setMetadata(null)
+    mediaSession.setQueue(queue)
+    mediaSession.setQueueTitle(NOW_PLAYING_PREFIX)
+    mediaSession.isActive = true
+    sessionToken = mediaSession.sessionToken
+  }
+
+  private fun getAvailableActions(): Long =
+    PlaybackState.ACTION_SKIP_TO_PREVIOUS or
+      PlaybackState.ACTION_SKIP_TO_NEXT or
+      PlaybackState.ACTION_FAST_FORWARD or
+      PlaybackState.ACTION_REWIND or
+      PlaybackState.ACTION_PLAY or
+      PlaybackState.ACTION_STOP or
+      PlaybackState.ACTION_PAUSE
+
+  private fun setPlaybackState(state: Int) {
+    playbackStateBuilder.setState(state, 0, 1.0f)
+    mediaSession.setPlaybackState(playbackStateBuilder.build())
+  }
+
+  fun play() {
+    setPlaybackState(PlaybackState.STATE_PLAYING)
+    if (currentTrack == -1) {
+      currentTrack = 1
+      initQueue()
+      mediaSession.setQueue(queue)
+      mediaSession.setMetadata(metadataItems.get("" + currentTrack))
+    }
+  }
+
+  fun stop() {
+    setPlaybackState(PlaybackState.STATE_STOPPED)
+    mediaSession.setMetadata(null)
+  }
+
+  fun pause() {
+    setPlaybackState(PlaybackState.STATE_PAUSED)
+  }
+
+  fun rewind() {
+    setPlaybackState(PlaybackState.STATE_REWINDING)
+  }
+
+  fun fastForward() {
+    setPlaybackState(PlaybackState.STATE_FAST_FORWARDING)
+  }
+
+  fun forward() {
+    if (currentTrack == QUEUE_SIZE || currentTrack == -1) currentTrack = 1 else currentTrack += 1
+    setPlaybackState(PlaybackState.STATE_SKIPPING_TO_NEXT)
+    mediaSession.setMetadata(metadataItems.get("" + currentTrack))
+  }
+
+  fun backward() {
+    if (currentTrack == 1 || currentTrack == -1) currentTrack = QUEUE_SIZE else currentTrack -= 1
+    setPlaybackState(PlaybackState.STATE_SKIPPING_TO_PREVIOUS)
+    mediaSession.setMetadata(metadataItems.get("" + currentTrack))
+  }
+
+  fun setLargeMetadata() {
+    mediaSession.setMetadata(
+      MediaMetadata.Builder()
+        .putString(MediaMetadata.METADATA_KEY_MEDIA_ID, "MEDIA_ID")
+        .putString(MediaMetadata.METADATA_KEY_TITLE, generateAlphanumericString(512))
+        .putString(MediaMetadata.METADATA_KEY_ARTIST, generateAlphanumericString(512))
+        .build()
+    )
+  }
+
+  private fun generateAlphanumericString(length: Int): String {
+    return buildString { repeat(length) { append(alphanumeric.random()) } }
+  }
+
+  private val mSessionCallback: MediaSession.Callback =
+    object : MediaSession.Callback() {
+      override fun onPlay() {
+        Log.i(TAG, "onPlay")
+        play()
+      }
+
+      override fun onPause() {
+        Log.i(TAG, "onPause")
+        pause()
+      }
+
+      override fun onSkipToPrevious() {
+        Log.i(TAG, "onSkipToPrevious")
+        // TODO : Need to handle to play previous audio in the list
+      }
+
+      override fun onSkipToNext() {
+        Log.i(TAG, "onSkipToNext")
+        // TODO : Need to handle to play next audio in the list
+      }
+
+      override fun onMediaButtonEvent(mediaButtonEvent: Intent): Boolean {
+        Log.i(TAG, "MediaSessionCallback——》onMediaButtonEvent $mediaButtonEvent")
+        return super.onMediaButtonEvent(mediaButtonEvent)
+      }
+    }
+
+  override fun onGetRoot(p0: String, clientUid: Int, rootHints: Bundle?): BrowserRoot? {
+    Log.i(TAG, "onGetRoot")
+    return BrowserRoot(ROOT, null)
+  }
+
+  override fun onLoadChildren(parentId: String, result: Result<MutableList<MediaItem>>) {
+    Log.i(TAG, "onLoadChildren")
+    if (parentId == ROOT) {
+      val map = mediaIdToChildren[ROOT]
+      Log.i(TAG, "onloadchildren $map")
+      result.sendResult(map)
+    } else if (parentId == NOW_PLAYING_PREFIX) {
+      result.sendResult(mediaIdToChildren[NOW_PLAYING_PREFIX])
+    } else {
+      Log.i(TAG, "onloadchildren inside else")
+      result.sendResult(null)
+    }
+  }
+
+  private fun initMediaItems() {
+    var mediaItems = mutableListOf<MediaItem>()
+    for (item in 1..QUEUE_SIZE) {
+      val metaData: MediaMetadata =
+        MediaMetadata.Builder()
+          .putString(MediaMetadata.METADATA_KEY_MEDIA_ID, NOW_PLAYING_PREFIX + item)
+          .putString(MediaMetadata.METADATA_KEY_TITLE, "Title$item")
+          .putString(MediaMetadata.METADATA_KEY_ARTIST, "Artist$item")
+          .putLong(MediaMetadata.METADATA_KEY_TRACK_NUMBER, item.toLong())
+          .putLong(MediaMetadata.METADATA_KEY_NUM_TRACKS, QUEUE_SIZE.toLong())
+          .build()
+      val mediaItem = MediaItem(metaData.description, MediaItem.FLAG_PLAYABLE)
+      mediaItems.add(mediaItem)
+      metadataItems.put("" + item, metaData)
+    }
+    mediaIdToChildren[NOW_PLAYING_PREFIX] = mediaItems
+  }
+
+  private fun initQueue() {
+    for ((key, value) in metadataItems.entries) {
+      val mediaItem = MediaItem(value.description, MediaItem.FLAG_PLAYABLE)
+      queue.add(MediaSession.QueueItem(mediaItem.description, key.toLong()))
+    }
+  }
+
+  private fun initBrowseFolderList() {
+    var rootList = mediaIdToChildren[ROOT] ?: mutableListOf()
+
+    val emptyFolderMetaData =
+      MediaMetadata.Builder()
+        .putString(MediaMetadata.METADATA_KEY_MEDIA_ID, EMPTY_FOLDER)
+        .putString(MediaMetadata.METADATA_KEY_TITLE, EMPTY_FOLDER)
+        .putLong(
+          MediaMetadata.METADATA_KEY_BT_FOLDER_TYPE,
+          MediaDescription.BT_FOLDER_TYPE_PLAYLISTS
+        )
+        .build()
+    val emptyFolderMediaItem = MediaItem(emptyFolderMetaData.description, MediaItem.FLAG_BROWSABLE)
+
+    val playlistMetaData =
+      MediaMetadata.Builder()
+        .apply {
+          putString(MediaMetadata.METADATA_KEY_MEDIA_ID, NOW_PLAYING_PREFIX)
+          putString(MediaMetadata.METADATA_KEY_TITLE, NOW_PLAYING_PREFIX)
+          putLong(
+            MediaMetadata.METADATA_KEY_BT_FOLDER_TYPE,
+            MediaDescription.BT_FOLDER_TYPE_PLAYLISTS
+          )
+        }
+        .build()
+
+    val playlistsMediaItem = MediaItem(playlistMetaData.description, MediaItem.FLAG_BROWSABLE)
+
+    rootList += emptyFolderMediaItem
+    rootList += playlistsMediaItem
+    mediaIdToChildren[ROOT] = rootList
+    initMediaItems()
+  }
+
+  companion object {
+    lateinit var instance: MediaPlayerBrowserService
+    const val ROOT = "__ROOT__"
+    const val EMPTY_FOLDER = "@empty@"
+    const val NOW_PLAYING_PREFIX = "NowPlayingId"
+    const val QUEUE_SIZE = 6
+  }
+}
diff --git a/android/pandora/server/src/com/android/pandora/Server.kt b/android/pandora/server/src/com/android/pandora/Server.kt
index df70116..bdcd1ff3 100644
--- a/android/pandora/server/src/com/android/pandora/Server.kt
+++ b/android/pandora/server/src/com/android/pandora/Server.kt
@@ -37,8 +37,9 @@
   private var hfp: Hfp
   private var hid: Hid
   private var l2cap: L2cap
+  private var mediaplayer: MediaPlayer
   private var security: Security
-  private var androidInternal : AndroidInternal
+  private var androidInternal: AndroidInternal
   private var grpcServer: GrpcServer
 
   init {
@@ -48,6 +49,7 @@
     hfp = Hfp(context)
     hid = Hid(context)
     l2cap = L2cap(context)
+    mediaplayer = MediaPlayer(context)
     security = Security(context)
     androidInternal = AndroidInternal()
 
@@ -59,6 +61,7 @@
         .addService(hfp)
         .addService(hid)
         .addService(l2cap)
+        .addService(mediaplayer)
         .addService(security)
         .addService(androidInternal)
 
@@ -92,6 +95,7 @@
     hfp.deinit()
     hid.deinit()
     l2cap.deinit()
+    mediaplayer.deinit()
     security.deinit()
     androidInternal.deinit()
   }
diff --git a/android/pandora/server/src/com/android/pandora/Utils.kt b/android/pandora/server/src/com/android/pandora/Utils.kt
index 091ce60..c3863c7 100644
--- a/android/pandora/server/src/com/android/pandora/Utils.kt
+++ b/android/pandora/server/src/com/android/pandora/Utils.kt
@@ -24,6 +24,7 @@
 import android.content.Context
 import android.content.Intent
 import android.content.IntentFilter
+import android.media.*
 import android.net.MacAddress
 import android.os.ParcelFileDescriptor
 import android.util.Log
@@ -318,3 +319,24 @@
 
 fun BluetoothDevice.toByteString() =
   ByteString.copyFrom(MacAddress.fromString(this.address).toByteArray())!!
+
+/** Creates Audio track instance and returns the reference. */
+fun buildAudioTrack(): AudioTrack? {
+  return AudioTrack.Builder()
+    .setAudioAttributes(
+      AudioAttributes.Builder()
+        .setUsage(AudioAttributes.USAGE_MEDIA)
+        .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
+        .build()
+    )
+    .setAudioFormat(
+      AudioFormat.Builder()
+        .setEncoding(AudioFormat.ENCODING_PCM_16BIT)
+        .setSampleRate(44100)
+        .setChannelMask(AudioFormat.CHANNEL_OUT_STEREO)
+        .build()
+    )
+    .setTransferMode(AudioTrack.MODE_STREAM)
+    .setBufferSizeInBytes(44100 * 2 * 2)
+    .build()
+}
diff --git a/android/pandora/test/Android.bp b/android/pandora/test/Android.bp
index 93d9cbf..4e783f6 100644
--- a/android/pandora/test/Android.bp
+++ b/android/pandora/test/Android.bp
@@ -32,3 +32,14 @@
     },
     data: ["config.yml"],
 }
+
+python_binary_host {
+    name: "avatar_runner",
+    main: "runner.py",
+    srcs: [
+        "runner.py",
+    ],
+    libs: [
+        "libavatar_experimental",
+    ],
+}
diff --git a/android/pandora/test/AndroidTest.xml b/android/pandora/test/AndroidTest.xml
index e198daa..9f6ce9f 100644
--- a/android/pandora/test/AndroidTest.xml
+++ b/android/pandora/test/AndroidTest.xml
@@ -21,6 +21,7 @@
         <option name="install-arg" value="-g" />
     </target_preparer>
     <target_preparer class="com.android.tradefed.targetprep.PythonVirtualenvPreparer">
+        <option name="dep-module" value="mobly" />
         <option name="dep-module" value="grpcio" />
         <option name="dep-module" value="pyee" />
         <option name="dep-module" value="ansicolors" />
diff --git a/android/pandora/test/connect.py b/android/pandora/test/connect.py
index 84c7bf3d..55cf92d 100644
--- a/android/pandora/test/connect.py
+++ b/android/pandora/test/connect.py
@@ -52,7 +52,8 @@
 if __name__ == '__main__':
     # MoblyBinaryHostTest pass test_runner arguments after a "--"
     # to make it work with rewrite argv to skip the "--"
-    index = sys.argv.index('--')
-    sys.argv = sys.argv[:1] + sys.argv[index + 1:]
+    if '--' in sys.argv:
+        index = sys.argv.index('--')
+        sys.argv = sys.argv[:1] + sys.argv[index + 1:]
     logging.basicConfig(level=logging.DEBUG)
     test_runner.main()
diff --git a/android/pandora/test/runner.py b/android/pandora/test/runner.py
new file mode 100644
index 0000000..613b1fc
--- /dev/null
+++ b/android/pandora/test/runner.py
@@ -0,0 +1,114 @@
+# Copyright 2022 Google LLC
+#
+# 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
+#
+#     https://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 os
+import sys
+import logging
+import argparse
+import subprocess
+
+from re import sub
+from pathlib import Path
+from genericpath import exists
+from multiprocessing import Process
+
+ANDROID_BUILD_TOP = os.getenv("ANDROID_BUILD_TOP")
+TARGET_PRODUCT = os.getenv("TARGET_PRODUCT")
+TARGET_BUILD_VARIANT = os.getenv("TARGET_BUILD_VARIANT")
+ANDROID_PRODUCT_OUT = os.getenv("ANDROID_PRODUCT_OUT")
+PANDORA_CF_APK = Path(
+    f'{ANDROID_BUILD_TOP}/out/target/product/vsoc_x86_64/testcases/PandoraServer/x86_64/PandoraServer.apk'
+)
+
+
+def build_pandora_server():
+  target = TARGET_PRODUCT if TARGET_BUILD_VARIANT == "release" else f'{TARGET_PRODUCT}-{TARGET_BUILD_VARIANT}'
+  logging.debug(f'build_pandora_server: {target}')
+  pandora_server_cmd = f'source build/envsetup.sh && lunch {target} && make PandoraServer'
+  subprocess.run(pandora_server_cmd,
+                 cwd=ANDROID_BUILD_TOP,
+                 shell=True,
+                 executable='/bin/bash',
+                 check=True)
+
+
+def install_pandora_server(serial):
+  logging.debug('Install PandoraServer.apk')
+  pandora_apk_path = Path(
+      f'{ANDROID_PRODUCT_OUT}/testcases/PandoraServer/x86_64/PandoraServer.apk')
+  if not pandora_apk_path.exists():
+    logging.error(
+        f"PandoraServer apk is not build or the path is wrong: {pandora_apk_path}"
+    )
+    sys.exit(1)
+  install_apk_cmd = ['adb', 'install', '-r', '-g', str(pandora_apk_path)]
+  if args.serial != "":
+    install_apk_cmd.append(f'-s {serial}')
+  subprocess.run(install_apk_cmd, check=True)
+
+
+def instrument_pandora_server():
+  logging.debug('instrument_pandora_server')
+  instrument_cmd = 'adb shell am instrument --no-hidden-api-checks -w com.android.pandora/.Main'
+  instrument_process = Process(
+      target=lambda: subprocess.run(instrument_cmd, shell=True, check=True))
+  instrument_process.start()
+  return instrument_process
+
+
+def run_test(args):
+  logging.debug(f'run_test config: {args.config} test: {args.test}')
+  test_cmd = ['python3', args.test, '-c', args.config]
+  if args.verbose:
+    test_cmd.append('--verbose')
+  p = subprocess.Popen(test_cmd)
+  p.wait(timeout=args.timeout)
+  p.terminate()
+
+
+def run(args):
+  if not PANDORA_CF_APK.exists() or args.build:
+    build_pandora_server()
+  install_pandora_server(args.serial)
+  instrument_process = instrument_pandora_server()
+  run_test(args)
+  instrument_process.terminate()
+
+
+if __name__ == '__main__':
+  parser = argparse.ArgumentParser()
+  parser.add_argument("test", type=str, help="Test script path")
+  parser.add_argument("config", type=str, help="Test config file path")
+  parser.add_argument("-b",
+                      "--build",
+                      action="store_true",
+                      help="Build the PandoraServer.apk")
+  parser.add_argument("-s",
+                      "--serial",
+                      type=str,
+                      default="",
+                      help="Use device with given serial")
+  parser.add_argument("-m",
+                      "--timeout",
+                      type=int,
+                      default=1800000,
+                      help="Mobly test timeout")
+  parser.add_argument("-v",
+                      "--verbose",
+                      action="store_true",
+                      help="Set console logger level to DEBUG")
+  args = parser.parse_args()
+  console_level = logging.DEBUG if args.verbose else logging.INFO
+  logging.basicConfig(level=console_level)
+  run(args)
diff --git a/pandora/interfaces/Android.bp b/pandora/interfaces/Android.bp
index 38a344a..cc76f5f 100644
--- a/pandora/interfaces/Android.bp
+++ b/pandora/interfaces/Android.bp
@@ -69,6 +69,7 @@
         "pandora_experimental/hid.proto",
         "pandora_experimental/host.proto",
         "pandora_experimental/l2cap.proto",
+        "pandora_experimental/mediaplayer.proto",
         "pandora_experimental/security.proto",
     ],
     out: [
@@ -88,6 +89,8 @@
         "pandora_experimental/host_pb2.py",
         "pandora_experimental/l2cap_grpc.py",
         "pandora_experimental/l2cap_pb2.py",
+        "pandora_experimental/mediaplayer_grpc.py",
+        "pandora_experimental/mediaplayer_pb2.py",
         "pandora_experimental/security_grpc.py",
         "pandora_experimental/security_pb2.py",
     ]
@@ -98,4 +101,4 @@
     srcs: [
         ":pandora_experimental-python-src",
     ],
-}
+}
\ No newline at end of file
diff --git a/pandora/interfaces/pandora_experimental/avrcp.proto b/pandora/interfaces/pandora_experimental/avrcp.proto
index b079f90..d2a7060 100644
--- a/pandora/interfaces/pandora_experimental/avrcp.proto
+++ b/pandora/interfaces/pandora_experimental/avrcp.proto
@@ -4,9 +4,5 @@
 
 package pandora;
 
-import "pandora_experimental/host.proto";
-
-
 service AVRCP {
-
-}
+}
\ No newline at end of file
diff --git a/pandora/interfaces/pandora_experimental/mediaplayer.proto b/pandora/interfaces/pandora_experimental/mediaplayer.proto
new file mode 100644
index 0000000..e6cf1f2
--- /dev/null
+++ b/pandora/interfaces/pandora_experimental/mediaplayer.proto
@@ -0,0 +1,19 @@
+syntax = "proto3";
+
+option java_outer_classname = "MediaPlayerProto";
+
+package pandora;
+
+import "google/protobuf/empty.proto";
+
+
+service MediaPlayer {
+  rpc Play(google.protobuf.Empty) returns (google.protobuf.Empty);
+  rpc Stop(google.protobuf.Empty) returns (google.protobuf.Empty);
+  rpc Pause(google.protobuf.Empty) returns (google.protobuf.Empty);
+  rpc Rewind(google.protobuf.Empty) returns (google.protobuf.Empty);
+  rpc FastForward(google.protobuf.Empty) returns (google.protobuf.Empty);
+  rpc Forward(google.protobuf.Empty) returns (google.protobuf.Empty);
+  rpc Backward(google.protobuf.Empty) returns (google.protobuf.Empty);
+  rpc SetLargeMetadata(google.protobuf.Empty) returns (google.protobuf.Empty);
+}
\ No newline at end of file
diff --git a/service/Android.bp b/service/Android.bp
index 0aa9dba..f1dff44 100644
--- a/service/Android.bp
+++ b/service/Android.bp
@@ -20,6 +20,7 @@
     name: "services.bluetooth-sources",
     srcs: [
         "java/**/*.java",
+        ":statslog-bluetooth-java-gen",
     ],
     visibility: [
         "//frameworks/base/services",
@@ -62,6 +63,7 @@
         "framework-annotations-lib",
         "framework-bluetooth-pre-jarjar",
         "app-compat-annotations",
+        "framework-statsd.stubs.module_lib",
     ],
 
     static_libs: [
@@ -69,6 +71,8 @@
         "androidx.appcompat_appcompat",
         "modules-utils-shell-command-handler",
         "bluetooth-nano-protos",
+        "bluetooth-protos-lite",
+        "libprotobuf-java-lite",
     ],
 
     apex_available: [
diff --git a/service/java/com/android/server/bluetooth/BluetoothAirplaneModeListener.java b/service/java/com/android/server/bluetooth/BluetoothAirplaneModeListener.java
index cd8949b..67d7f12 100644
--- a/service/java/com/android/server/bluetooth/BluetoothAirplaneModeListener.java
+++ b/service/java/com/android/server/bluetooth/BluetoothAirplaneModeListener.java
@@ -24,9 +24,11 @@
 import android.os.Handler;
 import android.os.Looper;
 import android.os.Message;
+import android.os.SystemClock;
 import android.provider.Settings;
 import android.util.Log;
 
+import com.android.bluetooth.BluetoothStatsLog;
 import com.android.internal.annotations.VisibleForTesting;
 
 /**
@@ -68,6 +70,19 @@
 
     @VisibleForTesting static final int MAX_TOAST_COUNT = 10; // 10 times
 
+    /* Tracks the bluetooth state before entering airplane mode*/
+    private boolean mIsBluetoothOnBeforeApmToggle = false;
+    /* Tracks the bluetooth state after entering airplane mode*/
+    private boolean mIsBluetoothOnAfterApmToggle = false;
+    /* Tracks whether user toggled bluetooth in airplane mode */
+    private boolean mUserToggledBluetoothDuringApm = false;
+    /* Tracks whether user toggled bluetooth in airplane mode within one minute */
+    private boolean mUserToggledBluetoothDuringApmWithinMinute = false;
+    /* Tracks whether media profile was connected before entering airplane mode */
+    private boolean mIsMediaProfileConnectedBeforeApmToggle = false;
+    /* Tracks when airplane mode has been enabled */
+    private long mApmEnabledTime = 0;
+
     private final BluetoothManagerService mBluetoothManager;
     private final BluetoothAirplaneModeHandler mHandler;
     private final Context mContext;
@@ -138,61 +153,77 @@
     @VisibleForTesting
     @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED)
     void handleAirplaneModeChange() {
-        if (shouldSkipAirplaneModeChange()) {
-            Log.i(TAG, "Ignore airplane mode change");
-            // Airplane mode enabled when Bluetooth is being used for audio/headering aid.
-            // Bluetooth is not disabled in such case, only state is changed to
-            // BLUETOOTH_ON_AIRPLANE mode.
-            mAirplaneHelper.setSettingsInt(Settings.Global.BLUETOOTH_ON,
-                    BluetoothManagerService.BLUETOOTH_ON_AIRPLANE);
-            if (!isApmEnhancementEnabled() || !isBluetoothToggledOnApm()) {
-                if (shouldPopToast()) {
-                    mAirplaneHelper.showToastMessage();
-                }
-            } else {
-                if (isWifiEnabledOnApm() && isFirstTimeNotification(APM_WIFI_BT_NOTIFICATION)) {
-                    try {
-                        sendApmNotification("bluetooth_and_wifi_stays_on_title",
-                                "bluetooth_and_wifi_stays_on_message",
-                                APM_WIFI_BT_NOTIFICATION);
-                    } catch (Exception e) {
-                        Log.e(TAG, "APM enhancement BT and Wi-Fi stays on notification not shown");
-                    }
-                } else if (!isWifiEnabledOnApm() && isFirstTimeNotification(APM_BT_NOTIFICATION)) {
-                    try {
-                        sendApmNotification("bluetooth_stays_on_title",
-                                "bluetooth_stays_on_message",
-                                APM_BT_NOTIFICATION);
-                    } catch (Exception e) {
-                        Log.e(TAG, "APM enhancement BT stays on notification not shown");
-                    }
-                }
-            }
+        if (mAirplaneHelper == null) {
             return;
         }
-        if (mAirplaneHelper != null) {
-            mAirplaneHelper.onAirplaneModeChanged(mBluetoothManager);
+        if (mAirplaneHelper.isAirplaneModeOn()) {
+            mApmEnabledTime = SystemClock.elapsedRealtime();
+            mIsBluetoothOnBeforeApmToggle = mAirplaneHelper.isBluetoothOn();
+            mIsBluetoothOnAfterApmToggle = shouldSkipAirplaneModeChange();
+            mIsMediaProfileConnectedBeforeApmToggle = mAirplaneHelper.isMediaProfileConnected();
+            if (mIsBluetoothOnAfterApmToggle) {
+                Log.i(TAG, "Ignore airplane mode change");
+                // Airplane mode enabled when Bluetooth is being used for audio/headering aid.
+                // Bluetooth is not disabled in such case, only state is changed to
+                // BLUETOOTH_ON_AIRPLANE mode.
+                mAirplaneHelper.setSettingsInt(Settings.Global.BLUETOOTH_ON,
+                        BluetoothManagerService.BLUETOOTH_ON_AIRPLANE);
+                if (!isApmEnhancementEnabled() || !isBluetoothToggledOnApm()) {
+                    if (shouldPopToast()) {
+                        mAirplaneHelper.showToastMessage();
+                    }
+                } else {
+                    if (isWifiEnabledOnApm() && isFirstTimeNotification(APM_WIFI_BT_NOTIFICATION)) {
+                        try {
+                            sendApmNotification("bluetooth_and_wifi_stays_on_title",
+                                    "bluetooth_and_wifi_stays_on_message",
+                                    APM_WIFI_BT_NOTIFICATION);
+                        } catch (Exception e) {
+                            Log.e(TAG,
+                                    "APM enhancement BT and Wi-Fi stays on notification not shown");
+                        }
+                    } else if (!isWifiEnabledOnApm() && isFirstTimeNotification(
+                            APM_BT_NOTIFICATION)) {
+                        try {
+                            sendApmNotification("bluetooth_stays_on_title",
+                                    "bluetooth_stays_on_message",
+                                    APM_BT_NOTIFICATION);
+                        } catch (Exception e) {
+                            Log.e(TAG, "APM enhancement BT stays on notification not shown");
+                        }
+                    }
+                }
+                return;
+            }
+        } else {
+            BluetoothStatsLog.write(BluetoothStatsLog.AIRPLANE_MODE_SESSION_REPORTED,
+                    BluetoothStatsLog.AIRPLANE_MODE_SESSION_REPORTED__PACKAGE_NAME__BLUETOOTH,
+                    mIsBluetoothOnBeforeApmToggle,
+                    mIsBluetoothOnAfterApmToggle,
+                    mAirplaneHelper.isBluetoothOn(),
+                    isBluetoothToggledOnApm(),
+                    mUserToggledBluetoothDuringApm,
+                    mUserToggledBluetoothDuringApmWithinMinute,
+                    mIsMediaProfileConnectedBeforeApmToggle);
+            mUserToggledBluetoothDuringApm = false;
+            mUserToggledBluetoothDuringApmWithinMinute = false;
         }
+        mAirplaneHelper.onAirplaneModeChanged(mBluetoothManager);
     }
 
     @VisibleForTesting
     boolean shouldSkipAirplaneModeChange() {
-        if (mAirplaneHelper == null) {
-            return false;
-        }
         boolean apmEnhancementUsed = isApmEnhancementEnabled() && isBluetoothToggledOnApm();
 
         // APM feature disabled or user has not used the feature yet by changing BT state in APM
         // BT will only remain on in APM when media profile is connected
         if (!apmEnhancementUsed && mAirplaneHelper.isBluetoothOn()
-                && mAirplaneHelper.isAirplaneModeOn()
                 && mAirplaneHelper.isMediaProfileConnected()) {
             return true;
         }
         // APM feature enabled and user has used the feature by changing BT state in APM
         // BT will only remain on in APM based on user's last action in APM
         if (apmEnhancementUsed && mAirplaneHelper.isBluetoothOn()
-                && mAirplaneHelper.isAirplaneModeOn()
                 && mAirplaneHelper.isBluetoothOnAPM()) {
             return true;
         }
@@ -200,7 +231,6 @@
         // BT will only remain on in APM if the default value is set to on
         if (isApmEnhancementEnabled() && !isBluetoothToggledOnApm()
                 && mAirplaneHelper.isBluetoothOn()
-                && mAirplaneHelper.isAirplaneModeOn()
                 && mAirplaneHelper.isBluetoothOnAPM()) {
             return true;
         }
@@ -245,4 +275,15 @@
         mAirplaneHelper.setSettingsSecureInt(notificationState,
                 NOTIFICATION_SHOWN);
     }
+
+    /**
+     * Helper method to update whether user toggled Bluetooth in airplane mode
+     */
+    public void updateBluetoothToggledTime() {
+        if (!mUserToggledBluetoothDuringApm) {
+            mUserToggledBluetoothDuringApmWithinMinute =
+                    SystemClock.elapsedRealtime() - mApmEnabledTime < 60000;
+        }
+        mUserToggledBluetoothDuringApm = true;
+    }
 }
diff --git a/service/java/com/android/server/bluetooth/BluetoothManagerService.java b/service/java/com/android/server/bluetooth/BluetoothManagerService.java
index a249286..f7bcc69 100644
--- a/service/java/com/android/server/bluetooth/BluetoothManagerService.java
+++ b/service/java/com/android/server/bluetooth/BluetoothManagerService.java
@@ -1379,20 +1379,23 @@
         synchronized (mReceiver) {
             mQuietEnableExternal = false;
             mEnableExternal = true;
-            if (isAirplaneModeOn() && isApmEnhancementOn()) {
-                setSettingsSecureInt(BLUETOOTH_APM_STATE, BLUETOOTH_ON_APM);
-                setSettingsSecureInt(APM_USER_TOGGLED_BLUETOOTH, USED);
-                if (isFirstTimeNotification(APM_BT_ENABLED_NOTIFICATION)) {
-                    final long callingIdentity = Binder.clearCallingIdentity();
-                    try {
-                        mBluetoothAirplaneModeListener.sendApmNotification(
-                                "bluetooth_enabled_apm_title",
-                                "bluetooth_enabled_apm_message",
-                                APM_BT_ENABLED_NOTIFICATION);
-                    } catch (Exception e) {
-                        Log.e(TAG, "APM enhancement BT enabled notification not shown");
-                    } finally {
-                        Binder.restoreCallingIdentity(callingIdentity);
+            if (isAirplaneModeOn()) {
+                mBluetoothAirplaneModeListener.updateBluetoothToggledTime();
+                if (isApmEnhancementOn()) {
+                    setSettingsSecureInt(BLUETOOTH_APM_STATE, BLUETOOTH_ON_APM);
+                    setSettingsSecureInt(APM_USER_TOGGLED_BLUETOOTH, USED);
+                    if (isFirstTimeNotification(APM_BT_ENABLED_NOTIFICATION)) {
+                        final long callingIdentity = Binder.clearCallingIdentity();
+                        try {
+                            mBluetoothAirplaneModeListener.sendApmNotification(
+                                    "bluetooth_enabled_apm_title",
+                                    "bluetooth_enabled_apm_message",
+                                    APM_BT_ENABLED_NOTIFICATION);
+                        } catch (Exception e) {
+                            Log.e(TAG, "APM enhancement BT enabled notification not shown");
+                        } finally {
+                            Binder.restoreCallingIdentity(callingIdentity);
+                        }
                     }
                 }
             }
@@ -1436,9 +1439,12 @@
         }
 
         synchronized (mReceiver) {
-            if (isAirplaneModeOn() && isApmEnhancementOn()) {
-                setSettingsSecureInt(BLUETOOTH_APM_STATE, BLUETOOTH_OFF_APM);
-                setSettingsSecureInt(APM_USER_TOGGLED_BLUETOOTH, USED);
+            if (isAirplaneModeOn()) {
+                mBluetoothAirplaneModeListener.updateBluetoothToggledTime();
+                if (isApmEnhancementOn()) {
+                    setSettingsSecureInt(BLUETOOTH_APM_STATE, BLUETOOTH_OFF_APM);
+                    setSettingsSecureInt(APM_USER_TOGGLED_BLUETOOTH, USED);
+                }
             }
 
             if (persist) {
diff --git a/service/tests/src/com/android/server/BluetoothAirplaneModeListenerTest.java b/service/tests/src/com/android/server/BluetoothAirplaneModeListenerTest.java
index 92c9851..cc3dd45 100644
--- a/service/tests/src/com/android/server/BluetoothAirplaneModeListenerTest.java
+++ b/service/tests/src/com/android/server/BluetoothAirplaneModeListenerTest.java
@@ -84,9 +84,6 @@
         Assert.assertFalse(mBluetoothAirplaneModeListener.shouldSkipAirplaneModeChange());
 
         when(mHelper.isMediaProfileConnected()).thenReturn(true);
-        Assert.assertFalse(mBluetoothAirplaneModeListener.shouldSkipAirplaneModeChange());
-
-        when(mHelper.isAirplaneModeOn()).thenReturn(true);
         Assert.assertTrue(mBluetoothAirplaneModeListener.shouldSkipAirplaneModeChange());
     }
 
diff --git a/system/bta/dm/bta_dm_act.cc b/system/bta/dm/bta_dm_act.cc
index bde70de..661504b 100644
--- a/system/bta/dm/bta_dm_act.cc
+++ b/system/bta/dm/bta_dm_act.cc
@@ -38,6 +38,7 @@
 #include "btif/include/stack_manager.h"
 #include "device/include/controller.h"
 #include "device/include/interop.h"
+#include "gd/common/init_flags.h"
 #include "main/shim/acl_api.h"
 #include "main/shim/btm_api.h"
 #include "main/shim/dumpsys.h"
@@ -47,6 +48,7 @@
 #include "osi/include/fixed_queue.h"
 #include "osi/include/log.h"
 #include "osi/include/osi.h"
+#include "osi/include/properties.h"
 #include "stack/btm/btm_ble_int.h"
 #include "stack/btm/btm_dev.h"
 #include "stack/btm/btm_sec.h"
@@ -87,7 +89,8 @@
 static uint8_t bta_dm_new_link_key_cback(const RawAddress& bd_addr,
                                          DEV_CLASS dev_class,
                                          tBTM_BD_NAME bd_name,
-                                         const LinkKey& key, uint8_t key_type);
+                                         const LinkKey& key, uint8_t key_type,
+                                         bool is_ctkd);
 static void bta_dm_authentication_complete_cback(const RawAddress& bd_addr,
                                                  DEV_CLASS dev_class,
                                                  tBTM_BD_NAME bd_name,
@@ -915,6 +918,10 @@
   bta_dm_search_cb.transport = p_data->discover.transport;
 
   bta_dm_search_cb.name_discover_done = false;
+
+  LOG_INFO("bta_dm_discovery: starting service discovery to %s , transport: %s",
+           PRIVATE_ADDRESS(p_data->discover.bd_addr),
+           bt_transport_text(p_data->discover.transport).c_str());
   bta_dm_discover_device(p_data->discover.bd_addr);
 }
 
@@ -1455,6 +1462,9 @@
   tBTA_DM_MSG* p_pending_discovery =
       (tBTA_DM_MSG*)osi_malloc(sizeof(tBTA_DM_API_DISCOVER));
   memcpy(p_pending_discovery, p_data, sizeof(tBTA_DM_API_DISCOVER));
+
+  LOG_INFO("bta_dm_discovery: queuing service discovery to %s",
+           p_pending_discovery->discover.bd_addr.ToString().c_str());
   fixed_queue_enqueue(bta_dm_search_cb.pending_discovery_queue,
                       p_pending_discovery);
 }
@@ -1507,7 +1517,10 @@
  ******************************************************************************/
 void bta_dm_search_clear_queue() {
   osi_free_and_reset((void**)&bta_dm_search_cb.p_pending_search);
-  fixed_queue_flush(bta_dm_search_cb.pending_discovery_queue, osi_free);
+  if (bluetooth::common::InitFlags::
+          IsBtmDmFlushDiscoveryQueueOnSearchCancel()) {
+    fixed_queue_flush(bta_dm_search_cb.pending_discovery_queue, osi_free);
+  }
 }
 
 /*******************************************************************************
@@ -1747,6 +1760,8 @@
 
       if (transport == BT_TRANSPORT_LE) {
         if (bta_dm_search_cb.services_to_search & BTA_BLE_SERVICE_MASK) {
+          LOG_INFO("bta_dm_discovery: starting GATT discovery on %s",
+                   PRIVATE_ADDRESS(bta_dm_search_cb.peer_bdaddr));
           // set the raw data buffer here
           memset(g_disc_raw_data_buf, 0, sizeof(g_disc_raw_data_buf));
           /* start GATT for service discovery */
@@ -1754,6 +1769,8 @@
           return;
         }
       } else {
+        LOG_INFO("bta_dm_discovery: starting SDP discovery on %s",
+                 PRIVATE_ADDRESS(bta_dm_search_cb.peer_bdaddr));
         bta_dm_search_cb.sdp_results = false;
         bta_dm_find_services(bta_dm_search_cb.peer_bdaddr);
         return;
@@ -2075,7 +2092,8 @@
 static uint8_t bta_dm_new_link_key_cback(const RawAddress& bd_addr,
                                          UNUSED_ATTR DEV_CLASS dev_class,
                                          tBTM_BD_NAME bd_name,
-                                         const LinkKey& key, uint8_t key_type) {
+                                         const LinkKey& key, uint8_t key_type,
+                                         bool is_ctkd) {
   tBTA_DM_SEC sec_event;
   tBTA_DM_AUTH_CMPL* p_auth_cmpl;
   tBTA_DM_SEC_EVT event = BTA_DM_AUTH_CMPL_EVT;
@@ -2092,6 +2110,8 @@
   p_auth_cmpl->key_type = key_type;
   p_auth_cmpl->success = true;
   p_auth_cmpl->key = key;
+  p_auth_cmpl->is_ctkd = is_ctkd;
+
   sec_event.auth_cmpl.fail_reason = HCI_SUCCESS;
 
   // Report the BR link key based on the BR/EDR address and type
diff --git a/system/bta/gatt/bta_gattc_act.cc b/system/bta/gatt/bta_gattc_act.cc
index ecb9a87..2026291 100644
--- a/system/bta/gatt/bta_gattc_act.cc
+++ b/system/bta/gatt/bta_gattc_act.cc
@@ -711,7 +711,7 @@
 
 /** Configure MTU size on the GATT connection */
 void bta_gattc_cfg_mtu(tBTA_GATTC_CLCB* p_clcb, const tBTA_GATTC_DATA* p_data) {
-  if (!bta_gattc_enqueue(p_clcb, p_data)) return;
+  if (bta_gattc_enqueue(p_clcb, p_data) == ENQUEUED_FOR_LATER) return;
 
   tGATT_STATUS status =
       GATTC_ConfigureMTU(p_clcb->bta_conn_id, p_data->api_mtu.mtu);
@@ -723,6 +723,7 @@
 
     bta_gattc_cmpl_sendmsg(p_clcb->bta_conn_id, GATTC_OPTYPE_CONFIG, status,
                            NULL);
+    bta_gattc_continue(p_clcb);
   }
 }
 
@@ -855,6 +856,8 @@
      * referenced by p_clcb->p_q_cmd
      */
     if (p_q_cmd != p_clcb->p_q_cmd) osi_free_and_reset((void**)&p_q_cmd);
+  } else {
+    bta_gattc_continue(p_clcb);
   }
 
   if (p_clcb->p_rcb->p_cback) {
@@ -866,7 +869,7 @@
 
 /** Read an attribute */
 void bta_gattc_read(tBTA_GATTC_CLCB* p_clcb, const tBTA_GATTC_DATA* p_data) {
-  if (!bta_gattc_enqueue(p_clcb, p_data)) return;
+  if (bta_gattc_enqueue(p_clcb, p_data) == ENQUEUED_FOR_LATER) return;
 
   tGATT_STATUS status;
   if (p_data->api_read.handle != 0) {
@@ -893,13 +896,14 @@
 
     bta_gattc_cmpl_sendmsg(p_clcb->bta_conn_id, GATTC_OPTYPE_READ, status,
                            NULL);
+    bta_gattc_continue(p_clcb);
   }
 }
 
 /** read multiple */
 void bta_gattc_read_multi(tBTA_GATTC_CLCB* p_clcb,
                           const tBTA_GATTC_DATA* p_data) {
-  if (!bta_gattc_enqueue(p_clcb, p_data)) return;
+  if (bta_gattc_enqueue(p_clcb, p_data) == ENQUEUED_FOR_LATER) return;
 
   tGATT_READ_PARAM read_param;
   memset(&read_param, 0, sizeof(tGATT_READ_PARAM));
@@ -918,12 +922,13 @@
 
     bta_gattc_cmpl_sendmsg(p_clcb->bta_conn_id, GATTC_OPTYPE_READ, status,
                            NULL);
+    bta_gattc_continue(p_clcb);
   }
 }
 
 /** Write an attribute */
 void bta_gattc_write(tBTA_GATTC_CLCB* p_clcb, const tBTA_GATTC_DATA* p_data) {
-  if (!bta_gattc_enqueue(p_clcb, p_data)) return;
+  if (bta_gattc_enqueue(p_clcb, p_data) == ENQUEUED_FOR_LATER) return;
 
   tGATT_STATUS status = GATT_SUCCESS;
   tGATT_VALUE attr;
@@ -947,12 +952,13 @@
 
     bta_gattc_cmpl_sendmsg(p_clcb->bta_conn_id, GATTC_OPTYPE_WRITE, status,
                            NULL);
+    bta_gattc_continue(p_clcb);
   }
 }
 
 /** send execute write */
 void bta_gattc_execute(tBTA_GATTC_CLCB* p_clcb, const tBTA_GATTC_DATA* p_data) {
-  if (!bta_gattc_enqueue(p_clcb, p_data)) return;
+  if (bta_gattc_enqueue(p_clcb, p_data) == ENQUEUED_FOR_LATER) return;
 
   tGATT_STATUS status =
       GATTC_ExecuteWrite(p_clcb->bta_conn_id, p_data->api_exec.is_execute);
@@ -962,6 +968,7 @@
 
     bta_gattc_cmpl_sendmsg(p_clcb->bta_conn_id, GATTC_OPTYPE_EXE_WRITE, status,
                            NULL);
+    bta_gattc_continue(p_clcb);
   }
 }
 
diff --git a/system/bta/gatt/bta_gattc_cache.cc b/system/bta/gatt/bta_gattc_cache.cc
index a477e1f..0123908 100644
--- a/system/bta/gatt/bta_gattc_cache.cc
+++ b/system/bta/gatt/bta_gattc_cache.cc
@@ -313,6 +313,7 @@
         !GATT_HANDLE_IS_VALID(end_handle)) {
       LOG(ERROR) << "invalid start_handle=" << loghex(start_handle)
                  << ", end_handle=" << loghex(end_handle);
+      p_sdp_rec = SDP_FindServiceInDb(cb_data->p_sdp_db, 0, p_sdp_rec);
       continue;
     }
 
diff --git a/system/bta/gatt/bta_gattc_int.h b/system/bta/gatt/bta_gattc_int.h
index 1df5e53..2cdc5c5 100644
--- a/system/bta/gatt/bta_gattc_int.h
+++ b/system/bta/gatt/bta_gattc_int.h
@@ -25,6 +25,7 @@
 #define BTA_GATTC_INT_H
 
 #include <cstdint>
+#include <deque>
 
 #include "bt_target.h"  // Must be first to define build configuration
 #include "bta/gatt/database.h"
@@ -254,6 +255,7 @@
   tBTA_GATTC_RCB* p_rcb;    /* pointer to the registration CB */
   tBTA_GATTC_SERV* p_srcb;  /* server cache CB */
   const tBTA_GATTC_DATA* p_q_cmd; /* command in queue waiting for execution */
+  std::deque<const tBTA_GATTC_DATA*> p_q_cmd_queue;
 
 // request during discover state
 #define BTA_GATTC_DISCOVER_REQ_NONE 0
@@ -423,8 +425,16 @@
 extern tBTA_GATTC_CLCB* bta_gattc_find_int_conn_clcb(tBTA_GATTC_DATA* p_msg);
 extern tBTA_GATTC_CLCB* bta_gattc_find_int_disconn_clcb(tBTA_GATTC_DATA* p_msg);
 
-extern bool bta_gattc_enqueue(tBTA_GATTC_CLCB* p_clcb,
-                              const tBTA_GATTC_DATA* p_data);
+enum BtaEnqueuedResult_t {
+  ENQUEUED_READY_TO_SEND,
+  ENQUEUED_FOR_LATER,
+};
+
+extern BtaEnqueuedResult_t bta_gattc_enqueue(tBTA_GATTC_CLCB* p_clcb,
+                                             const tBTA_GATTC_DATA* p_data);
+extern bool bta_gattc_is_data_queued(tBTA_GATTC_CLCB* p_clcb,
+                                     const tBTA_GATTC_DATA* p_data);
+extern void bta_gattc_continue(tBTA_GATTC_CLCB* p_clcb);
 
 extern bool bta_gattc_check_notif_registry(tBTA_GATTC_RCB* p_clreg,
                                            tBTA_GATTC_SERV* p_srcb,
diff --git a/system/bta/gatt/bta_gattc_main.cc b/system/bta/gatt/bta_gattc_main.cc
index bba20b5..b624c5a 100644
--- a/system/bta/gatt/bta_gattc_main.cc
+++ b/system/bta/gatt/bta_gattc_main.cc
@@ -328,7 +328,7 @@
     action = state_table[event][i];
     if (action != BTA_GATTC_IGNORE) {
       (*bta_gattc_action[action])(p_clcb, p_data);
-      if (p_clcb->p_q_cmd == p_data) {
+      if (bta_gattc_is_data_queued(p_clcb, p_data)) {
         /* buffer is queued, don't free in the bta dispatcher.
          * we free it ourselves when a completion event is received.
          */
diff --git a/system/bta/gatt/bta_gattc_utils.cc b/system/bta/gatt/bta_gattc_utils.cc
index 1412702..a3ff62d 100644
--- a/system/bta/gatt/bta_gattc_utils.cc
+++ b/system/bta/gatt/bta_gattc_utils.cc
@@ -24,6 +24,8 @@
 
 #define LOG_TAG "bt_bta_gattc"
 
+#include <base/logging.h>
+
 #include <cstdint>
 
 #include "bt_target.h"  // Must be first to define build configuration
@@ -31,12 +33,11 @@
 #include "device/include/controller.h"
 #include "gd/common/init_flags.h"
 #include "osi/include/allocator.h"
+#include "osi/include/log.h"
 #include "types/bt_transport.h"
 #include "types/hci_role.h"
 #include "types/raw_address.h"
 
-#include <base/logging.h>
-
 static uint8_t ble_acceptlist_size() {
   const controller_t* controller = controller_get_interface();
   if (!controller->supports_ble()) {
@@ -146,6 +147,7 @@
       p_clcb->status = GATT_SUCCESS;
       p_clcb->transport = transport;
       p_clcb->bda = remote_bda;
+      p_clcb->p_q_cmd = NULL;
 
       p_clcb->p_rcb = bta_gattc_cl_get_regcb(client_if);
 
@@ -217,8 +219,29 @@
     p_srcb->gatt_database.Clear();
   }
 
-  osi_free_and_reset((void**)&p_clcb->p_q_cmd);
-  memset(p_clcb, 0, sizeof(tBTA_GATTC_CLCB));
+  while (!p_clcb->p_q_cmd_queue.empty()) {
+    auto p_q_cmd = p_clcb->p_q_cmd_queue.front();
+    p_clcb->p_q_cmd_queue.pop_front();
+    osi_free_and_reset((void**)&p_q_cmd);
+  }
+
+  if (p_clcb->p_q_cmd != NULL) {
+    osi_free_and_reset((void**)&p_clcb->p_q_cmd);
+  }
+
+  /* Clear p_clcb. Some of the fields are already reset e.g. p_q_cmd_queue and
+   * p_q_cmd. */
+  p_clcb->bta_conn_id = 0;
+  p_clcb->bda = {};
+  p_clcb->transport = 0;
+  p_clcb->p_rcb = NULL;
+  p_clcb->p_srcb = NULL;
+  p_clcb->request_during_discovery = 0;
+  p_clcb->auto_update = 0;
+  p_clcb->disc_active = 0;
+  p_clcb->in_use = 0;
+  p_clcb->state = BTA_GATTC_IDLE_ST;
+  p_clcb->status = GATT_SUCCESS;
 }
 
 /*******************************************************************************
@@ -315,24 +338,57 @@
   }
   return p_tcb;
 }
+
+void bta_gattc_continue(tBTA_GATTC_CLCB* p_clcb) {
+  if (p_clcb->p_q_cmd != NULL) {
+    LOG_INFO("Already scheduled another request for conn_id = 0x%04x",
+             p_clcb->bta_conn_id);
+    return;
+  }
+
+  if (p_clcb->p_q_cmd_queue.empty()) {
+    LOG_INFO("Nothing to do for conn_id = 0x%04x", p_clcb->bta_conn_id);
+    return;
+  }
+
+  const tBTA_GATTC_DATA* p_q_cmd = p_clcb->p_q_cmd_queue.front();
+  p_clcb->p_q_cmd_queue.pop_front();
+  bta_gattc_sm_execute(p_clcb, p_q_cmd->hdr.event, p_q_cmd);
+}
+
+bool bta_gattc_is_data_queued(tBTA_GATTC_CLCB* p_clcb,
+                              const tBTA_GATTC_DATA* p_data) {
+  if (p_clcb->p_q_cmd == p_data) {
+    return true;
+  }
+
+  auto it = std::find(p_clcb->p_q_cmd_queue.begin(),
+                      p_clcb->p_q_cmd_queue.end(), p_data);
+  return it != p_clcb->p_q_cmd_queue.end();
+}
 /*******************************************************************************
  *
  * Function         bta_gattc_enqueue
  *
  * Description      enqueue a client request in clcb.
  *
- * Returns          success or failure.
+ * Returns          BtaEnqueuedResult_t
  *
  ******************************************************************************/
-bool bta_gattc_enqueue(tBTA_GATTC_CLCB* p_clcb, const tBTA_GATTC_DATA* p_data) {
+BtaEnqueuedResult_t bta_gattc_enqueue(tBTA_GATTC_CLCB* p_clcb,
+                                      const tBTA_GATTC_DATA* p_data) {
   if (p_clcb->p_q_cmd == NULL) {
     p_clcb->p_q_cmd = p_data;
-    return true;
+    return ENQUEUED_READY_TO_SEND;
   }
 
-  LOG(ERROR) << __func__ << ": already has a pending command";
-  /* skip the callback now. ----- need to send callback ? */
-  return false;
+  LOG_INFO(
+      "Already has a pending command to executer. Queuing for later %s conn "
+      "id=0x%04x",
+      p_clcb->bda.ToString().c_str(), p_clcb->bta_conn_id);
+  p_clcb->p_q_cmd_queue.push_back(p_data);
+
+  return ENQUEUED_FOR_LATER;
 }
 
 /*******************************************************************************
diff --git a/system/bta/include/bta_api.h b/system/bta/include/bta_api.h
index 98182f4..18a71f3 100644
--- a/system/bta/include/bta_api.h
+++ b/system/bta/include/bta_api.h
@@ -304,6 +304,7 @@
       fail_reason; /* The HCI reason/error code for when success=false */
   tBLE_ADDR_TYPE addr_type; /* Peer device address type */
   tBT_DEVICE_TYPE dev_type;
+  bool is_ctkd; /* True if key is derived using CTKD procedure */
 } tBTA_DM_AUTH_CMPL;
 
 /* Structure associated with BTA_DM_LINK_UP_EVT */
diff --git a/system/bta/le_audio/client.cc b/system/bta/le_audio/client.cc
index 9f6ff28..948efcd 100644
--- a/system/bta/le_audio/client.cc
+++ b/system/bta/le_audio/client.cc
@@ -18,6 +18,8 @@
 #include <base/bind.h>
 #include <base/strings/string_number_conversions.h>
 
+#include <deque>
+
 #include "advertise_data_parser.h"
 #include "audio_hal_interface/le_audio_software.h"
 #include "bta/csis/csis_types.h"
@@ -66,6 +68,7 @@
 using bluetooth::le_audio::GroupStreamStatus;
 using le_audio::CodecManager;
 using le_audio::ContentControlIdKeeper;
+using le_audio::DeviceConnectState;
 using le_audio::LeAudioDevice;
 using le_audio::LeAudioDeviceGroup;
 using le_audio::LeAudioDeviceGroups;
@@ -471,7 +474,7 @@
       if (group_id == bluetooth::groups::kGroupUnknown) return;
 
       LOG(INFO) << __func__ << "Set member adding ...";
-      leAudioDevices_.Add(address, true);
+      leAudioDevices_.Add(address, DeviceConnectState::CONNECTING_BY_USER);
       leAudioDevice = leAudioDevices_.FindByAddress(address);
     } else {
       if (leAudioDevice->group_id_ != bluetooth::groups::kGroupUnknown) {
@@ -739,13 +742,15 @@
       return false;
     }
 
+    if (group->GetState() != AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING) {
+      stream_setup_start_timestamp_ =
+          bluetooth::common::time_get_os_boottime_us();
+    }
+
     bool result = groupStateMachine_->StartStream(
         group, static_cast<LeAudioContextType>(final_context_type),
         adjusted_metadata_context_type,
         GetAllCcids(adjusted_metadata_context_type));
-    if (result)
-      stream_setup_start_timestamp_ =
-          bluetooth::common::time_get_os_boottime_us();
 
     return result;
   }
@@ -974,6 +979,7 @@
     } else {
       /* In case there was an active group. Stop the stream */
       GroupStop(active_group_id_);
+      callbacks_->OnGroupStatus(active_group_id_, GroupStatus::INACTIVE);
     }
 
     active_group_id_ = group_id;
@@ -988,7 +994,7 @@
 
     if (leAudioDevice->conn_id_ != GATT_INVALID_CONN_ID) {
       Disconnect(address);
-      leAudioDevice->removing_device_ = true;
+      leAudioDevice->SetConnectionState(DeviceConnectState::REMOVING);
       return;
     }
 
@@ -1007,9 +1013,10 @@
   void Connect(const RawAddress& address) override {
     LeAudioDevice* leAudioDevice = leAudioDevices_.FindByAddress(address);
     if (!leAudioDevice) {
-      leAudioDevices_.Add(address, true);
+      leAudioDevices_.Add(address, DeviceConnectState::CONNECTING_BY_USER);
+
     } else {
-      leAudioDevice->connecting_actively_ = true;
+      leAudioDevice->SetConnectionState(DeviceConnectState::CONNECTING_BY_USER);
 
       le_audio::MetricsCollector::Get()->OnConnectionStateChanged(
           leAudioDevice->group_id_, address, ConnectionState::CONNECTING,
@@ -1058,7 +1065,7 @@
         source_audio_location, sink_supported_context_types,
         source_supported_context_types);
 
-    leAudioDevices_.Add(address, false);
+    leAudioDevices_.Add(address, DeviceConnectState::DISCONNECTED);
     leAudioDevice = leAudioDevices_.FindByAddress(address);
 
     int group_id = DeviceGroups::Get()->GetGroupId(
@@ -1073,6 +1080,10 @@
           le_audio::types::kLeAudioDirectionSink;
     }
 
+    callbacks_->OnSinkAudioLocationAvailable(
+        leAudioDevice->address_,
+        leAudioDevice->snk_audio_locations_.to_ulong());
+
     leAudioDevice->src_audio_locations_ = source_audio_location;
     if (source_audio_location != 0) {
       leAudioDevice->audio_directions_ |=
@@ -1105,6 +1116,9 @@
     }
 
     if (autoconnect) {
+      leAudioDevice->SetConnectionState(
+          DeviceConnectState::CONNECTING_AUTOCONNECT);
+      leAudioDevice->autoconnect_flag_ = true;
       BTA_GATTC_Open(gatt_if_, address, false, false);
     }
   }
@@ -1150,6 +1164,8 @@
                << " to background connect to connected group: "
                << leAudioDevice->group_id_;
 
+    leAudioDevice->SetConnectionState(
+        DeviceConnectState::CONNECTING_AUTOCONNECT);
     BTA_GATTC_Open(gatt_if_, leAudioDevice->address_, false, false);
   }
 
@@ -1163,9 +1179,9 @@
     }
 
     /* cancel pending direct connect */
-    if (leAudioDevice->connecting_actively_) {
+    if (leAudioDevice->GetConnectionState() ==
+        DeviceConnectState::CONNECTING_BY_USER) {
       BTA_GATTC_CancelOpen(gatt_if_, address, true);
-      leAudioDevice->connecting_actively_ = false;
     }
 
     /* Removes all registrations for connection */
@@ -1175,6 +1191,7 @@
       /* User is disconnecting the device, we shall remove the autoconnect flag
        */
       btif_storage_set_leaudio_autoconnect(address, false);
+      leAudioDevice->autoconnect_flag_ = false;
 
       auto group = aseGroups_.FindById(leAudioDevice->group_id_);
       if (group &&
@@ -1200,6 +1217,8 @@
       return;
     }
 
+    leAudioDevice->SetConnectionState(DeviceConnectState::DISCONNECTING);
+
     if (acl_force_disconnect) {
      leAudioDevice->DisconnectAcl();
      return;
@@ -1509,7 +1528,13 @@
 
     if (status != GATT_SUCCESS) {
       /* autoconnect connection failed, that's ok */
-      if (!leAudioDevice->connecting_actively_) return;
+      if (leAudioDevice->GetConnectionState() ==
+          DeviceConnectState::CONNECTING_AUTOCONNECT) {
+        leAudioDevice->SetConnectionState(DeviceConnectState::DISCONNECTED);
+        return;
+      }
+
+      leAudioDevice->SetConnectionState(DeviceConnectState::DISCONNECTED);
 
       LOG(ERROR) << "Failed to connect to LeAudio leAudioDevice, status: "
                  << +status;
@@ -1527,7 +1552,15 @@
 
     BTM_RequestPeerSCA(leAudioDevice->address_, transport);
 
-    leAudioDevice->connecting_actively_ = false;
+    if (leAudioDevice->GetConnectionState() ==
+        DeviceConnectState::CONNECTING_AUTOCONNECT) {
+      leAudioDevice->SetConnectionState(
+          DeviceConnectState::CONNECTED_AUTOCONNECT_GETTING_READY);
+    } else {
+      leAudioDevice->SetConnectionState(
+          DeviceConnectState::CONNECTED_BY_USER_GETTING_READY);
+    }
+
     leAudioDevice->conn_id_ = conn_id;
     leAudioDevice->mtu_ = mtu;
 
@@ -1545,13 +1578,8 @@
     }
 
     if (BTM_IsLinkKeyKnown(address, BT_TRANSPORT_LE)) {
-      int result = BTM_SetEncryption(
-          address, BT_TRANSPORT_LE,
-          [](const RawAddress* bd_addr, tBT_TRANSPORT transport,
-             void* p_ref_data, tBTM_STATUS status) {
-            if (instance) instance->OnEncryptionComplete(*bd_addr, status);
-          },
-          nullptr, BTM_BLE_SEC_ENCRYPT);
+      int result = BTM_SetEncryption(address, BT_TRANSPORT_LE, nullptr, nullptr,
+                                     BTM_BLE_SEC_ENCRYPT);
 
       LOG(INFO) << __func__
                 << "Encryption required. Request result: " << result;
@@ -1633,13 +1661,22 @@
     if (status != BTM_SUCCESS) {
       LOG(ERROR) << "Encryption failed"
                  << " status: " << int{status};
-      BTA_GATTC_Close(leAudioDevice->conn_id_);
-      if (leAudioDevice->connecting_actively_) {
+      if (leAudioDevice->GetConnectionState() ==
+          DeviceConnectState::CONNECTED_BY_USER_GETTING_READY) {
         callbacks_->OnConnectionState(ConnectionState::DISCONNECTED, address);
         le_audio::MetricsCollector::Get()->OnConnectionStateChanged(
             leAudioDevice->group_id_, address, ConnectionState::CONNECTED,
             le_audio::ConnectionStatus::FAILED);
       }
+
+      leAudioDevice->SetConnectionState(DeviceConnectState::DISCONNECTING);
+
+      BTA_GATTC_Close(leAudioDevice->conn_id_);
+      return;
+    }
+
+    if (leAudioDevice->encrypted_) {
+      LOG(INFO) << __func__ << " link already encrypted, nothing to do";
       return;
     }
 
@@ -1649,11 +1686,6 @@
     if (leAudioDevice->known_service_handles_)
       RegisterKnownNotifications(leAudioDevice);
 
-    if (leAudioDevice->encrypted_) {
-      LOG(INFO) << __func__ << " link already encrypted, nothing to do";
-      return;
-    }
-
     leAudioDevice->encrypted_ = true;
 
     /* If we know services and read is not ongoing, this is reconnection and
@@ -1678,6 +1710,7 @@
       return;
     }
 
+    BtaGattQueue::Clean(leAudioDevice->conn_id_);
     LeAudioDeviceGroup* group = aseGroups_.FindById(leAudioDevice->group_id_);
 
     groupStateMachine_->ProcessHciNotifAclDisconnected(group, leAudioDevice);
@@ -1694,7 +1727,7 @@
         leAudioDevice->group_id_, address, ConnectionState::DISCONNECTED,
         le_audio::ConnectionStatus::SUCCESS);
 
-    if (leAudioDevice->removing_device_) {
+    if (leAudioDevice->GetConnectionState() == DeviceConnectState::REMOVING) {
       if (leAudioDevice->group_id_ != bluetooth::groups::kGroupUnknown) {
         auto group = aseGroups_.FindById(leAudioDevice->group_id_);
         group_remove_node(group, address, true);
@@ -1704,7 +1737,11 @@
     }
     /* Attempt background re-connect if disconnect was not intended locally */
     if (reason != GATT_CONN_TERMINATE_LOCAL_HOST) {
+      leAudioDevice->SetConnectionState(
+          DeviceConnectState::CONNECTING_AUTOCONNECT);
       BTA_GATTC_Open(gatt_if_, address, false, false);
+    } else {
+      leAudioDevice->SetConnectionState(DeviceConnectState::DISCONNECTED);
     }
   }
 
@@ -1782,6 +1819,11 @@
       return;
     }
 
+    if (!leAudioDevice->encrypted_) {
+      LOG_DEBUG("Wait for device to be encrypted");
+      return;
+    }
+
     if (!leAudioDevice->known_service_handles_)
       BTA_GATTC_ServiceSearchRequest(
           leAudioDevice->conn_id_,
@@ -2223,6 +2265,8 @@
        * codec configuration */
       group->SetPendingConfiguration();
       groupStateMachine_->StopStream(group);
+      stream_setup_start_timestamp_ =
+          bluetooth::common::time_get_os_boottime_us();
       return;
     }
 
@@ -2230,6 +2274,9 @@
       LOG_WARN("Could not add device %s to the group %d streaming. ",
                leAudioDevice->address_.ToString().c_str(), group->group_id_);
       scheduleAttachDeviceToTheStream(leAudioDevice->address_);
+    } else {
+      stream_setup_start_timestamp_ =
+          bluetooth::common::time_get_os_boottime_us();
     }
   }
 
@@ -2258,22 +2305,29 @@
   }
 
   void connectionReady(LeAudioDevice* leAudioDevice) {
+    LOG_DEBUG("%s,  %s", leAudioDevice->address_.ToString().c_str(),
+              bluetooth::common::ToString(leAudioDevice->GetConnectionState())
+                  .c_str());
     callbacks_->OnConnectionState(ConnectionState::CONNECTED,
                                   leAudioDevice->address_);
 
+    if (leAudioDevice->GetConnectionState() ==
+            DeviceConnectState::CONNECTED_BY_USER_GETTING_READY &&
+        (leAudioDevice->autoconnect_flag_ == false)) {
+      btif_storage_set_leaudio_autoconnect(leAudioDevice->address_, true);
+      leAudioDevice->autoconnect_flag_ = true;
+    }
+
+    leAudioDevice->SetConnectionState(DeviceConnectState::CONNECTED);
+    le_audio::MetricsCollector::Get()->OnConnectionStateChanged(
+        leAudioDevice->group_id_, leAudioDevice->address_,
+        ConnectionState::CONNECTED, le_audio::ConnectionStatus::SUCCESS);
+
     if (leAudioDevice->group_id_ != bluetooth::groups::kGroupUnknown) {
       LeAudioDeviceGroup* group = aseGroups_.FindById(leAudioDevice->group_id_);
       UpdateContextAndLocations(group, leAudioDevice);
       AttachToStreamingGroupIfNeeded(leAudioDevice);
     }
-
-    if (leAudioDevice->first_connection_) {
-      btif_storage_set_leaudio_autoconnect(leAudioDevice->address_, true);
-      leAudioDevice->first_connection_ = false;
-    }
-    le_audio::MetricsCollector::Get()->OnConnectionStateChanged(
-        leAudioDevice->group_id_, leAudioDevice->address_,
-        ConnectionState::CONNECTED, le_audio::ConnectionStatus::SUCCESS);
   }
 
   bool IsAseAcceptingAudioData(struct ase* ase) {
@@ -2838,16 +2892,16 @@
     std::stringstream stream;
     if (print_audio_state) {
       if (sender) {
-        stream << "   audio sender state: " << audio_sender_state_ << "\n";
+        stream << "\taudio sender state: " << audio_sender_state_ << "\n";
       } else {
-        stream << "   audio receiver state: " << audio_receiver_state_ << "\n";
+        stream << "\taudio receiver state: " << audio_receiver_state_ << "\n";
       }
     }
 
-    stream << "   num_channels: " << +conf->num_channels << "\n"
-           << "   sample rate: " << +conf->sample_rate << "\n"
-           << "   bits pers sample: " << +conf->bits_per_sample << "\n"
-           << "   data_interval_us: " << +conf->data_interval_us << "\n";
+    stream << "\tsample rate: " << +conf->sample_rate
+           << ",\tchan: " << +conf->num_channels
+           << ",\tbits: " << +conf->bits_per_sample
+           << ",\tdata_interval_us: " << +conf->data_interval_us << "\n";
 
     dprintf(fd, "%s", stream.str().c_str());
   }
@@ -2880,17 +2934,27 @@
 
   void Dump(int fd) {
     dprintf(fd, "  Active group: %d\n", active_group_id_);
-    dprintf(fd, "    configuration content type: 0x%08hx\n",
+    dprintf(fd, "    configuration: %s  (0x%08hx)\n",
+            bluetooth::common::ToString(configuration_context_type_).c_str(),
             configuration_context_type_);
+    dprintf(fd, "    metadata context type mask: ");
+
+    for (auto ctx : le_audio::types::kLeAudioContextAllTypesArray) {
+      if (static_cast<uint16_t>(ctx) & metadata_context_types_.to_ulong()) {
+        dprintf(fd, "%s, ", bluetooth::common::ToString(ctx).c_str());
+      }
+    }
+    dprintf(fd, "\n");
     dprintf(fd, "    TBS state: %s\n", in_call_ ? " In call" : "No calls");
-    dprintf(
-        fd, "    stream setup time if started: %d ms\n",
-        (int)((stream_setup_end_timestamp_ - stream_setup_start_timestamp_) /
-              1000));
+    dprintf(fd, "    Start time: ");
+    for (auto t : stream_start_history_queue_) {
+      dprintf(fd, ", %d ms", static_cast<int>(t));
+    }
+    dprintf(fd, "\n");
     printCurrentStreamConfiguration(fd);
     dprintf(fd, "  ----------------\n ");
     dprintf(fd, "  LE Audio Groups:\n");
-    aseGroups_.Dump(fd);
+    aseGroups_.Dump(fd, active_group_id_);
     dprintf(fd, "\n  Not grouped devices:\n");
     leAudioDevices_.Dump(fd, bluetooth::groups::kGroupUnknown);
   }
@@ -3827,6 +3891,23 @@
     }
   }
 
+  void take_stream_time(void) {
+    if (stream_setup_start_timestamp_ == 0) {
+      return;
+    }
+
+    if (stream_start_history_queue_.size() == 10) {
+      stream_start_history_queue_.pop_back();
+    }
+
+    stream_setup_end_timestamp_ = bluetooth::common::time_get_os_boottime_us();
+    stream_start_history_queue_.emplace_front(
+        (stream_setup_end_timestamp_ - stream_setup_start_timestamp_) / 1000);
+
+    stream_setup_end_timestamp_ = 0;
+    stream_setup_start_timestamp_ = 0;
+  }
+
   void StatusReportCb(int group_id, GroupStreamStatus status) {
     LOG_INFO("status: %d , audio_sender_state %s, audio_receiver_state %s",
              static_cast<int>(status),
@@ -3845,8 +3926,8 @@
         if (audio_receiver_state_ == AudioState::READY_TO_START)
           StartReceivingAudio(group_id);
 
-        stream_setup_end_timestamp_ =
-            bluetooth::common::time_get_os_boottime_us();
+        take_stream_time();
+
         le_audio::MetricsCollector::Get()->OnStreamStarted(
             active_group_id_, configuration_context_type_);
         break;
@@ -3884,8 +3965,6 @@
          */
         FALLTHROUGH;
       case GroupStreamStatus::IDLE: {
-        stream_setup_end_timestamp_ = 0;
-        stream_setup_start_timestamp_ = 0;
         if (group && group->IsPendingConfiguration()) {
           SuspendedForReconfiguration();
           auto adjusted_metedata_context_type =
@@ -3898,6 +3977,8 @@
             return;
           }
         }
+        stream_setup_end_timestamp_ = 0;
+        stream_setup_start_timestamp_ = 0;
         CancelStreamingRequest();
         if (group) {
           NotifyUpperLayerGroupTurnedIdleDuringCall(group->group_id_);
@@ -3933,6 +4014,7 @@
   AudioContexts metadata_context_types_;
   uint64_t stream_setup_start_timestamp_;
   uint64_t stream_setup_end_timestamp_;
+  std::deque<uint64_t> stream_start_history_queue_;
 
   /* Microphone (s) */
   AudioState audio_receiver_state_;
diff --git a/system/bta/le_audio/devices.cc b/system/bta/le_audio/devices.cc
index 76c34e2..ca423ed 100644
--- a/system/bta/le_audio/devices.cc
+++ b/system/bta/le_audio/devices.cc
@@ -54,6 +54,42 @@
 using le_audio::types::LeAudioLc3Config;
 
 namespace le_audio {
+std::ostream& operator<<(std::ostream& os, const DeviceConnectState& state) {
+  const char* char_value_ = "UNKNOWN";
+
+  switch (state) {
+    case DeviceConnectState::CONNECTED:
+      char_value_ = "CONNECTED";
+      break;
+    case DeviceConnectState::DISCONNECTED:
+      char_value_ = "DISCONNECTED";
+      break;
+    case DeviceConnectState::REMOVING:
+      char_value_ = "REMOVING";
+      break;
+    case DeviceConnectState::DISCONNECTING:
+      char_value_ = "DISCONNECTING";
+      break;
+    case DeviceConnectState::CONNECTING_BY_USER:
+      char_value_ = "CONNECTING_BY_USER";
+      break;
+    case DeviceConnectState::CONNECTED_BY_USER_GETTING_READY:
+      char_value_ = "CONNECTED_BY_USER_GETTING_READY";
+      break;
+    case DeviceConnectState::CONNECTING_AUTOCONNECT:
+      char_value_ = "CONNECTING_AUTOCONNECT";
+      break;
+    case DeviceConnectState::CONNECTED_AUTOCONNECT_GETTING_READY:
+      char_value_ = "CONNECTED_AUTOCONNECT_GETTING_READY";
+      break;
+  }
+
+  os << char_value_ << " ("
+     << "0x" << std::setfill('0') << std::setw(2) << static_cast<int>(state)
+     << ")";
+  return os;
+}
+
 /* LeAudioDeviceGroup Class methods implementation */
 void LeAudioDeviceGroup::AddNode(
     const std::shared_ptr<LeAudioDevice>& leAudioDevice) {
@@ -973,7 +1009,11 @@
   LOG_INFO("device: %s", leAudioDevice->address_.ToString().c_str());
 
   struct ase* ase = leAudioDevice->GetFirstActiveAse();
-  ASSERT_LOG(ase, " Shouldn't be called without an active ASE");
+  if (!ase) {
+    LOG_ERROR(" Device %s shouldn't be called without an active ASE",
+              leAudioDevice->address_.ToString().c_str());
+    return false;
+  }
 
   for (; ase != nullptr; ase = leAudioDevice->GetNextActiveAse(ase)) {
     uint8_t cis_id = kInvalidCisId;
@@ -1536,8 +1576,16 @@
         continue;
       }
 
-      /* For the moment, we configure only connected devices. */
-      if (device->conn_id_ == GATT_INVALID_CONN_ID) continue;
+      /* For the moment, we configure only connected devices and when it is
+       * ready to stream i.e. All ASEs are discovered and device is reported as
+       * connected
+       */
+      if (device->GetConnectionState() != DeviceConnectState::CONNECTED) {
+        LOG_WARN(
+            "Device %s, in the state %s", device->address_.ToString().c_str(),
+            bluetooth::common::ToString(device->GetConnectionState()).c_str());
+        continue;
+      }
 
       if (!device->ConfigureAses(ent, context_type, &active_ase_num,
                                  group_snk_audio_locations,
@@ -1859,67 +1907,72 @@
 }
 
 LeAudioDeviceGroup::~LeAudioDeviceGroup(void) { this->Cleanup(); }
-void LeAudioDeviceGroup::Dump(int fd) {
+void LeAudioDeviceGroup::Dump(int fd, int active_group_id) {
+  bool is_active = (group_id_ == active_group_id);
   std::stringstream stream;
   auto* active_conf = GetActiveConfiguration();
+  uint32_t context_type_mask = GetActiveContexts().to_ulong();
 
-  stream << "    == Group id: " << group_id_ << " == \n"
-         << "      state: " << GetState() << "\n"
-         << "      target state: " << GetTargetState() << "\n"
-         << "      cig state: " << cig_state_ << "\n"
-         << "      number of devices: " << Size() << "\n"
-         << "      number of connected devices: " << NumOfConnected() << "\n"
-         << "      active context types: "
-         << loghex(GetActiveContexts().to_ulong()) << "\n"
-         << "      current context type: "
-         << static_cast<int>(GetCurrentContextType()) << "\n"
-         << "      active stream configuration name: "
+  stream << "\n    == Group id: " << group_id_
+         << " == " << (is_active ? ",\tActive\n" : ",\tInactive\n")
+         << "      state: " << GetState()
+         << ",\ttarget state: " << GetTargetState()
+         << ",\tcig state: " << cig_state_ << "\n"
+         << "      active context type mask: " << context_type_mask;
+
+  for (auto ctx : types::kLeAudioContextAllTypesArray) {
+    if (static_cast<uint16_t>(ctx) & static_cast<uint16_t>(context_type_mask)) {
+      stream << ", " << bluetooth::common::ToString(ctx).c_str();
+    }
+  }
+
+  stream << ",\n      current context type: "
+         << bluetooth::common::ToString(GetCurrentContextType()).c_str() << "\n"
+         << "      active configuration name: "
          << (active_conf ? active_conf->name : " not set") << "\n"
-         << "    Last used stream configuration: \n"
-         << "      pending_configuration: " << stream_conf.pending_configuration
+         << "      stream configuration: "
+         << (stream_conf.conf != nullptr ? stream_conf.conf->name : " unknown ")
          << "\n"
-         << "      codec id : " << +(stream_conf.id.coding_format) << "\n"
-         << "      name: "
-         << (stream_conf.conf != nullptr ? stream_conf.conf->name : " null ")
+         << "      codec id: " << +(stream_conf.id.coding_format)
+         << ",\tpending_configuration: " << stream_conf.pending_configuration
          << "\n"
-         << "      number of sinks in the configuration "
-         << stream_conf.sink_num_of_devices << "\n"
-         << "      number of sink_streams connected: "
-         << stream_conf.sink_streams.size() << "\n"
-         << "      number of sources in the configuration "
-         << stream_conf.source_num_of_devices << "\n"
-         << "      number of source_streams connected: "
-         << stream_conf.source_streams.size() << "\n"
+         << "      num of devices(connected): " << Size() << "("
+         << NumOfConnected() << ")\n"
+         << ",     num of sinks(connected): " << stream_conf.sink_num_of_devices
+         << "(" << stream_conf.sink_streams.size() << ")\n"
+         << "      num of sources(connected): "
+         << stream_conf.source_num_of_devices << "("
+         << stream_conf.source_streams.size() << ")\n"
          << "      allocated CISes: " << static_cast<int>(cises_.size());
 
   if (cises_.size() > 0) {
-    stream << "\n\t === CISes === ";
+    stream << "\n\t == CISes == ";
     for (auto cis : cises_) {
       stream << "\n\t cis id: " << static_cast<int>(cis.id)
-             << ", type: " << static_cast<int>(cis.type)
-             << ", conn_handle: " << static_cast<int>(cis.conn_handle)
-             << ", addr: " << cis.addr;
+             << ",\ttype: " << static_cast<int>(cis.type)
+             << ",\tconn_handle: " << static_cast<int>(cis.conn_handle)
+             << ",\taddr: " << cis.addr;
     }
+    stream << "\n\t ====";
   }
 
   if (GetFirstActiveDevice() != nullptr) {
     uint32_t sink_delay;
-    stream << "\n      presentation_delay for sink (speaker): ";
-    if (GetPresentationDelay(&sink_delay, le_audio::types::kLeAudioDirectionSink)) {
-      stream << sink_delay << " us";
+    if (GetPresentationDelay(&sink_delay,
+                             le_audio::types::kLeAudioDirectionSink)) {
+      stream << "\n      presentation_delay for sink (speaker): " << sink_delay
+             << " us";
     }
-    stream << "\n      presentation_delay for source (microphone): ";
+
     uint32_t source_delay;
-    if (GetPresentationDelay(&source_delay, le_audio::types::kLeAudioDirectionSource)) {
-      stream << source_delay << " us";
+    if (GetPresentationDelay(&source_delay,
+                             le_audio::types::kLeAudioDirectionSource)) {
+      stream << "\n      presentation_delay for source (microphone): "
+             << source_delay << " us";
     }
-    stream << "\n";
-  } else {
-    stream << "\n      presentation_delay for sink (speaker):\n"
-           << "      presentation_delay for source (microphone): \n";
   }
 
-  stream << "      === devices: ===";
+  stream << "\n      == devices: ==";
 
   dprintf(fd, "%s", stream.str().c_str());
 
@@ -1929,6 +1982,17 @@
 }
 
 /* LeAudioDevice Class methods implementation */
+void LeAudioDevice::SetConnectionState(DeviceConnectState state) {
+  LOG_DEBUG(" %s --> %s",
+            bluetooth::common::ToString(connection_state_).c_str(),
+            bluetooth::common::ToString(state).c_str());
+  connection_state_ = state;
+}
+
+DeviceConnectState LeAudioDevice::GetConnectionState(void) {
+  return connection_state_;
+}
+
 void LeAudioDevice::ClearPACs(void) {
   snk_pacs_.clear();
   src_pacs_.clear();
@@ -2341,32 +2405,29 @@
 }
 
 void LeAudioDevice::Dump(int fd) {
+  uint16_t acl_handle = BTM_GetHCIConnHandle(address_, BT_TRANSPORT_LE);
+
   std::stringstream stream;
   stream << std::boolalpha;
-  stream << "\n\taddress: " << address_
-         << (conn_id_ == GATT_INVALID_CONN_ID ? "\n\t  Not connected "
-                                              : "\n\t  Connected conn_id =")
+  stream << "\n\taddress: " << address_ << ": " << connection_state_ << ": "
          << (conn_id_ == GATT_INVALID_CONN_ID ? "" : std::to_string(conn_id_))
-         << "\n\t  set member: " << csis_member_
-         << "\n\t  known_service_handles_: " << known_service_handles_
-         << "\n\t  notify_connected_after_read_: "
-         << notify_connected_after_read_
-         << "\n\t  removing_device_: " << removing_device_
-         << "\n\t  first_connection_: " << first_connection_
-         << "\n\t  encrypted_: " << encrypted_
-         << "\n\t  connecting_actively_: " << connecting_actively_
-         << "\n\t  number of ases_: " << static_cast<int>(ases_.size());
+         << ", acl_handle: " << std::to_string(acl_handle) << ",\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  == ASE == ";
+    stream << "\n\t  == ASEs == ";
     for (auto& ase : ases_) {
       stream << "\n\t  id: " << static_cast<int>(ase.id)
-             << ", active: " << ase.active << ", direction: "
+             << ",\tactive: " << ase.active << ", dir: "
              << (ase.direction == types::kLeAudioDirectionSink ? "sink"
                                                                : "source")
-             << ", allocated cis id: " << static_cast<int>(ase.cis_id);
+             << ",\tcis_id: " << static_cast<int>(ase.cis_id) << ",\tstate: "
+             << bluetooth::common::ToString(ase.data_path_state);
     }
   }
+  stream << "\n\t  ====";
 
   dprintf(fd, "%s", stream.str().c_str());
 }
@@ -2504,9 +2565,9 @@
   groups_.clear();
 }
 
-void LeAudioDeviceGroups::Dump(int fd) {
+void LeAudioDeviceGroups::Dump(int fd, int active_group_id) {
   for (auto& g : groups_) {
-    g->Dump(fd);
+    g->Dump(fd, active_group_id);
   }
 }
 
@@ -2534,7 +2595,7 @@
 }
 
 /* LeAudioDevices Class methods implementation */
-void LeAudioDevices::Add(const RawAddress& address, bool first_connection,
+void LeAudioDevices::Add(const RawAddress& address, DeviceConnectState state,
                          int group_id) {
   auto device = FindByAddress(address);
   if (device != nullptr) {
@@ -2544,7 +2605,7 @@
   }
 
   leAudioDevices_.emplace_back(
-      std::make_shared<LeAudioDevice>(address, first_connection, group_id));
+      std::make_shared<LeAudioDevice>(address, state, group_id));
 }
 
 void LeAudioDevices::Remove(const RawAddress& address) {
diff --git a/system/bta/le_audio/devices.h b/system/bta/le_audio/devices.h
index 90fa87e..1bdd6fc 100644
--- a/system/bta/le_audio/devices.h
+++ b/system/bta/le_audio/devices.h
@@ -34,6 +34,31 @@
 #include "raw_address.h"
 
 namespace le_audio {
+
+/* Enums */
+enum class DeviceConnectState : uint8_t {
+  /* Initial state */
+  DISCONNECTED,
+  /* When ACL connected, encrypted, CCC registered and initial characteristics
+     read is completed */
+  CONNECTED,
+  /* Used when device is unbonding (RemoveDevice() API is called) */
+  REMOVING,
+  /* Disconnecting */
+  DISCONNECTING,
+  /* 2 states below are used when user creates connection. Connect API is
+     called. */
+  CONNECTING_BY_USER,
+  /* Always used after CONNECTING_BY_USER */
+  CONNECTED_BY_USER_GETTING_READY,
+  /* 2 states are used when autoconnect was used for the connection.*/
+  CONNECTING_AUTOCONNECT,
+  /* Always used after CONNECTING_AUTOCONNECT */
+  CONNECTED_AUTOCONNECT_GETTING_READY,
+};
+
+std::ostream& operator<<(std::ostream& os, const DeviceConnectState& state);
+
 /* Class definitions */
 
 /* LeAudioDevice class represents GATT server device with ASCS, PAC services as
@@ -50,15 +75,11 @@
  public:
   RawAddress address_;
 
+  DeviceConnectState connection_state_;
   bool known_service_handles_;
   bool notify_connected_after_read_;
-  bool removing_device_;
-
-  /* we are making active attempt to connect to this device, 'direct connect'.
-   * This is true only during initial phase of first connection. */
-  bool first_connection_;
-  bool connecting_actively_;
   bool closing_stream_for_disconnection_;
+  bool autoconnect_flag_;
   uint16_t conn_id_;
   uint16_t mtu_;
   bool encrypted_;
@@ -84,15 +105,14 @@
   alarm_t* link_quality_timer;
   uint16_t link_quality_timer_data;
 
-  LeAudioDevice(const RawAddress& address_, bool first_connection,
+  LeAudioDevice(const RawAddress& address_, DeviceConnectState state,
                 int group_id = bluetooth::groups::kGroupUnknown)
       : address_(address_),
+        connection_state_(state),
         known_service_handles_(false),
         notify_connected_after_read_(false),
-        removing_device_(false),
-        first_connection_(first_connection),
-        connecting_actively_(first_connection),
         closing_stream_for_disconnection_(false),
+        autoconnect_flag_(false),
         conn_id_(GATT_INVALID_CONN_ID),
         mtu_(0),
         encrypted_(false),
@@ -102,6 +122,8 @@
         link_quality_timer(nullptr) {}
   ~LeAudioDevice(void);
 
+  void SetConnectionState(DeviceConnectState state);
+  DeviceConnectState GetConnectionState(void);
   void ClearPACs(void);
   void RegisterPACs(std::vector<struct types::acs_ac_record>* apr_db,
                     std::vector<struct types::acs_ac_record>* apr);
@@ -171,7 +193,7 @@
  */
 class LeAudioDevices {
  public:
-  void Add(const RawAddress& address, bool first_connection,
+  void Add(const RawAddress& address, le_audio::DeviceConnectState state,
            int group_id = bluetooth::groups::kGroupUnknown);
   void Remove(const RawAddress& address);
   LeAudioDevice* FindByAddress(const RawAddress& address);
@@ -335,7 +357,7 @@
 
   bool IsInTransition(void);
   bool IsReleasing(void);
-  void Dump(int fd);
+  void Dump(int fd, int active_group_id);
 
  private:
   uint32_t transport_latency_mtos_us_;
@@ -381,7 +403,7 @@
   size_t Size();
   bool IsAnyInTransition();
   void Cleanup(void);
-  void Dump(int fd);
+  void Dump(int fd, int active_group_id);
 
  private:
   std::vector<std::unique_ptr<LeAudioDeviceGroup>> groups_;
diff --git a/system/bta/le_audio/devices_test.cc b/system/bta/le_audio/devices_test.cc
index 2077e37..75b6559 100644
--- a/system/bta/le_audio/devices_test.cc
+++ b/system/bta/le_audio/devices_test.cc
@@ -35,6 +35,7 @@
 namespace internal {
 namespace {
 
+using ::le_audio::DeviceConnectState;
 using ::le_audio::LeAudioDevice;
 using ::le_audio::LeAudioDeviceGroup;
 using ::le_audio::LeAudioDevices;
@@ -72,23 +73,23 @@
 TEST_F(LeAudioDevicesTest, test_add) {
   RawAddress test_address_0 = GetTestAddress(0);
   ASSERT_EQ((size_t)0, devices_->Size());
-  devices_->Add(test_address_0, true);
+  devices_->Add(test_address_0, DeviceConnectState::CONNECTING_BY_USER);
   ASSERT_EQ((size_t)1, devices_->Size());
-  devices_->Add(GetTestAddress(1), true, 1);
+  devices_->Add(GetTestAddress(1), DeviceConnectState::CONNECTING_BY_USER, 1);
   ASSERT_EQ((size_t)2, devices_->Size());
-  devices_->Add(test_address_0, true);
+  devices_->Add(test_address_0, DeviceConnectState::CONNECTING_BY_USER);
   ASSERT_EQ((size_t)2, devices_->Size());
-  devices_->Add(GetTestAddress(1), true, 2);
+  devices_->Add(GetTestAddress(1), DeviceConnectState::CONNECTING_BY_USER, 2);
   ASSERT_EQ((size_t)2, devices_->Size());
 }
 
 TEST_F(LeAudioDevicesTest, test_remove) {
   RawAddress test_address_0 = GetTestAddress(0);
-  devices_->Add(test_address_0, true);
+  devices_->Add(test_address_0, DeviceConnectState::CONNECTING_BY_USER);
   RawAddress test_address_1 = GetTestAddress(1);
-  devices_->Add(test_address_1, true);
+  devices_->Add(test_address_1, DeviceConnectState::CONNECTING_BY_USER);
   RawAddress test_address_2 = GetTestAddress(2);
-  devices_->Add(test_address_2, true);
+  devices_->Add(test_address_2, DeviceConnectState::CONNECTING_BY_USER);
   ASSERT_EQ((size_t)3, devices_->Size());
   devices_->Remove(test_address_0);
   ASSERT_EQ((size_t)2, devices_->Size());
@@ -100,11 +101,11 @@
 
 TEST_F(LeAudioDevicesTest, test_find_by_address_success) {
   RawAddress test_address_0 = GetTestAddress(0);
-  devices_->Add(test_address_0, true);
+  devices_->Add(test_address_0, DeviceConnectState::CONNECTING_BY_USER);
   RawAddress test_address_1 = GetTestAddress(1);
-  devices_->Add(test_address_1, false);
+  devices_->Add(test_address_1, DeviceConnectState::DISCONNECTED);
   RawAddress test_address_2 = GetTestAddress(2);
-  devices_->Add(test_address_2, true);
+  devices_->Add(test_address_2, DeviceConnectState::CONNECTING_BY_USER);
   LeAudioDevice* device = devices_->FindByAddress(test_address_1);
   ASSERT_NE(nullptr, device);
   ASSERT_EQ(test_address_1, device->address_);
@@ -112,20 +113,20 @@
 
 TEST_F(LeAudioDevicesTest, test_find_by_address_failed) {
   RawAddress test_address_0 = GetTestAddress(0);
-  devices_->Add(test_address_0, true);
+  devices_->Add(test_address_0, DeviceConnectState::CONNECTING_BY_USER);
   RawAddress test_address_2 = GetTestAddress(2);
-  devices_->Add(test_address_2, true);
+  devices_->Add(test_address_2, DeviceConnectState::CONNECTING_BY_USER);
   LeAudioDevice* device = devices_->FindByAddress(GetTestAddress(1));
   ASSERT_EQ(nullptr, device);
 }
 
 TEST_F(LeAudioDevicesTest, test_get_by_address_success) {
   RawAddress test_address_0 = GetTestAddress(0);
-  devices_->Add(test_address_0, true);
+  devices_->Add(test_address_0, DeviceConnectState::CONNECTING_BY_USER);
   RawAddress test_address_1 = GetTestAddress(1);
-  devices_->Add(test_address_1, false);
+  devices_->Add(test_address_1, DeviceConnectState::DISCONNECTED);
   RawAddress test_address_2 = GetTestAddress(2);
-  devices_->Add(test_address_2, true);
+  devices_->Add(test_address_2, DeviceConnectState::CONNECTING_BY_USER);
   std::shared_ptr<LeAudioDevice> device =
       devices_->GetByAddress(test_address_1);
   ASSERT_NE(nullptr, device);
@@ -134,28 +135,28 @@
 
 TEST_F(LeAudioDevicesTest, test_get_by_address_failed) {
   RawAddress test_address_0 = GetTestAddress(0);
-  devices_->Add(test_address_0, true);
+  devices_->Add(test_address_0, DeviceConnectState::CONNECTING_BY_USER);
   RawAddress test_address_2 = GetTestAddress(2);
-  devices_->Add(test_address_2, true);
+  devices_->Add(test_address_2, DeviceConnectState::CONNECTING_BY_USER);
   std::shared_ptr<LeAudioDevice> device =
       devices_->GetByAddress(GetTestAddress(1));
   ASSERT_EQ(nullptr, device);
 }
 
 TEST_F(LeAudioDevicesTest, test_find_by_conn_id_success) {
-  devices_->Add(GetTestAddress(1), true);
+  devices_->Add(GetTestAddress(1), DeviceConnectState::CONNECTING_BY_USER);
   RawAddress test_address_0 = GetTestAddress(0);
-  devices_->Add(test_address_0, true);
-  devices_->Add(GetTestAddress(4), true);
+  devices_->Add(test_address_0, DeviceConnectState::CONNECTING_BY_USER);
+  devices_->Add(GetTestAddress(4), DeviceConnectState::CONNECTING_BY_USER);
   LeAudioDevice* device = devices_->FindByAddress(test_address_0);
   device->conn_id_ = 0x0005;
   ASSERT_EQ(device, devices_->FindByConnId(0x0005));
 }
 
 TEST_F(LeAudioDevicesTest, test_find_by_conn_id_failed) {
-  devices_->Add(GetTestAddress(1), true);
-  devices_->Add(GetTestAddress(0), true);
-  devices_->Add(GetTestAddress(4), true);
+  devices_->Add(GetTestAddress(1), DeviceConnectState::CONNECTING_BY_USER);
+  devices_->Add(GetTestAddress(0), DeviceConnectState::CONNECTING_BY_USER);
+  devices_->Add(GetTestAddress(4), DeviceConnectState::CONNECTING_BY_USER);
   ASSERT_EQ(nullptr, devices_->FindByConnId(0x0006));
 }
 
@@ -412,8 +413,8 @@
                                int snk_ase_num_cached = 0,
                                int src_ase_num_cached = 0) {
     int index = group_->Size() + 1;
-    auto device =
-        (std::make_shared<LeAudioDevice>(GetTestAddress(index), false));
+    auto device = (std::make_shared<LeAudioDevice>(
+        GetTestAddress(index), DeviceConnectState::DISCONNECTED));
     devices_.push_back(device);
     group_->AddNode(device);
 
@@ -449,6 +450,7 @@
         ::le_audio::codec_spec_conf::kLeAudioLocationFrontRight;
 
     device->conn_id_ = index;
+    device->SetConnectionState(DeviceConnectState::CONNECTED);
     return device.get();
   }
 
diff --git a/system/bta/le_audio/le_audio_types.cc b/system/bta/le_audio/le_audio_types.cc
index f6d03e1..447e131 100644
--- a/system/bta/le_audio/le_audio_types.cc
+++ b/system/bta/le_audio/le_audio_types.cc
@@ -578,6 +578,17 @@
 }
 
 namespace types {
+std::ostream& operator<<(std::ostream& os,
+                         const AudioStreamDataPathState& state) {
+  static const char* char_value_[6] = {
+      "IDLE",        "CIS_DISCONNECTING", "CIS_ASSIGNED",
+      "CIS_PENDING", "CIS_ESTABLISHED",   "DATA_PATH_ESTABLISHED"};
+
+  os << char_value_[static_cast<uint8_t>(state)] << " ("
+     << "0x" << std::setfill('0') << std::setw(2) << static_cast<int>(state)
+     << ")";
+  return os;
+}
 std::ostream& operator<<(std::ostream& os, const types::CigState& state) {
   static const char* char_value_[5] = {"NONE", "CREATING", "CREATED",
                                        "REMOVING", "RECOVERING"};
diff --git a/system/bta/le_audio/le_audio_types.h b/system/bta/le_audio/le_audio_types.h
index 776ef52..4f915ab 100644
--- a/system/bta/le_audio/le_audio_types.h
+++ b/system/bta/le_audio/le_audio_types.h
@@ -310,7 +310,6 @@
 constexpr uint16_t kMaxTransportLatencyMin = 0x0005;
 constexpr uint16_t kMaxTransportLatencyMax = 0x0FA0;
 
-/* Enums */
 enum class CigState : uint8_t { NONE, CREATING, CREATED, REMOVING, RECOVERING };
 
 /* ASE states according to BAP defined state machine states */
@@ -600,6 +599,8 @@
 std::ostream& operator<<(std::ostream& os, const CigState& state);
 std::ostream& operator<<(std::ostream& os, const LeAudioLc3Config& config);
 std::ostream& operator<<(std::ostream& os, const LeAudioContextType& context);
+std::ostream& operator<<(std::ostream& os,
+                         const AudioStreamDataPathState& state);
 }  // namespace types
 
 namespace set_configurations {
diff --git a/system/bta/le_audio/state_machine.cc b/system/bta/le_audio/state_machine.cc
index 20ae741..f74fe8c 100644
--- a/system/bta/le_audio/state_machine.cc
+++ b/system/bta/le_audio/state_machine.cc
@@ -919,6 +919,10 @@
   }
 
   void SetTargetState(LeAudioDeviceGroup* group, AseState state) {
+    LOG_DEBUG("Watchdog watch started for group=%d transition from %s to %s",
+              group->group_id_, ToString(group->GetTargetState()).c_str(),
+              ToString(state).c_str());
+
     group->SetTargetState(state);
 
     /* Group should tie in time to get requested status */
@@ -1491,8 +1495,9 @@
     ase = leAudioDevice->GetFirstActiveAse();
     ASSERT_LOG(ase, "shouldn't be called without an active ASE");
     for (; ase != nullptr; ase = leAudioDevice->GetNextActiveAse(ase)) {
-      LOG_INFO(" Configure ase_id %d, cis_id %d, ase state %s", ase->id,
-               ase->cis_id, ToString(ase->state).c_str());
+      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());
       conf.ase_id = ase->id;
       conf.target_latency = ase->target_latency;
       conf.target_phy = group->GetTargetPhy(ase->direction);
@@ -1897,6 +1902,9 @@
     ase = leAudioDevice->GetFirstActiveAse();
     LOG_ASSERT(ase) << __func__ << " shouldn't be called without an active ASE";
     do {
+      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());
       conf.ase_id = ase->id;
       conf.metadata = ase->metadata;
       confs.push_back(conf);
@@ -1915,6 +1923,9 @@
 
     std::vector<uint8_t> ids;
     do {
+      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());
       ids.push_back(ase->id);
     } while ((ase = leAudioDevice->GetNextActiveAse(ase)));
 
@@ -1932,6 +1943,9 @@
 
     std::vector<uint8_t> ids;
     do {
+      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());
       ids.push_back(ase->id);
     } while ((ase = leAudioDevice->GetNextActiveAse(ase)));
 
@@ -1949,6 +1963,10 @@
 
     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());
+
       /* TODO: Configure first ASE qos according to context type */
       struct le_audio::client_parser::ascs::ctp_qos_conf conf;
       conf.ase_id = ase->id;
@@ -2006,6 +2024,10 @@
       /* 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());
+
         struct le_audio::client_parser::ascs::ctp_update_metadata conf;
 
         conf.ase_id = ase->id;
diff --git a/system/bta/le_audio/state_machine_test.cc b/system/bta/le_audio/state_machine_test.cc
index 56cb8cc..e6160cd 100644
--- a/system/bta/le_audio/state_machine_test.cc
+++ b/system/bta/le_audio/state_machine_test.cc
@@ -35,6 +35,7 @@
 #include "mock_iso_manager.h"
 #include "types/bt_transport.h"
 
+using ::le_audio::DeviceConnectState;
 using ::testing::_;
 using ::testing::AnyNumber;
 using ::testing::AtLeast;
@@ -443,13 +444,13 @@
     ::le_audio::AudioSetConfigurationProvider::Cleanup();
   }
 
-  std::shared_ptr<LeAudioDevice> PrepareConnectedDevice(uint8_t id,
-                                                        bool first_connection,
-                                                        uint8_t num_ase_snk,
-                                                        uint8_t num_ase_src) {
-    auto leAudioDevice =
-        std::make_shared<LeAudioDevice>(GetTestAddress(id), first_connection);
+  std::shared_ptr<LeAudioDevice> PrepareConnectedDevice(
+      uint8_t id, DeviceConnectState initial_connect_state, uint8_t num_ase_snk,
+      uint8_t num_ase_src) {
+    auto leAudioDevice = std::make_shared<LeAudioDevice>(GetTestAddress(id),
+                                                         initial_connect_state);
     leAudioDevice->conn_id_ = id;
+    leAudioDevice->SetConnectionState(DeviceConnectState::CONNECTED);
 
     uint16_t attr_handle = ATTR_HANDLE_ASCS_POOL_START;
     leAudioDevice->snk_audio_locations_hdls_.val_hdl = attr_handle++;
@@ -668,7 +669,8 @@
                                  uint16_t update_context_type,
                                  bool insert_default_pac_records = true) {
     // Prepare fake connected device group
-    bool first_connections = true;
+    DeviceConnectState initial_connect_state =
+        DeviceConnectState::CONNECTING_BY_USER;
     int total_devices = device_cnt;
     le_audio::LeAudioDeviceGroup* group = nullptr;
 
@@ -696,7 +698,7 @@
 
     while (device_cnt) {
       auto leAudioDevice = PrepareConnectedDevice(
-          device_cnt--, first_connections, num_ase_snk, num_ase_src);
+          device_cnt--, initial_connect_state, num_ase_snk, num_ase_src);
 
       if (insert_default_pac_records) {
         uint16_t attr_handle = ATTR_HANDLE_PACS_POOL_START;
diff --git a/system/bta/le_audio/storage_helper_test.cc b/system/bta/le_audio/storage_helper_test.cc
index 5b765b1..fe99f52 100644
--- a/system/bta/le_audio/storage_helper_test.cc
+++ b/system/bta/le_audio/storage_helper_test.cc
@@ -105,7 +105,7 @@
   // clang-format on
 
   RawAddress test_address0 = GetTestAddress(0);
-  LeAudioDevice leAudioDevice(test_address0, false);
+  LeAudioDevice leAudioDevice(test_address0, DeviceConnectState::DISCONNECTED);
   ASSERT_TRUE(DeserializeSinkPacs(&leAudioDevice, validSinkPack));
   std::vector<uint8_t> serialize;
   ASSERT_TRUE(SerializeSinkPacs(&leAudioDevice, serialize));
@@ -189,7 +189,7 @@
   // clang-format on
 
   RawAddress test_address0 = GetTestAddress(0);
-  LeAudioDevice leAudioDevice(test_address0, false);
+  LeAudioDevice leAudioDevice(test_address0, DeviceConnectState::DISCONNECTED);
   ASSERT_TRUE(DeserializeSourcePacs(&leAudioDevice, validSourcePack));
   std::vector<uint8_t> serialize;
   ASSERT_TRUE(SerializeSourcePacs(&leAudioDevice, serialize));
@@ -252,7 +252,7 @@
   };
   // clang-format on
   RawAddress test_address0 = GetTestAddress(0);
-  LeAudioDevice leAudioDevice(test_address0, false);
+  LeAudioDevice leAudioDevice(test_address0, DeviceConnectState::DISCONNECTED);
   ASSERT_TRUE(DeserializeAses(&leAudioDevice, validAses));
 
   std::vector<uint8_t> serialize;
@@ -310,7 +310,7 @@
   };
   // clang-format on
   RawAddress test_address0 = GetTestAddress(0);
-  LeAudioDevice leAudioDevice(test_address0, false);
+  LeAudioDevice leAudioDevice(test_address0, DeviceConnectState::DISCONNECTED);
   ASSERT_TRUE(DeserializeHandles(&leAudioDevice, validHandles));
   std::vector<uint8_t> serialize;
   ASSERT_TRUE(SerializeHandles(&leAudioDevice, serialize));
diff --git a/system/bta/vc/vc.cc b/system/bta/vc/vc.cc
index b2b85cb..53816f9 100644
--- a/system/bta/vc/vc.cc
+++ b/system/bta/vc/vc.cc
@@ -753,7 +753,7 @@
     devices_control_point_helper(op->devices_, op->opcode_, &(op->arguments_));
   }
 
-  void PrepareVolumeControlOperation(std::vector<RawAddress>& devices,
+  void PrepareVolumeControlOperation(std::vector<RawAddress> devices,
                                      int group_id, bool is_autonomous,
                                      uint8_t opcode,
                                      std::vector<uint8_t>& arguments) {
@@ -764,11 +764,22 @@
         arguments.size());
 
     if (std::find_if(ongoing_operations_.begin(), ongoing_operations_.end(),
-                     [opcode, &arguments](const VolumeOperation& op) {
-                       return (op.opcode_ == opcode) &&
-                              std::equal(op.arguments_.begin(),
-                                         op.arguments_.end(),
-                                         arguments.begin());
+                     [opcode, &devices, &arguments](const VolumeOperation& op) {
+                       if (op.opcode_ != opcode) return false;
+                       if (!std::equal(op.arguments_.begin(),
+                                       op.arguments_.end(), arguments.begin()))
+                         return false;
+                       // Filter out all devices which have the exact operation
+                       // already scheduled
+                       devices.erase(
+                           std::remove_if(devices.begin(), devices.end(),
+                                          [&op](auto d) {
+                                            return find(op.devices_.begin(),
+                                                        op.devices_.end(),
+                                                        d) != op.devices_.end();
+                                          }),
+                           devices.end());
+                       return devices.empty();
                      }) == ongoing_operations_.end()) {
       ongoing_operations_.emplace_back(latest_operation_id_++, group_id,
                                        is_autonomous, opcode, arguments,
diff --git a/system/bta/vc/vc_test.cc b/system/bta/vc/vc_test.cc
index cac1f92..3043689 100644
--- a/system/bta/vc/vc_test.cc
+++ b/system/bta/vc/vc_test.cc
@@ -1087,6 +1087,23 @@
   std::vector<uint8_t> value({0x03, 0x01, 0x02});
   GetNotificationEvent(conn_id_1, test_address_1, 0x0021, value);
   GetNotificationEvent(conn_id_2, test_address_2, 0x0021, value);
+
+  /* Verify exactly one operation with this exact value is queued for each
+   * device */
+  EXPECT_CALL(gatt_queue,
+              WriteCharacteristic(conn_id_1, 0x0024, _, GATT_WRITE, _, _))
+      .Times(1);
+  EXPECT_CALL(gatt_queue,
+              WriteCharacteristic(conn_id_2, 0x0024, _, GATT_WRITE, _, _))
+      .Times(1);
+  VolumeControl::Get()->SetVolume(test_address_1, 20);
+  VolumeControl::Get()->SetVolume(test_address_2, 20);
+  VolumeControl::Get()->SetVolume(test_address_1, 20);
+  VolumeControl::Get()->SetVolume(test_address_2, 20);
+
+  std::vector<uint8_t> value2({20, 0x00, 0x03});
+  GetNotificationEvent(conn_id_1, test_address_1, 0x0021, value2);
+  GetNotificationEvent(conn_id_2, test_address_2, 0x0021, value2);
 }
 
 TEST_F(VolumeControlCsis, test_set_volume_device_not_ready) {
diff --git a/system/btif/Android.bp b/system/btif/Android.bp
index 22d24f3..6299237 100644
--- a/system/btif/Android.bp
+++ b/system/btif/Android.bp
@@ -102,7 +102,6 @@
         "co/bta_gatts_co.cc",
         // HAL layer
         "src/bluetooth.cc",
-        "src/bluetooth_data_migration.cc",
         // BTIF implementation
         "src/btif_a2dp.cc",
         "src/btif_a2dp_control.cc",
diff --git a/system/btif/src/bluetooth.cc b/system/btif/src/bluetooth.cc
index f588993..0e170c8 100644
--- a/system/btif/src/bluetooth.cc
+++ b/system/btif/src/bluetooth.cc
@@ -172,29 +172,17 @@
  *
  ****************************************************************************/
 
-#ifdef OS_ANDROID
-const std::vector<std::string> get_allowed_bt_package_name(void);
-void handle_migration(const std::string& dst,
-                      const std::vector<std::string>& allowed_bt_package_name);
-#endif
-
 static int init(bt_callbacks_t* callbacks, bool start_restricted,
                 bool is_common_criteria_mode, int config_compare_result,
                 const char** init_flags, bool is_atv,
                 const char* user_data_directory) {
+  (void)user_data_directory;
   LOG_INFO(
       "%s: start restricted = %d ; common criteria mode = %d, config compare "
       "result = %d",
       __func__, start_restricted, is_common_criteria_mode,
       config_compare_result);
 
-#ifdef OS_ANDROID
-  if (user_data_directory != nullptr) {
-    handle_migration(std::string(user_data_directory),
-                     get_allowed_bt_package_name());
-  }
-#endif
-
   bluetooth::common::InitFlags::Load(init_flags);
 
   if (interface_ready()) return BT_STATUS_DONE;
diff --git a/system/btif/src/bluetooth_data_migration.cc b/system/btif/src/bluetooth_data_migration.cc
deleted file mode 100644
index 45d0bda..0000000
--- a/system/btif/src/bluetooth_data_migration.cc
+++ /dev/null
@@ -1,126 +0,0 @@
-/******************************************************************************
- *
- *  Copyright 2022 Google LLC
- *
- *  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/logging.h>
-
-#include <filesystem>
-#include <string>
-#include <vector>
-
-namespace fs = std::filesystem;
-
-// The user data should be stored in the subdirectory of |USER_DE_PATH|
-static const std::string USER_DE_PATH = "/data/user_de/0";
-
-// The migration process start only if |MIGRATION_FILE_CHECKER| is found in a
-// previous location
-static const std::string MIGRATION_FILE_CHECKER = "databases/bluetooth_db";
-
-// List of possible package_name for bluetooth to get the data from / to
-static const std::vector<std::string> ALLOWED_BT_PACKAGE_NAME = {
-    "com.android.bluetooth",                  // legacy name
-    "com.android.bluetooth.services",         // Beta users
-    "com.google.android.bluetooth.services",  // Droid fooder users
-};
-
-// Accessor to get the default allowed package list to be used in migration
-// OEM can call their own method with their own allowed list
-const std::vector<std::string> get_allowed_bt_package_name(void) {
-  return ALLOWED_BT_PACKAGE_NAME;
-}
-
-// Check if |dst| is in |base_dir| subdirectory and check the package name in
-// |dst| is a allowed package name in the |pkg_list|
-//
-// Return an empty string if an issue occurred
-// or the package name contained in |dst| on success
-static std::string parse_destination_package_name(
-    const std::string& dst, const std::string& base_dir,
-    const std::vector<std::string>& pkg_list) {
-  const std::size_t found = dst.rfind("/");
-  // |dst| must contain a '/'
-  if (found == std::string::npos) {
-    LOG(ERROR) << "Destination format not valid " << dst;
-    return "";
-  }
-  // |dst| directory is supposed to be in |base_dir|
-  if (found != base_dir.length()) {
-    LOG(ERROR) << "Destination location not allowed: " << dst;
-    return "";
-  }
-  // This check prevent a '/' to be at the end of |dst|
-  if (found >= dst.length() - 1) {
-    LOG(ERROR) << "Destination format not valid " << dst;
-    return "";
-  }
-
-  const std::string dst_package_name = dst.substr(found + 1);  // +1 for '/'
-
-  if (std::find(pkg_list.begin(), pkg_list.end(), dst_package_name) ==
-      pkg_list.end()) {
-    LOG(ERROR) << "Destination package_name not valid: " << dst_package_name
-               << " Created from " << dst;
-    return "";
-  }
-  LOG(INFO) << "Current Bluetooth package name is: " << dst_package_name;
-  return dst_package_name;
-}
-
-// Check for data to migrate from the |allowed_bt_package_name|
-// A migration will be performed if:
-// * |dst| is different than |allowed_bt_package_name|
-// * the following file is found:
-//    |USER_DE_PATH|/|allowed_bt_package_name|/|MIGRATION_FILE_CHECKER|
-//
-// After migration occurred, the |MIGRATION_FILE_CHECKER| is deleted to ensure
-// the migration is only performed once
-void handle_migration(const std::string& dst,
-                      const std::vector<std::string>& allowed_bt_package_name) {
-  const std::string dst_package_name = parse_destination_package_name(
-      dst, USER_DE_PATH, allowed_bt_package_name);
-  if (dst_package_name.empty()) return;
-
-  for (const auto& pkg_name : allowed_bt_package_name) {
-    std::error_code error;
-
-    if (dst_package_name == pkg_name) {
-      LOG(INFO) << "Same location skipped: " << dst_package_name;
-      continue;
-    }
-    const fs::path dst_path = dst;
-    const fs::path pkg_path = USER_DE_PATH + "/" + pkg_name;
-    const fs::path local_migration_file_checker =
-        pkg_path.string() + "/" + MIGRATION_FILE_CHECKER;
-    if (!fs::exists(local_migration_file_checker, error)) {
-      LOG(INFO) << "Not a valid candidate for migration: " << pkg_path;
-      continue;
-    }
-
-    const fs::copy_options copy_flag =
-        fs::copy_options::overwrite_existing | fs::copy_options::recursive;
-    fs::copy(pkg_path, dst_path, copy_flag, error);
-
-    if (error) {
-      LOG(ERROR) << "Migration failed: " << error.message();
-    } else {
-      fs::remove(local_migration_file_checker);
-      LOG(INFO) << "Migration completed from " << pkg_path << " to " << dst;
-    }
-    break;  // Copy from one and only one directory
-  }
-}
diff --git a/system/btif/src/btif_av.cc b/system/btif/src/btif_av.cc
index 7f613b8..4e132c6 100644
--- a/system/btif/src/btif_av.cc
+++ b/system/btif/src/btif_av.cc
@@ -790,6 +790,10 @@
   }
 }
 
+const char* dump_av_sm_event_name(int event) {
+  return dump_av_sm_event_name(static_cast<btif_av_sm_event_t>(event));
+}
+
 BtifAvEvent::BtifAvEvent(uint32_t event, const void* p_data, size_t data_length)
     : event_(event), data_(nullptr), data_length_(0) {
   DeepCopy(event, p_data, data_length);
diff --git a/system/btif/src/btif_dm.cc b/system/btif/src/btif_dm.cc
index c5f4a6b..8ee07ad 100644
--- a/system/btif/src/btif_dm.cc
+++ b/system/btif/src/btif_dm.cc
@@ -109,8 +109,6 @@
 const Uuid UUID_BATTERY = Uuid::FromString("180F");
 const bool enable_address_consolidate = true;  // TODO remove
 
-#define COD_MASK 0x07FF
-
 #define COD_UNCLASSIFIED ((0x1F) << 8)
 #define COD_HID_KEYBOARD 0x0540
 #define COD_HID_POINTING 0x0580
@@ -123,6 +121,8 @@
 #define COD_AV_PORTABLE_AUDIO 0x041C
 #define COD_AV_HIFI_AUDIO 0x0428
 
+#define COD_CLASS_LE_AUDIO (1 << 14)
+
 #define BTIF_DM_MAX_SDP_ATTEMPTS_AFTER_PAIRING 2
 
 #ifndef PROPERTY_CLASS_OF_DEVICE
@@ -445,7 +445,7 @@
   if (btif_storage_get_remote_device_property(
           (RawAddress*)remote_bdaddr, &prop_name) == BT_STATUS_SUCCESS) {
     LOG_INFO("%s remote_cod = 0x%08x", __func__, remote_cod);
-    return remote_cod & COD_MASK;
+    return remote_cod;
   }
 
   return 0;
@@ -463,6 +463,9 @@
   return (get_cod(&bd_addr) & COD_HID_MASK) == COD_HID_MAJOR;
 }
 
+bool check_cod_le_audio(const RawAddress& bd_addr) {
+  return (get_cod(&bd_addr) & COD_CLASS_LE_AUDIO) == COD_CLASS_LE_AUDIO;
+}
 /*****************************************************************************
  *
  * Function        check_sdp_bl
@@ -657,6 +660,25 @@
                                      properties);
 }
 
+/* If device is LE Audio capable, we prefer LE connection first, this speeds
+ * up LE profile connection, and limits all possible service discovery
+ * ordering issues (first Classic, GATT over SDP, etc) */
+static bool is_device_le_audio_capable(const RawAddress bd_addr) {
+  if (!LeAudioClient::IsLeAudioClientRunning() ||
+      !check_cod_le_audio(bd_addr)) {
+    return false;
+  }
+
+  tBT_DEVICE_TYPE tmp_dev_type;
+  tBLE_ADDR_TYPE addr_type = BLE_ADDR_PUBLIC;
+  BTM_ReadDevInfo(bd_addr, &tmp_dev_type, &addr_type);
+  if (tmp_dev_type & BT_DEVICE_TYPE_BLE) {
+    return true;
+  }
+
+  return false;
+}
+
 /*******************************************************************************
  *
  * Function         btif_dm_cb_create_bond
@@ -672,6 +694,11 @@
   bool is_hid = check_cod(&bd_addr, COD_HID_POINTING);
   bond_state_changed(BT_STATUS_SUCCESS, bd_addr, BT_BOND_STATE_BONDING);
 
+  if (transport == BT_TRANSPORT_AUTO && is_device_le_audio_capable(bd_addr)) {
+    LOG_INFO("LE Audio && advertising over LE, use LE transport for Bonding");
+    transport = BT_TRANSPORT_LE;
+  }
+
   int device_type = 0;
   tBLE_ADDR_TYPE addr_type = BLE_ADDR_PUBLIC;
   std::string addrstr = bd_addr.ToString();
@@ -1044,8 +1071,7 @@
     }
 
     bool is_crosskey = false;
-    if (pairing_cb.state == BT_BOND_STATE_BONDING &&
-        p_auth_cmpl->bd_addr != pairing_cb.bd_addr) {
+    if (pairing_cb.state == BT_BOND_STATE_BONDING && p_auth_cmpl->is_ctkd) {
       LOG_INFO("bonding initiated due to cross key pairing");
       is_crosskey = true;
     }
@@ -1078,8 +1104,7 @@
       invoke_remote_device_properties_cb(BT_STATUS_SUCCESS, bd_addr, 1, &prop);
     } else {
       /* If bonded due to cross-key, save the static address too*/
-      if (pairing_cb.state == BT_BOND_STATE_BONDING &&
-          p_auth_cmpl->bd_addr != pairing_cb.bd_addr) {
+      if (is_crosskey) {
         BTIF_TRACE_DEBUG(
             "%s: bonding initiated due to cross key, adding static address",
             __func__);
diff --git a/system/btif/test/btif_core_test.cc b/system/btif/test/btif_core_test.cc
index 82f86dc..2ecdd2f 100644
--- a/system/btif/test/btif_core_test.cc
+++ b/system/btif/test/btif_core_test.cc
@@ -20,9 +20,16 @@
 #include <map>
 
 #include "bta/include/bta_ag_api.h"
+#include "bta/include/bta_av_api.h"
+#include "bta/include/bta_hd_api.h"
+#include "bta/include/bta_hf_client_api.h"
+#include "bta/include/bta_hh_api.h"
 #include "btcore/include/module.h"
 #include "btif/include/btif_api.h"
 #include "btif/include/btif_common.h"
+#include "btif/include/btif_util.h"
+#include "include/hardware/bluetooth.h"
+#include "include/hardware/bt_av.h"
 #include "types/raw_address.h"
 
 void set_hal_cbacks(bt_callbacks_t* callbacks);
@@ -174,3 +181,456 @@
   ASSERT_EQ(std::future_status::ready, future.wait_for(timeout_time));
   ASSERT_EQ(val, future.get());
 }
+
+extern const char* dump_av_sm_event_name(int event);
+TEST_F(BtifCoreTest, dump_av_sm_event_name) {
+  std::vector<std::pair<int, std::string>> events = {
+      std::make_pair(BTA_AV_ENABLE_EVT, "BTA_AV_ENABLE_EVT"),
+      std::make_pair(BTA_AV_REGISTER_EVT, "BTA_AV_REGISTER_EVT"),
+      std::make_pair(BTA_AV_OPEN_EVT, "BTA_AV_OPEN_EVT"),
+      std::make_pair(BTA_AV_CLOSE_EVT, "BTA_AV_CLOSE_EVT"),
+      std::make_pair(BTA_AV_START_EVT, "BTA_AV_START_EVT"),
+      std::make_pair(BTA_AV_STOP_EVT, "BTA_AV_STOP_EVT"),
+      std::make_pair(BTA_AV_PROTECT_REQ_EVT, "BTA_AV_PROTECT_REQ_EVT"),
+      std::make_pair(BTA_AV_PROTECT_RSP_EVT, "BTA_AV_PROTECT_RSP_EVT"),
+      std::make_pair(BTA_AV_RC_OPEN_EVT, "BTA_AV_RC_OPEN_EVT"),
+      std::make_pair(BTA_AV_RC_CLOSE_EVT, "BTA_AV_RC_CLOSE_EVT"),
+      std::make_pair(BTA_AV_RC_BROWSE_OPEN_EVT, "BTA_AV_RC_BROWSE_OPEN_EVT"),
+      std::make_pair(BTA_AV_RC_BROWSE_CLOSE_EVT, "BTA_AV_RC_BROWSE_CLOSE_EVT"),
+      std::make_pair(BTA_AV_REMOTE_CMD_EVT, "BTA_AV_REMOTE_CMD_EVT"),
+      std::make_pair(BTA_AV_REMOTE_RSP_EVT, "BTA_AV_REMOTE_RSP_EVT"),
+      std::make_pair(BTA_AV_VENDOR_CMD_EVT, "BTA_AV_VENDOR_CMD_EVT"),
+      std::make_pair(BTA_AV_VENDOR_RSP_EVT, "BTA_AV_VENDOR_RSP_EVT"),
+      std::make_pair(BTA_AV_RECONFIG_EVT, "BTA_AV_RECONFIG_EVT"),
+      std::make_pair(BTA_AV_SUSPEND_EVT, "BTA_AV_SUSPEND_EVT"),
+      std::make_pair(BTA_AV_PENDING_EVT, "BTA_AV_PENDING_EVT"),
+      std::make_pair(BTA_AV_META_MSG_EVT, "BTA_AV_META_MSG_EVT"),
+      std::make_pair(BTA_AV_REJECT_EVT, "BTA_AV_REJECT_EVT"),
+      std::make_pair(BTA_AV_RC_FEAT_EVT, "BTA_AV_RC_FEAT_EVT"),
+      std::make_pair(BTA_AV_RC_PSM_EVT, "BTA_AV_RC_PSM_EVT"),
+      std::make_pair(BTA_AV_OFFLOAD_START_RSP_EVT,
+                     "BTA_AV_OFFLOAD_START_RSP_EVT"),
+  };
+  for (const auto& event : events) {
+    ASSERT_STREQ(event.second.c_str(), dump_av_sm_event_name(event.first));
+  }
+  std::ostringstream oss;
+  oss << "UNKNOWN_EVENT";
+  ASSERT_STREQ(oss.str().c_str(),
+               dump_av_sm_event_name(std::numeric_limits<int>::max()));
+}
+
+TEST_F(BtifCoreTest, dump_dm_search_event) {
+  std::vector<std::pair<uint16_t, std::string>> events = {
+      std::make_pair(BTA_DM_INQ_RES_EVT, "BTA_DM_INQ_RES_EVT"),
+      std::make_pair(BTA_DM_INQ_CMPL_EVT, "BTA_DM_INQ_CMPL_EVT"),
+      std::make_pair(BTA_DM_DISC_RES_EVT, "BTA_DM_DISC_RES_EVT"),
+      std::make_pair(BTA_DM_DISC_BLE_RES_EVT, "BTA_DM_DISC_BLE_RES_EVT"),
+      std::make_pair(BTA_DM_DISC_CMPL_EVT, "BTA_DM_DISC_CMPL_EVT"),
+      std::make_pair(BTA_DM_SEARCH_CANCEL_CMPL_EVT,
+                     "BTA_DM_SEARCH_CANCEL_CMPL_EVT"),
+  };
+  for (const auto& event : events) {
+    ASSERT_STREQ(event.second.c_str(), dump_dm_search_event(event.first));
+  }
+  std::ostringstream oss;
+  oss << "UNKNOWN MSG ID";
+  ASSERT_STREQ(oss.str().c_str(),
+               dump_dm_search_event(std::numeric_limits<uint16_t>::max()));
+}
+
+TEST_F(BtifCoreTest, dump_property_type) {
+  std::vector<std::pair<bt_property_type_t, std::string>> types = {
+      std::make_pair(BT_PROPERTY_BDNAME, "BT_PROPERTY_BDNAME"),
+      std::make_pair(BT_PROPERTY_BDADDR, "BT_PROPERTY_BDADDR"),
+      std::make_pair(BT_PROPERTY_UUIDS, "BT_PROPERTY_UUIDS"),
+      std::make_pair(BT_PROPERTY_CLASS_OF_DEVICE,
+                     "BT_PROPERTY_CLASS_OF_DEVICE"),
+      std::make_pair(BT_PROPERTY_TYPE_OF_DEVICE, "BT_PROPERTY_TYPE_OF_DEVICE"),
+      std::make_pair(BT_PROPERTY_REMOTE_RSSI, "BT_PROPERTY_REMOTE_RSSI"),
+      std::make_pair(BT_PROPERTY_ADAPTER_DISCOVERABLE_TIMEOUT,
+                     "BT_PROPERTY_ADAPTER_DISCOVERABLE_TIMEOUT"),
+      std::make_pair(BT_PROPERTY_ADAPTER_BONDED_DEVICES,
+                     "BT_PROPERTY_ADAPTER_BONDED_DEVICES"),
+      std::make_pair(BT_PROPERTY_ADAPTER_SCAN_MODE,
+                     "BT_PROPERTY_ADAPTER_SCAN_MODE"),
+      std::make_pair(BT_PROPERTY_REMOTE_FRIENDLY_NAME,
+                     "BT_PROPERTY_REMOTE_FRIENDLY_NAME"),
+  };
+  for (const auto& type : types) {
+    ASSERT_STREQ(type.second.c_str(), dump_property_type(type.first));
+  }
+  std::ostringstream oss;
+  oss << "UNKNOWN PROPERTY ID";
+  ASSERT_STREQ(oss.str().c_str(),
+               dump_property_type(static_cast<bt_property_type_t>(
+                   std::numeric_limits<uint16_t>::max())));
+}
+
+TEST_F(BtifCoreTest, dump_dm_event) {
+  std::vector<std::pair<uint8_t, std::string>> events = {
+      std::make_pair(BTA_DM_PIN_REQ_EVT, "BTA_DM_PIN_REQ_EVT"),
+      std::make_pair(BTA_DM_AUTH_CMPL_EVT, "BTA_DM_AUTH_CMPL_EVT"),
+      std::make_pair(BTA_DM_LINK_UP_EVT, "BTA_DM_LINK_UP_EVT"),
+      std::make_pair(BTA_DM_LINK_DOWN_EVT, "BTA_DM_LINK_DOWN_EVT"),
+      std::make_pair(BTA_DM_BOND_CANCEL_CMPL_EVT,
+                     "BTA_DM_BOND_CANCEL_CMPL_EVT"),
+      std::make_pair(BTA_DM_SP_CFM_REQ_EVT, "BTA_DM_SP_CFM_REQ_EVT"),
+      std::make_pair(BTA_DM_SP_KEY_NOTIF_EVT, "BTA_DM_SP_KEY_NOTIF_EVT"),
+      std::make_pair(BTA_DM_BLE_KEY_EVT, "BTA_DM_BLE_KEY_EVT"),
+      std::make_pair(BTA_DM_BLE_SEC_REQ_EVT, "BTA_DM_BLE_SEC_REQ_EVT"),
+      std::make_pair(BTA_DM_BLE_PASSKEY_NOTIF_EVT,
+                     "BTA_DM_BLE_PASSKEY_NOTIF_EVT"),
+      std::make_pair(BTA_DM_BLE_PASSKEY_REQ_EVT, "BTA_DM_BLE_PASSKEY_REQ_EVT"),
+      std::make_pair(BTA_DM_BLE_OOB_REQ_EVT, "BTA_DM_BLE_OOB_REQ_EVT"),
+      std::make_pair(BTA_DM_BLE_SC_OOB_REQ_EVT, "BTA_DM_BLE_SC_OOB_REQ_EVT"),
+      std::make_pair(BTA_DM_BLE_LOCAL_IR_EVT, "BTA_DM_BLE_LOCAL_IR_EVT"),
+      std::make_pair(BTA_DM_BLE_LOCAL_ER_EVT, "BTA_DM_BLE_LOCAL_ER_EVT"),
+      std::make_pair(BTA_DM_BLE_AUTH_CMPL_EVT, "BTA_DM_BLE_AUTH_CMPL_EVT"),
+      std::make_pair(BTA_DM_DEV_UNPAIRED_EVT, "BTA_DM_DEV_UNPAIRED_EVT"),
+      std::make_pair(BTA_DM_ENER_INFO_READ, "BTA_DM_ENER_INFO_READ"),
+      std::make_pair(BTA_DM_REPORT_BONDING_EVT, "BTA_DM_REPORT_BONDING_EVT"),
+  };
+  for (const auto& event : events) {
+    ASSERT_STREQ(event.second.c_str(), dump_dm_event(event.first));
+  }
+  std::ostringstream oss;
+  oss << "UNKNOWN DM EVENT";
+  ASSERT_STREQ(oss.str().c_str(),
+               dump_dm_event(std::numeric_limits<uint8_t>::max()));
+}
+
+TEST_F(BtifCoreTest, dump_hf_event) {
+  std::vector<std::pair<uint8_t, std::string>> events = {
+      std::make_pair(BTA_AG_ENABLE_EVT, "BTA_AG_ENABLE_EVT"),
+      std::make_pair(BTA_AG_REGISTER_EVT, "BTA_AG_REGISTER_EVT"),
+      std::make_pair(BTA_AG_OPEN_EVT, "BTA_AG_OPEN_EVT"),
+      std::make_pair(BTA_AG_CLOSE_EVT, "BTA_AG_CLOSE_EVT"),
+      std::make_pair(BTA_AG_CONN_EVT, "BTA_AG_CONN_EVT"),
+      std::make_pair(BTA_AG_AUDIO_OPEN_EVT, "BTA_AG_AUDIO_OPEN_EVT"),
+      std::make_pair(BTA_AG_AUDIO_CLOSE_EVT, "BTA_AG_AUDIO_CLOSE_EVT"),
+      std::make_pair(BTA_AG_SPK_EVT, "BTA_AG_SPK_EVT"),
+      std::make_pair(BTA_AG_MIC_EVT, "BTA_AG_MIC_EVT"),
+      std::make_pair(BTA_AG_AT_CKPD_EVT, "BTA_AG_AT_CKPD_EVT"),
+      std::make_pair(BTA_AG_DISABLE_EVT, "BTA_AG_DISABLE_EVT"),
+      std::make_pair(BTA_AG_WBS_EVT, "BTA_AG_WBS_EVT"),
+      std::make_pair(BTA_AG_AT_A_EVT, "BTA_AG_AT_A_EVT"),
+      std::make_pair(BTA_AG_AT_D_EVT, "BTA_AG_AT_D_EVT"),
+      std::make_pair(BTA_AG_AT_CHLD_EVT, "BTA_AG_AT_CHLD_EVT"),
+      std::make_pair(BTA_AG_AT_CHUP_EVT, "BTA_AG_AT_CHUP_EVT"),
+      std::make_pair(BTA_AG_AT_CIND_EVT, "BTA_AG_AT_CIND_EVT"),
+      std::make_pair(BTA_AG_AT_VTS_EVT, "BTA_AG_AT_VTS_EVT"),
+      std::make_pair(BTA_AG_AT_BINP_EVT, "BTA_AG_AT_BINP_EVT"),
+      std::make_pair(BTA_AG_AT_BLDN_EVT, "BTA_AG_AT_BLDN_EVT"),
+      std::make_pair(BTA_AG_AT_BVRA_EVT, "BTA_AG_AT_BVRA_EVT"),
+      std::make_pair(BTA_AG_AT_NREC_EVT, "BTA_AG_AT_NREC_EVT"),
+      std::make_pair(BTA_AG_AT_CNUM_EVT, "BTA_AG_AT_CNUM_EVT"),
+      std::make_pair(BTA_AG_AT_BTRH_EVT, "BTA_AG_AT_BTRH_EVT"),
+      std::make_pair(BTA_AG_AT_CLCC_EVT, "BTA_AG_AT_CLCC_EVT"),
+      std::make_pair(BTA_AG_AT_COPS_EVT, "BTA_AG_AT_COPS_EVT"),
+      std::make_pair(BTA_AG_AT_UNAT_EVT, "BTA_AG_AT_UNAT_EVT"),
+      std::make_pair(BTA_AG_AT_CBC_EVT, "BTA_AG_AT_CBC_EVT"),
+      std::make_pair(BTA_AG_AT_BAC_EVT, "BTA_AG_AT_BAC_EVT"),
+      std::make_pair(BTA_AG_AT_BCS_EVT, "BTA_AG_AT_BCS_EVT"),
+      std::make_pair(BTA_AG_AT_BIND_EVT, "BTA_AG_AT_BIND_EVT"),
+      std::make_pair(BTA_AG_AT_BIEV_EVT, "BTA_AG_AT_BIEV_EVT"),
+      std::make_pair(BTA_AG_AT_BIA_EVT, "BTA_AG_AT_BIA_EVT"),
+  };
+  for (const auto& event : events) {
+    ASSERT_STREQ(event.second.c_str(), dump_hf_event(event.first));
+  }
+  std::ostringstream oss;
+  oss << "UNKNOWN MSG ID";
+  ASSERT_STREQ(oss.str().c_str(),
+               dump_hf_event(std::numeric_limits<uint8_t>::max()));
+}
+
+TEST_F(BtifCoreTest, dump_hf_client_event) {
+  std::vector<std::pair<int, std::string>> events = {
+      std::make_pair(BTA_HF_CLIENT_ENABLE_EVT, "BTA_HF_CLIENT_ENABLE_EVT"),
+      std::make_pair(BTA_HF_CLIENT_REGISTER_EVT, "BTA_HF_CLIENT_REGISTER_EVT"),
+      std::make_pair(BTA_HF_CLIENT_OPEN_EVT, "BTA_HF_CLIENT_OPEN_EVT"),
+      std::make_pair(BTA_HF_CLIENT_CLOSE_EVT, "BTA_HF_CLIENT_CLOSE_EVT"),
+      std::make_pair(BTA_HF_CLIENT_CONN_EVT, "BTA_HF_CLIENT_CONN_EVT"),
+      std::make_pair(BTA_HF_CLIENT_AUDIO_OPEN_EVT,
+                     "BTA_HF_CLIENT_AUDIO_OPEN_EVT"),
+      std::make_pair(BTA_HF_CLIENT_AUDIO_MSBC_OPEN_EVT,
+                     "BTA_HF_CLIENT_AUDIO_MSBC_OPEN_EVT"),
+      std::make_pair(BTA_HF_CLIENT_AUDIO_CLOSE_EVT,
+                     "BTA_HF_CLIENT_AUDIO_CLOSE_EVT"),
+      std::make_pair(BTA_HF_CLIENT_SPK_EVT, "BTA_HF_CLIENT_SPK_EVT"),
+      std::make_pair(BTA_HF_CLIENT_MIC_EVT, "BTA_HF_CLIENT_MIC_EVT"),
+      std::make_pair(BTA_HF_CLIENT_DISABLE_EVT, "BTA_HF_CLIENT_DISABLE_EVT"),
+      std::make_pair(BTA_HF_CLIENT_IND_EVT, "BTA_HF_CLIENT_IND_EVT"),
+      std::make_pair(BTA_HF_CLIENT_VOICE_REC_EVT,
+                     "BTA_HF_CLIENT_VOICE_REC_EVT"),
+      std::make_pair(BTA_HF_CLIENT_OPERATOR_NAME_EVT,
+                     "BTA_HF_CLIENT_OPERATOR_NAME_EVT"),
+      std::make_pair(BTA_HF_CLIENT_CLIP_EVT, "BTA_HF_CLIENT_CLIP_EVT"),
+      std::make_pair(BTA_HF_CLIENT_CCWA_EVT, "BTA_HF_CLIENT_CCWA_EVT"),
+      std::make_pair(BTA_HF_CLIENT_AT_RESULT_EVT,
+                     "BTA_HF_CLIENT_AT_RESULT_EVT"),
+      std::make_pair(BTA_HF_CLIENT_CLCC_EVT, "BTA_HF_CLIENT_CLCC_EVT"),
+      std::make_pair(BTA_HF_CLIENT_CNUM_EVT, "BTA_HF_CLIENT_CNUM_EVT"),
+      std::make_pair(BTA_HF_CLIENT_BTRH_EVT, "BTA_HF_CLIENT_BTRH_EVT"),
+      std::make_pair(BTA_HF_CLIENT_BSIR_EVT, "BTA_HF_CLIENT_BSIR_EVT"),
+      std::make_pair(BTA_HF_CLIENT_BINP_EVT, "BTA_HF_CLIENT_BINP_EVT"),
+      std::make_pair(BTA_HF_CLIENT_RING_INDICATION,
+                     "BTA_HF_CLIENT_RING_INDICATION"),
+  };
+  for (const auto& event : events) {
+    ASSERT_STREQ(event.second.c_str(), dump_hf_client_event(event.first));
+  }
+  std::ostringstream oss;
+  oss << "UNKNOWN MSG ID";
+  ASSERT_STREQ(oss.str().c_str(),
+               dump_hf_client_event(std::numeric_limits<uint16_t>::max()));
+}
+
+TEST_F(BtifCoreTest, dump_hh_event) {
+  std::vector<std::pair<int, std::string>> events = {
+      std::make_pair(BTA_HH_ENABLE_EVT, "BTA_HH_ENABLE_EVT"),
+      std::make_pair(BTA_HH_DISABLE_EVT, "BTA_HH_DISABLE_EVT"),
+      std::make_pair(BTA_HH_OPEN_EVT, "BTA_HH_OPEN_EVT"),
+      std::make_pair(BTA_HH_CLOSE_EVT, "BTA_HH_CLOSE_EVT"),
+      std::make_pair(BTA_HH_GET_DSCP_EVT, "BTA_HH_GET_DSCP_EVT"),
+      std::make_pair(BTA_HH_GET_PROTO_EVT, "BTA_HH_GET_PROTO_EVT"),
+      std::make_pair(BTA_HH_GET_RPT_EVT, "BTA_HH_GET_RPT_EVT"),
+      std::make_pair(BTA_HH_GET_IDLE_EVT, "BTA_HH_GET_IDLE_EVT"),
+      std::make_pair(BTA_HH_SET_PROTO_EVT, "BTA_HH_SET_PROTO_EVT"),
+      std::make_pair(BTA_HH_SET_RPT_EVT, "BTA_HH_SET_RPT_EVT"),
+      std::make_pair(BTA_HH_SET_IDLE_EVT, "BTA_HH_SET_IDLE_EVT"),
+      std::make_pair(BTA_HH_VC_UNPLUG_EVT, "BTA_HH_VC_UNPLUG_EVT"),
+      std::make_pair(BTA_HH_ADD_DEV_EVT, "BTA_HH_ADD_DEV_EVT"),
+      std::make_pair(BTA_HH_RMV_DEV_EVT, "BTA_HH_RMV_DEV_EVT"),
+      std::make_pair(BTA_HH_API_ERR_EVT, "BTA_HH_API_ERR_EVT"),
+  };
+  for (const auto& event : events) {
+    ASSERT_STREQ(event.second.c_str(), dump_hh_event(event.first));
+  }
+  std::ostringstream oss;
+  oss << "UNKNOWN MSG ID";
+  ASSERT_STREQ(oss.str().c_str(),
+               dump_hh_event(std::numeric_limits<uint16_t>::max()));
+}
+
+TEST_F(BtifCoreTest, dump_hd_event) {
+  std::vector<std::pair<uint16_t, std::string>> events = {
+      std::make_pair(BTA_HD_ENABLE_EVT, "BTA_HD_ENABLE_EVT"),
+      std::make_pair(BTA_HD_DISABLE_EVT, "BTA_HD_DISABLE_EVT"),
+      std::make_pair(BTA_HD_REGISTER_APP_EVT, "BTA_HD_REGISTER_APP_EVT"),
+      std::make_pair(BTA_HD_UNREGISTER_APP_EVT, "BTA_HD_UNREGISTER_APP_EVT"),
+      std::make_pair(BTA_HD_OPEN_EVT, "BTA_HD_OPEN_EVT"),
+      std::make_pair(BTA_HD_CLOSE_EVT, "BTA_HD_CLOSE_EVT"),
+      std::make_pair(BTA_HD_GET_REPORT_EVT, "BTA_HD_GET_REPORT_EVT"),
+      std::make_pair(BTA_HD_SET_REPORT_EVT, "BTA_HD_SET_REPORT_EVT"),
+      std::make_pair(BTA_HD_SET_PROTOCOL_EVT, "BTA_HD_SET_PROTOCOL_EVT"),
+      std::make_pair(BTA_HD_INTR_DATA_EVT, "BTA_HD_INTR_DATA_EVT"),
+      std::make_pair(BTA_HD_VC_UNPLUG_EVT, "BTA_HD_VC_UNPLUG_EVT"),
+      std::make_pair(BTA_HD_CONN_STATE_EVT, "BTA_HD_CONN_STATE_EVT"),
+      std::make_pair(BTA_HD_API_ERR_EVT, "BTA_HD_API_ERR_EVT"),
+  };
+  for (const auto& event : events) {
+    ASSERT_STREQ(event.second.c_str(), dump_hd_event(event.first));
+  }
+  std::ostringstream oss;
+  oss << "UNKNOWN MSG ID";
+  ASSERT_STREQ(oss.str().c_str(),
+               dump_hd_event(std::numeric_limits<uint16_t>::max()));
+}
+
+TEST_F(BtifCoreTest, dump_thread_evt) {
+  std::vector<std::pair<bt_cb_thread_evt, std::string>> events = {
+      std::make_pair(ASSOCIATE_JVM, "ASSOCIATE_JVM"),
+      std::make_pair(DISASSOCIATE_JVM, "DISASSOCIATE_JVM"),
+  };
+  for (const auto& event : events) {
+    ASSERT_STREQ(event.second.c_str(), dump_thread_evt(event.first));
+  }
+  std::ostringstream oss;
+  oss << "unknown thread evt";
+  ASSERT_STREQ(oss.str().c_str(), dump_thread_evt(static_cast<bt_cb_thread_evt>(
+                                      std::numeric_limits<uint16_t>::max())));
+}
+
+TEST_F(BtifCoreTest, dump_av_conn_state) {
+  std::vector<std::pair<uint16_t, std::string>> events = {
+      std::make_pair(BTAV_CONNECTION_STATE_DISCONNECTED,
+                     "BTAV_CONNECTION_STATE_DISCONNECTED"),
+      std::make_pair(BTAV_CONNECTION_STATE_CONNECTING,
+                     "BTAV_CONNECTION_STATE_CONNECTING"),
+      std::make_pair(BTAV_CONNECTION_STATE_CONNECTED,
+                     "BTAV_CONNECTION_STATE_CONNECTED"),
+      std::make_pair(BTAV_CONNECTION_STATE_DISCONNECTING,
+                     "BTAV_CONNECTION_STATE_DISCONNECTING"),
+  };
+  for (const auto& event : events) {
+    ASSERT_STREQ(event.second.c_str(), dump_av_conn_state(event.first));
+  }
+  std::ostringstream oss;
+  oss << "UNKNOWN MSG ID";
+  ASSERT_STREQ(oss.str().c_str(),
+               dump_av_conn_state(std::numeric_limits<uint16_t>::max()));
+}
+
+TEST_F(BtifCoreTest, dump_av_audio_state) {
+  std::vector<std::pair<uint16_t, std::string>> events = {
+      std::make_pair(BTAV_AUDIO_STATE_REMOTE_SUSPEND,
+                     "BTAV_AUDIO_STATE_REMOTE_SUSPEND"),
+      std::make_pair(BTAV_AUDIO_STATE_STOPPED, "BTAV_AUDIO_STATE_STOPPED"),
+      std::make_pair(BTAV_AUDIO_STATE_STARTED, "BTAV_AUDIO_STATE_STARTED"),
+  };
+  for (const auto& event : events) {
+    ASSERT_STREQ(event.second.c_str(), dump_av_audio_state(event.first));
+  }
+  std::ostringstream oss;
+  oss << "UNKNOWN MSG ID";
+  ASSERT_STREQ(oss.str().c_str(),
+               dump_av_audio_state(std::numeric_limits<uint16_t>::max()));
+}
+
+TEST_F(BtifCoreTest, dump_adapter_scan_mode) {
+  std::vector<std::pair<bt_scan_mode_t, std::string>> events = {
+      std::make_pair(BT_SCAN_MODE_NONE, "BT_SCAN_MODE_NONE"),
+      std::make_pair(BT_SCAN_MODE_CONNECTABLE, "BT_SCAN_MODE_CONNECTABLE"),
+      std::make_pair(BT_SCAN_MODE_CONNECTABLE_DISCOVERABLE,
+                     "BT_SCAN_MODE_CONNECTABLE_DISCOVERABLE"),
+  };
+  for (const auto& event : events) {
+    ASSERT_STREQ(event.second.c_str(), dump_adapter_scan_mode(event.first));
+  }
+  std::ostringstream oss;
+  oss << "unknown scan mode";
+  ASSERT_STREQ(oss.str().c_str(),
+               dump_adapter_scan_mode(static_cast<bt_scan_mode_t>(
+                   std::numeric_limits<int>::max())));
+}
+
+TEST_F(BtifCoreTest, dump_bt_status) {
+  std::vector<std::pair<bt_status_t, std::string>> events = {
+      std::make_pair(BT_STATUS_SUCCESS, "BT_STATUS_SUCCESS"),
+      std::make_pair(BT_STATUS_FAIL, "BT_STATUS_FAIL"),
+      std::make_pair(BT_STATUS_NOT_READY, "BT_STATUS_NOT_READY"),
+      std::make_pair(BT_STATUS_NOMEM, "BT_STATUS_NOMEM"),
+      std::make_pair(BT_STATUS_BUSY, "BT_STATUS_BUSY"),
+      std::make_pair(BT_STATUS_UNSUPPORTED, "BT_STATUS_UNSUPPORTED"),
+  };
+  for (const auto& event : events) {
+    ASSERT_STREQ(event.second.c_str(), dump_bt_status(event.first));
+  }
+  std::ostringstream oss;
+  oss << "unknown scan mode";
+  ASSERT_STREQ(oss.str().c_str(), dump_bt_status(static_cast<bt_status_t>(
+                                      std::numeric_limits<int>::max())));
+}
+
+TEST_F(BtifCoreTest, dump_rc_event) {
+  std::vector<std::pair<uint8_t, std::string>> events = {
+      std::make_pair(BTA_AV_RC_OPEN_EVT, "BTA_AV_RC_OPEN_EVT"),
+      std::make_pair(BTA_AV_RC_CLOSE_EVT, "BTA_AV_RC_CLOSE_EVT"),
+      std::make_pair(BTA_AV_RC_BROWSE_OPEN_EVT, "BTA_AV_RC_BROWSE_OPEN_EVT"),
+      std::make_pair(BTA_AV_RC_BROWSE_CLOSE_EVT, "BTA_AV_RC_BROWSE_CLOSE_EVT"),
+      std::make_pair(BTA_AV_REMOTE_CMD_EVT, "BTA_AV_REMOTE_CMD_EVT"),
+      std::make_pair(BTA_AV_REMOTE_RSP_EVT, "BTA_AV_REMOTE_RSP_EVT"),
+      std::make_pair(BTA_AV_VENDOR_CMD_EVT, "BTA_AV_VENDOR_CMD_EVT"),
+      std::make_pair(BTA_AV_VENDOR_RSP_EVT, "BTA_AV_VENDOR_RSP_EVT"),
+      std::make_pair(BTA_AV_META_MSG_EVT, "BTA_AV_META_MSG_EVT"),
+      std::make_pair(BTA_AV_RC_FEAT_EVT, "BTA_AV_RC_FEAT_EVT"),
+      std::make_pair(BTA_AV_RC_PSM_EVT, "BTA_AV_RC_PSM_EVT"),
+  };
+  for (const auto& event : events) {
+    ASSERT_STREQ(event.second.c_str(), dump_rc_event(event.first));
+  }
+  std::ostringstream oss;
+  oss << "UNKNOWN_EVENT";
+  ASSERT_STREQ(oss.str().c_str(),
+               dump_rc_event(std::numeric_limits<uint8_t>::max()));
+}
+
+TEST_F(BtifCoreTest, dump_rc_notification_event_id) {
+  std::vector<std::pair<uint8_t, std::string>> events = {
+      std::make_pair(AVRC_EVT_PLAY_STATUS_CHANGE,
+                     "AVRC_EVT_PLAY_STATUS_CHANGE"),
+      std::make_pair(AVRC_EVT_TRACK_CHANGE, "AVRC_EVT_TRACK_CHANGE"),
+      std::make_pair(AVRC_EVT_TRACK_REACHED_END, "AVRC_EVT_TRACK_REACHED_END"),
+      std::make_pair(AVRC_EVT_TRACK_REACHED_START,
+                     "AVRC_EVT_TRACK_REACHED_START"),
+      std::make_pair(AVRC_EVT_PLAY_POS_CHANGED, "AVRC_EVT_PLAY_POS_CHANGED"),
+      std::make_pair(AVRC_EVT_BATTERY_STATUS_CHANGE,
+                     "AVRC_EVT_BATTERY_STATUS_CHANGE"),
+      std::make_pair(AVRC_EVT_SYSTEM_STATUS_CHANGE,
+                     "AVRC_EVT_SYSTEM_STATUS_CHANGE"),
+      std::make_pair(AVRC_EVT_APP_SETTING_CHANGE,
+                     "AVRC_EVT_APP_SETTING_CHANGE"),
+      std::make_pair(AVRC_EVT_VOLUME_CHANGE, "AVRC_EVT_VOLUME_CHANGE"),
+      std::make_pair(AVRC_EVT_ADDR_PLAYER_CHANGE,
+                     "AVRC_EVT_ADDR_PLAYER_CHANGE"),
+      std::make_pair(AVRC_EVT_AVAL_PLAYERS_CHANGE,
+                     "AVRC_EVT_AVAL_PLAYERS_CHANGE"),
+      std::make_pair(AVRC_EVT_NOW_PLAYING_CHANGE,
+                     "AVRC_EVT_NOW_PLAYING_CHANGE"),
+      std::make_pair(AVRC_EVT_UIDS_CHANGE, "AVRC_EVT_UIDS_CHANGE"),
+  };
+  for (const auto& event : events) {
+    ASSERT_STREQ(event.second.c_str(),
+                 dump_rc_notification_event_id(event.first));
+  }
+  std::ostringstream oss;
+  oss << "Unhandled Event ID";
+  ASSERT_STREQ(oss.str().c_str(), dump_rc_notification_event_id(
+                                      std::numeric_limits<uint8_t>::max()));
+}
+
+TEST_F(BtifCoreTest, dump_rc_pdu) {
+  std::vector<std::pair<uint8_t, std::string>> pdus = {
+      std::make_pair(AVRC_PDU_LIST_PLAYER_APP_ATTR,
+                     "AVRC_PDU_LIST_PLAYER_APP_ATTR"),
+      std::make_pair(AVRC_PDU_LIST_PLAYER_APP_VALUES,
+                     "AVRC_PDU_LIST_PLAYER_APP_VALUES"),
+      std::make_pair(AVRC_PDU_GET_CUR_PLAYER_APP_VALUE,
+                     "AVRC_PDU_GET_CUR_PLAYER_APP_VALUE"),
+      std::make_pair(AVRC_PDU_SET_PLAYER_APP_VALUE,
+                     "AVRC_PDU_SET_PLAYER_APP_VALUE"),
+      std::make_pair(AVRC_PDU_GET_PLAYER_APP_ATTR_TEXT,
+                     "AVRC_PDU_GET_PLAYER_APP_ATTR_TEXT"),
+      std::make_pair(AVRC_PDU_GET_PLAYER_APP_VALUE_TEXT,
+                     "AVRC_PDU_GET_PLAYER_APP_VALUE_TEXT"),
+      std::make_pair(AVRC_PDU_INFORM_DISPLAY_CHARSET,
+                     "AVRC_PDU_INFORM_DISPLAY_CHARSET"),
+      std::make_pair(AVRC_PDU_INFORM_BATTERY_STAT_OF_CT,
+                     "AVRC_PDU_INFORM_BATTERY_STAT_OF_CT"),
+      std::make_pair(AVRC_PDU_GET_ELEMENT_ATTR, "AVRC_PDU_GET_ELEMENT_ATTR"),
+      std::make_pair(AVRC_PDU_GET_PLAY_STATUS, "AVRC_PDU_GET_PLAY_STATUS"),
+      std::make_pair(AVRC_PDU_REGISTER_NOTIFICATION,
+                     "AVRC_PDU_REGISTER_NOTIFICATION"),
+      std::make_pair(AVRC_PDU_REQUEST_CONTINUATION_RSP,
+                     "AVRC_PDU_REQUEST_CONTINUATION_RSP"),
+      std::make_pair(AVRC_PDU_ABORT_CONTINUATION_RSP,
+                     "AVRC_PDU_ABORT_CONTINUATION_RSP"),
+      std::make_pair(AVRC_PDU_SET_ABSOLUTE_VOLUME,
+                     "AVRC_PDU_SET_ABSOLUTE_VOLUME"),
+      std::make_pair(AVRC_PDU_SET_ADDRESSED_PLAYER,
+                     "AVRC_PDU_SET_ADDRESSED_PLAYER"),
+      std::make_pair(AVRC_PDU_CHANGE_PATH, "AVRC_PDU_CHANGE_PATH"),
+      std::make_pair(AVRC_PDU_GET_CAPABILITIES, "AVRC_PDU_GET_CAPABILITIES"),
+      std::make_pair(AVRC_PDU_SET_BROWSED_PLAYER,
+                     "AVRC_PDU_SET_BROWSED_PLAYER"),
+      std::make_pair(AVRC_PDU_GET_FOLDER_ITEMS, "AVRC_PDU_GET_FOLDER_ITEMS"),
+      std::make_pair(AVRC_PDU_GET_ITEM_ATTRIBUTES,
+                     "AVRC_PDU_GET_ITEM_ATTRIBUTES"),
+      std::make_pair(AVRC_PDU_PLAY_ITEM, "AVRC_PDU_PLAY_ITEM"),
+      std::make_pair(AVRC_PDU_SEARCH, "AVRC_PDU_SEARCH"),
+      std::make_pair(AVRC_PDU_ADD_TO_NOW_PLAYING,
+                     "AVRC_PDU_ADD_TO_NOW_PLAYING"),
+      std::make_pair(AVRC_PDU_GET_TOTAL_NUM_OF_ITEMS,
+                     "AVRC_PDU_GET_TOTAL_NUM_OF_ITEMS"),
+      std::make_pair(AVRC_PDU_GENERAL_REJECT, "AVRC_PDU_GENERAL_REJECT"),
+  };
+  for (const auto& pdu : pdus) {
+    ASSERT_STREQ(pdu.second.c_str(), dump_rc_pdu(pdu.first));
+  }
+  std::ostringstream oss;
+  oss << "Unknown PDU";
+  ASSERT_STREQ(oss.str().c_str(),
+               dump_rc_pdu(std::numeric_limits<uint8_t>::max()));
+}
diff --git a/system/device/include/interop_database.h b/system/device/include/interop_database.h
index d2f0c99..d9dd282 100644
--- a/system/device/include/interop_database.h
+++ b/system/device/include/interop_database.h
@@ -126,6 +126,9 @@
     // Toyota Prius - 2015
     {{{0xfc, 0xc2, 0xde, 0, 0, 0}}, 3, INTEROP_DISABLE_ROLE_SWITCH},
 
+    // Toyota Prius - b/231092023
+    {{{0x9c, 0xdf, 0x03, 0, 0, 0}}, 3, INTEROP_DISABLE_ROLE_SWITCH},
+
     // OBU II Bluetooth dongle
     {{{0x00, 0x04, 0x3e, 0, 0, 0}}, 3, INTEROP_DISABLE_ROLE_SWITCH},
 
diff --git a/system/gd/common/init_flags.cc b/system/gd/common/init_flags.cc
index c119aa6..853f94c 100644
--- a/system/gd/common/init_flags.cc
+++ b/system/gd/common/init_flags.cc
@@ -30,6 +30,7 @@
 bool InitFlags::logging_debug_enabled_for_all = false;
 int InitFlags::hci_adapter = 0;
 std::unordered_map<std::string, bool> InitFlags::logging_debug_explicit_tag_settings = {};
+bool InitFlags::btm_dm_flush_discovery_queue_on_search_cancel = false;
 
 bool ParseBoolFlag(const std::vector<std::string>& flag_pair, const std::string& flag, bool* variable) {
   if (flag != flag_pair[0]) {
@@ -70,6 +71,11 @@
     // Parse adapter index (defaults to 0)
     ParseIntFlag(flag_pair, "--hci", &hci_adapter);
 
+    ParseBoolFlag(
+        flag_pair,
+        "INIT_btm_dm_flush_discovery_queue_on_search_cancel",
+        &btm_dm_flush_discovery_queue_on_search_cancel);
+
     ParseBoolFlag(flag_pair, "INIT_logging_debug_enabled_for_all", &logging_debug_enabled_for_all);
     if ("INIT_logging_debug_enabled_for_tags" == flag_pair[0]) {
       auto tags = StringSplit(flag_pair[1], ",");
diff --git a/system/gd/common/init_flags.fbs b/system/gd/common/init_flags.fbs
index aba1b07..ae561ef 100644
--- a/system/gd/common/init_flags.fbs
+++ b/system/gd/common/init_flags.fbs
@@ -12,6 +12,7 @@
     gd_controller_enabled:bool (privacy:"Any");
     gd_core_enabled:bool (privacy:"Any");
     btaa_hci_log_enabled:bool (privacy:"Any");
+    btm_dm_flush_discovery_queue_on_search_cancel:bool (privacy:"Any");
 }
 
 root_type InitFlagsData;
diff --git a/system/gd/common/init_flags.h b/system/gd/common/init_flags.h
index 250e4f5..8ee9cc8 100644
--- a/system/gd/common/init_flags.h
+++ b/system/gd/common/init_flags.h
@@ -41,6 +41,10 @@
     return logging_debug_enabled_for_all;
   }
 
+  inline static bool IsBtmDmFlushDiscoveryQueueOnSearchCancel() {
+    return btm_dm_flush_discovery_queue_on_search_cancel;
+  }
+
   inline static int GetAdapterIndex() {
     return hci_adapter;
   }
@@ -50,6 +54,7 @@
  private:
   static void SetAll(bool value);
   static bool logging_debug_enabled_for_all;
+  static bool btm_dm_flush_discovery_queue_on_search_cancel;
   static int hci_adapter;
   // save both log allow list and block list in the map to save hashing time
   static std::unordered_map<std::string, bool> logging_debug_explicit_tag_settings;
diff --git a/system/gd/common/init_flags_test.cc b/system/gd/common/init_flags_test.cc
index 76babcc..74186e5 100644
--- a/system/gd/common/init_flags_test.cc
+++ b/system/gd/common/init_flags_test.cc
@@ -22,6 +22,12 @@
 
 using bluetooth::common::InitFlags;
 
+TEST(InitFlagsTest, test_enable_btm_flush_discovery_queue_on_search_cancel) {
+  const char* input[] = {"INIT_btm_dm_flush_discovery_queue_on_search_cancel=true", nullptr};
+  InitFlags::Load(input);
+  ASSERT_TRUE(InitFlags::IsBtmDmFlushDiscoveryQueueOnSearchCancel());
+}
+
 TEST(InitFlagsTest, test_enable_debug_logging_for_all) {
   const char* input[] = {"INIT_logging_debug_enabled_for_all=true", nullptr};
   InitFlags::Load(input);
diff --git a/system/gd/hci/acl_manager/classic_impl.h b/system/gd/hci/acl_manager/classic_impl.h
index 569ce75..87b18b4 100644
--- a/system/gd/hci/acl_manager/classic_impl.h
+++ b/system/gd/hci/acl_manager/classic_impl.h
@@ -25,6 +25,7 @@
 #include "hci/acl_manager/event_checkers.h"
 #include "hci/acl_manager/round_robin_scheduler.h"
 #include "hci/controller.h"
+#include "os/metrics.h"
 #include "security/security_manager_listener.h"
 #include "security/security_module.h"
 
@@ -209,6 +210,14 @@
       }
       return kIllegalConnectionHandle;
     }
+    Address get_address(uint16_t handle) const {
+      std::unique_lock<std::mutex> lock(acl_connections_guard_);
+      auto connection = acl_connections_.find(handle);
+      if (connection == acl_connections_.end()) {
+        return Address::kEmpty;
+      }
+      return connection->second.address_with_type_.GetAddress();
+    }
     bool is_classic_link_already_connected(const Address& address) const {
       std::unique_lock<std::mutex> lock(acl_connections_guard_);
       for (const auto& connection : acl_connections_) {
@@ -411,6 +420,8 @@
   static constexpr bool kRemoveConnectionAfterwards = true;
   void on_classic_disconnect(uint16_t handle, ErrorCode reason) {
     bool event_also_routes_to_other_receivers = connections.crash_on_unknown_handle_;
+    bluetooth::os::LogMetricBluetoothDisconnectionReasonReported(
+        static_cast<uint32_t>(reason), connections.get_address(handle), handle);
     connections.crash_on_unknown_handle_ = false;
     connections.execute(
         handle,
@@ -604,6 +615,8 @@
     auto view = ReadRemoteSupportedFeaturesCompleteView::Create(packet);
     ASSERT_LOG(view.IsValid(), "Read remote supported features packet invalid");
     uint16_t handle = view.GetConnectionHandle();
+    bluetooth::os::LogMetricBluetoothRemoteSupportedFeatures(
+        connections.get_address(handle), 0, view.GetLmpFeatures(), handle);
     connections.execute(handle, [=](ConnectionManagementCallbacks* callbacks) {
       callbacks->OnReadRemoteSupportedFeaturesComplete(view.GetLmpFeatures());
     });
@@ -613,6 +626,8 @@
     auto view = ReadRemoteExtendedFeaturesCompleteView::Create(packet);
     ASSERT_LOG(view.IsValid(), "Read remote extended features packet invalid");
     uint16_t handle = view.GetConnectionHandle();
+    bluetooth::os::LogMetricBluetoothRemoteSupportedFeatures(
+        connections.get_address(handle), view.GetPageNumber(), view.GetExtendedLmpFeatures(), handle);
     connections.execute(handle, [=](ConnectionManagementCallbacks* callbacks) {
       callbacks->OnReadRemoteExtendedFeaturesComplete(
           view.GetPageNumber(), view.GetMaximumPageNumber(), view.GetExtendedLmpFeatures());
diff --git a/system/gd/hci/controller.cc b/system/gd/hci/controller.cc
index 3827f83..4508904 100644
--- a/system/gd/hci/controller.cc
+++ b/system/gd/hci/controller.cc
@@ -23,6 +23,7 @@
 
 #include "common/init_flags.h"
 #include "hci/hci_layer.h"
+#include "os/metrics.h"
 
 namespace bluetooth {
 namespace hci {
@@ -244,6 +245,12 @@
     ASSERT_LOG(status == ErrorCode::SUCCESS, "Status 0x%02hhx, %s", status, ErrorCodeText(status).c_str());
 
     local_version_information_ = complete_view.GetLocalVersionInformation();
+    bluetooth::os::LogMetricBluetoothLocalVersions(
+        local_version_information_.manufacturer_name_,
+        static_cast<uint8_t>(local_version_information_.lmp_version_),
+        local_version_information_.lmp_subversion_,
+        static_cast<uint8_t>(local_version_information_.hci_version_),
+        local_version_information_.hci_revision_);
   }
 
   void read_local_supported_commands_complete_handler(CommandCompleteView view) {
@@ -261,7 +268,7 @@
     ASSERT_LOG(status == ErrorCode::SUCCESS, "Status 0x%02hhx, %s", status, ErrorCodeText(status).c_str());
     uint8_t page_number = complete_view.GetPageNumber();
     extended_lmp_features_array_.push_back(complete_view.GetExtendedLmpFeatures());
-
+    bluetooth::os::LogMetricBluetoothLocalSupportedFeatures(page_number, complete_view.GetExtendedLmpFeatures());
     // Query all extended features
     if (page_number < complete_view.GetMaximumPageNumber()) {
       page_number++;
diff --git a/system/gd/hci/hci_packets.pdl b/system/gd/hci/hci_packets.pdl
index abee8a1..c09fd05 100644
--- a/system/gd/hci/hci_packets.pdl
+++ b/system/gd/hci/hci_packets.pdl
@@ -777,6 +777,7 @@
 // Vendor specific events
 enum VseSubeventCode : 8 {
   BLE_THRESHOLD = 0x54,
+  BLE_STCHANGE = 0x55,
   BLE_TRACKING = 0x56,
   DEBUG_INFO = 0x57,
   BQR_EVENT = 0x58,
@@ -6000,6 +6001,17 @@
   _body_,
 }
 
+enum VseStateChangeReason : 8 {
+  CONNECTION_RECEIVED = 0x00,
+}
+
+packet LEAdvertiseStateChangeEvent : VendorSpecificEvent (subevent_code = BLE_STCHANGE) {
+  advertising_instance : 8,
+  state_change_reason : VseStateChangeReason,
+  connection_handle : 12,
+  reserved : 4,
+}
+
 packet LEAdvertisementTrackingWithInfoEvent : LEAdvertisementTrackingEvent {
   tx_power : 8,
   rssi : 8,
diff --git a/system/gd/hci/le_advertising_manager.cc b/system/gd/hci/le_advertising_manager.cc
index 5e5a79f..9437a94 100644
--- a/system/gd/hci/le_advertising_manager.cc
+++ b/system/gd/hci/le_advertising_manager.cc
@@ -24,6 +24,7 @@
 #include "hci/hci_layer.h"
 #include "hci/hci_packets.h"
 #include "hci/le_advertising_interface.h"
+#include "hci/vendor_specific_event_manager.h"
 #include "module.h"
 #include "os/handler.h"
 #include "os/log.h"
@@ -103,17 +104,25 @@
     advertising_sets_.clear();
   }
 
-  void start(os::Handler* handler, hci::HciLayer* hci_layer, hci::Controller* controller,
-             hci::AclManager* acl_manager) {
+  void start(
+      os::Handler* handler,
+      hci::HciLayer* hci_layer,
+      hci::Controller* controller,
+      hci::AclManager* acl_manager,
+      hci::VendorSpecificEventManager* vendor_specific_event_manager) {
     module_handler_ = handler;
     hci_layer_ = hci_layer;
     controller_ = controller;
     le_maximum_advertising_data_length_ = controller_->GetLeMaximumAdvertisingDataLength();
     acl_manager_ = acl_manager;
     le_address_manager_ = acl_manager->GetLeAddressManager();
+    num_instances_ = controller_->GetLeNumberOfSupportedAdverisingSets();
+
     le_advertising_interface_ =
         hci_layer_->GetLeAdvertisingInterface(module_handler_->BindOn(this, &LeAdvertisingManager::impl::handle_event));
-    num_instances_ = controller_->GetLeNumberOfSupportedAdverisingSets();
+    vendor_specific_event_manager->RegisterEventHandler(
+        hci::VseSubeventCode::BLE_STCHANGE,
+        handler->BindOn(this, &LeAdvertisingManager::impl::multi_advertising_state_change));
 
     if (controller_->SupportsBleExtendedAdvertising()) {
       advertising_api_type_ = AdvertisingApiType::EXTENDED;
@@ -152,6 +161,33 @@
     advertising_callbacks_ = advertising_callback;
   }
 
+  void multi_advertising_state_change(hci::VendorSpecificEventView event) {
+    auto view = hci::LEAdvertiseStateChangeEventView::Create(event);
+    ASSERT(view.IsValid());
+
+    auto advertiser_id = view.GetAdvertisingInstance();
+
+    LOG_INFO(
+        "Instance: 0x%x StateChangeReason: 0x%s Handle: 0x%x Address: %s",
+        advertiser_id,
+        VseStateChangeReasonText(view.GetStateChangeReason()).c_str(),
+        view.GetConnectionHandle(),
+        advertising_sets_[view.GetAdvertisingInstance()].current_address.ToString().c_str());
+
+    if (view.GetStateChangeReason() == VseStateChangeReason::CONNECTION_RECEIVED) {
+      acl_manager_->OnAdvertisingSetTerminated(
+          ErrorCode::SUCCESS, view.GetConnectionHandle(), advertising_sets_[advertiser_id].current_address);
+
+      enabled_sets_[advertiser_id].advertising_handle_ = kInvalidHandle;
+
+      if (!advertising_sets_[advertiser_id].directed) {
+        // TODO(250666237) calculate remaining duration and advertising events
+        LOG_INFO("Resuming advertising, since not directed");
+        enable_advertiser(advertiser_id, true, 0, 0);
+      }
+    }
+  }
+
   void handle_event(LeMetaEventView event) {
     switch (event.GetSubeventCode()) {
       case hci::SubeventCode::SCAN_REQUEST_RECEIVED:
@@ -1374,11 +1410,16 @@
   list->add<hci::HciLayer>();
   list->add<hci::Controller>();
   list->add<hci::AclManager>();
+  list->add<hci::VendorSpecificEventManager>();
 }
 
 void LeAdvertisingManager::Start() {
-  pimpl_->start(GetHandler(), GetDependency<hci::HciLayer>(), GetDependency<hci::Controller>(),
-                GetDependency<AclManager>());
+  pimpl_->start(
+      GetHandler(),
+      GetDependency<hci::HciLayer>(),
+      GetDependency<hci::Controller>(),
+      GetDependency<AclManager>(),
+      GetDependency<VendorSpecificEventManager>());
 }
 
 void LeAdvertisingManager::Stop() {
diff --git a/system/gd/hci/le_scanning_manager_test.cc b/system/gd/hci/le_scanning_manager_test.cc
index 1d70ef5..8671958 100644
--- a/system/gd/hci/le_scanning_manager_test.cc
+++ b/system/gd/hci/le_scanning_manager_test.cc
@@ -14,34 +14,101 @@
  * limitations under the License.
  */
 
+#include "hci/le_scanning_manager.h"
+
 #include <gmock/gmock.h>
 #include <gtest/gtest.h>
 
 #include <algorithm>
 #include <chrono>
 #include <future>
+#include <list>
 #include <map>
+#include <memory>
+#include <mutex>
+#include <queue>
+#include <vector>
 
 #include "common/bind.h"
 #include "hci/acl_manager.h"
 #include "hci/address.h"
 #include "hci/controller.h"
 #include "hci/hci_layer.h"
-#include "hci/le_scanning_manager.h"
+#include "hci/uuid.h"
 #include "os/thread.h"
 #include "packet/raw_builder.h"
 
 using ::testing::_;
 using ::testing::Eq;
 
-namespace bluetooth {
-namespace hci {
-namespace {
+using namespace bluetooth;
+using namespace std::chrono_literals;
 
 using packet::kLittleEndian;
 using packet::PacketView;
 using packet::RawBuilder;
 
+namespace {
+
+hci::AdvertisingPacketContentFilterCommand make_filter(const hci::ApcfFilterType& filter_type) {
+  hci::AdvertisingPacketContentFilterCommand filter{};
+  filter.filter_type = filter_type;
+
+  switch (filter_type) {
+    case hci::ApcfFilterType::AD_TYPE:
+    case hci::ApcfFilterType::SERVICE_DATA:
+      filter.ad_type = 0x09;
+      filter.data = {0x12, 0x34, 0x56, 0x78};
+      filter.data_mask = {0xff, 0xff, 0xff, 0xff};
+      break;
+    case hci::ApcfFilterType::BROADCASTER_ADDRESS:
+      filter.address = hci::Address::kEmpty;
+      filter.application_address_type = hci::ApcfApplicationAddressType::RANDOM;
+      break;
+    case hci::ApcfFilterType::SERVICE_UUID:
+      filter.uuid = hci::Uuid::From32Bit(0x12345678);
+      filter.uuid_mask = hci::Uuid::From32Bit(0xffffffff);
+      break;
+    case hci::ApcfFilterType::LOCAL_NAME:
+      filter.name = {0x01, 0x02, 0x03};
+      break;
+    case hci::ApcfFilterType::MANUFACTURER_DATA:
+      filter.company = 0x12;
+      filter.company_mask = 0xff;
+      filter.data = {0x12, 0x34, 0x56, 0x78};
+      filter.data_mask = {0xff, 0xff, 0xff, 0xff};
+      break;
+    default:
+      break;
+  }
+  return filter;
+}
+
+hci::LeAdvertisingResponse make_advertising_report() {
+  hci::LeAdvertisingResponse report{};
+  report.event_type_ = hci::AdvertisingEventType::ADV_DIRECT_IND;
+  report.address_type_ = hci::AddressType::PUBLIC_DEVICE_ADDRESS;
+  hci::Address::FromString("12:34:56:78:9a:bc", report.address_);
+  std::vector<hci::LengthAndData> adv_data{};
+  hci::LengthAndData data_item{};
+  data_item.data_.push_back(static_cast<uint8_t>(hci::GapDataType::FLAGS));
+  data_item.data_.push_back(0x34);
+  adv_data.push_back(data_item);
+  data_item.data_.push_back(static_cast<uint8_t>(hci::GapDataType::COMPLETE_LOCAL_NAME));
+  for (auto octet : {'r', 'a', 'n', 'd', 'o', 'm', ' ', 'd', 'e', 'v', 'i', 'c', 'e'}) {
+    data_item.data_.push_back(octet);
+  }
+  adv_data.push_back(data_item);
+  report.advertising_data_ = adv_data;
+  return report;
+}
+
+}  // namespace
+
+namespace bluetooth {
+namespace hci {
+namespace {
+
 PacketView<kLittleEndian> GetPacketView(std::unique_ptr<packet::BasePacketBuilder> packet) {
   auto bytes = std::make_shared<std::vector<uint8_t>>();
   BitInserter i(*bytes);
@@ -98,17 +165,21 @@
   }
 
   // Set command future for 'num_command' commands are expected
-  void SetCommandFuture(uint16_t num_command) {
+  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_ = std::make_unique<std::future<void>>(command_promise_->get_future());
+    command_future_ = command_promise_->get_future();
   }
 
   CommandView GetCommand() {
     // Wait for EnqueueCommand if command_queue_ is empty
-    if (command_queue_.empty() && command_future_ != nullptr) {
-      command_future_->wait_for(std::chrono::milliseconds(1000));
+    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_);
@@ -166,6 +237,7 @@
   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();
   }
@@ -191,7 +263,14 @@
         EventCode::COMMAND_COMPLETE, GetHandler()->BindOn(this, &TestHciLayer::CommandCompleteCallback));
     RegisterEventHandler(EventCode::COMMAND_STATUS, GetHandler()->BindOn(this, &TestHciLayer::CommandStatusCallback));
   }
-  void Stop() override {}
+  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_;
@@ -200,7 +279,7 @@
   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::unique_ptr<std::future<void>> command_future_;
+  std::future<void> command_future_;
   mutable std::mutex mutex_;
   uint16_t command_count_ = 0;
   CommandView empty_command_view_ =
@@ -252,52 +331,86 @@
 
   void enqueue_command(std::unique_ptr<CommandBuilder> command_packet){};
 
+ private:
   os::Thread* thread_;
   os::Handler* handler_;
   TestLeAddressManager* test_le_address_manager_;
 };
 
+class MockCallbacks : public bluetooth::hci::ScanningCallback {
+ public:
+  MOCK_METHOD(
+      void,
+      OnScannerRegistered,
+      (const bluetooth::hci::Uuid app_uuid, ScannerId scanner_id, ScanningStatus status),
+      (override));
+  MOCK_METHOD(void, OnSetScannerParameterComplete, (ScannerId scanner_id, ScanningStatus status), (override));
+  MOCK_METHOD(
+      void,
+      OnScanResult,
+      (uint16_t event_type,
+       uint8_t address_type,
+       Address address,
+       uint8_t primary_phy,
+       uint8_t secondary_phy,
+       uint8_t advertising_sid,
+       int8_t tx_power,
+       int8_t rssi,
+       uint16_t periodic_advertising_interval,
+       std::vector<uint8_t> advertising_data),
+      (override));
+  MOCK_METHOD(
+      void,
+      OnTrackAdvFoundLost,
+      (bluetooth::hci::AdvertisingFilterOnFoundOnLostInfo on_found_on_lost_info),
+      (override));
+  MOCK_METHOD(
+      void,
+      OnBatchScanReports,
+      (int client_if, int status, int report_format, int num_records, std::vector<uint8_t> data),
+      (override));
+  MOCK_METHOD(void, OnBatchScanThresholdCrossed, (int client_if), (override));
+  MOCK_METHOD(void, OnTimeout, (), (override));
+  MOCK_METHOD(void, OnFilterEnable, (Enable enable, uint8_t status), (override));
+  MOCK_METHOD(void, OnFilterParamSetup, (uint8_t available_spaces, ApcfAction action, uint8_t status), (override));
+  MOCK_METHOD(
+      void,
+      OnFilterConfigCallback,
+      (ApcfFilterType filter_type, uint8_t available_spaces, ApcfAction action, uint8_t status),
+      (override));
+  MOCK_METHOD(void, OnPeriodicSyncStarted, (int, uint8_t, uint16_t, uint8_t, AddressWithType, uint8_t, uint16_t));
+  MOCK_METHOD(void, OnPeriodicSyncReport, (uint16_t, int8_t, int8_t, uint8_t, std::vector<uint8_t>));
+  MOCK_METHOD(void, OnPeriodicSyncLost, (uint16_t));
+  MOCK_METHOD(void, OnPeriodicSyncTransferred, (int, uint8_t, Address));
+} mock_callbacks_;
+
 class LeScanningManagerTest : public ::testing::Test {
  protected:
   void SetUp() override {
     test_hci_layer_ = new TestHciLayer;  // Ownership is transferred to registry
     test_controller_ = new TestController;
-    test_controller_->AddSupported(param_opcode_);
-    if (is_filter_support_) {
-      test_controller_->AddSupported(OpCode::LE_ADV_FILTER);
-    }
-    if (is_batch_scan_support_) {
-      test_controller_->AddSupported(OpCode::LE_BATCH_SCAN);
-    }
     test_acl_manager_ = new TestAclManager;
     fake_registry_.InjectTestModule(&HciLayer::Factory, test_hci_layer_);
     fake_registry_.InjectTestModule(&Controller::Factory, test_controller_);
     fake_registry_.InjectTestModule(&AclManager::Factory, test_acl_manager_);
     client_handler_ = fake_registry_.GetTestModuleHandler(&HciLayer::Factory);
-    ASSERT_NE(client_handler_, nullptr);
-    if (is_filter_support_) {
-      // Will send APCF read extended features command if APCF supported
-      test_hci_layer_->SetCommandFuture(2);
-    } else {
-      test_hci_layer_->SetCommandFuture(1);
-    }
-    // configure_scan will be trigger by impl.start() and enqueue set scan parameter command
-    fake_registry_.Start<LeScanningManager>(&thread_);
-    le_scanning_manager =
-        static_cast<LeScanningManager*>(fake_registry_.GetModuleUnderTest(&LeScanningManager::Factory));
-    HandleConfiguration();
-    le_scanning_manager->RegisterScanningCallback(&mock_callbacks_);
+    ASSERT_TRUE(client_handler_ != nullptr);
   }
 
   void TearDown() override {
     sync_client_handler();
-    fake_registry_.SynchronizeModuleHandler(&LeScanningManager::Factory, std::chrono::milliseconds(20));
+    if (fake_registry_.IsStarted<LeScanningManager>()) {
+      fake_registry_.SynchronizeModuleHandler(&LeScanningManager::Factory, std::chrono::milliseconds(20));
+    }
     fake_registry_.StopAll();
   }
 
-  virtual void HandleConfiguration() {
-    ASSERT_EQ(param_opcode_, test_hci_layer_->GetCommand().GetOpCode());
-    test_hci_layer_->IncomingEvent(LeSetScanParametersCompleteBuilder::Create(1, ErrorCode::SUCCESS));
+  void start_le_scanning_manager() {
+    fake_registry_.Start<LeScanningManager>(&thread_);
+    le_scanning_manager =
+        static_cast<LeScanningManager*>(fake_registry_.GetModuleUnderTest(&LeScanningManager::Factory));
+    le_scanning_manager->RegisterScanningCallback(&mock_callbacks_);
+    sync_client_handler();
   }
 
   void sync_client_handler() {
@@ -316,193 +429,106 @@
   LeScanningManager* le_scanning_manager = nullptr;
   os::Handler* client_handler_ = nullptr;
 
-  class MockCallbacks : public bluetooth::hci::ScanningCallback {
-   public:
-    MOCK_METHOD(
-        void,
-        OnScannerRegistered,
-        (const bluetooth::hci::Uuid app_uuid, ScannerId scanner_id, ScanningStatus status),
-        (override));
-    MOCK_METHOD(void, OnSetScannerParameterComplete, (ScannerId scanner_id, ScanningStatus status), (override));
-    MOCK_METHOD(
-        void,
-        OnScanResult,
-        (uint16_t event_type,
-         uint8_t address_type,
-         Address address,
-         uint8_t primary_phy,
-         uint8_t secondary_phy,
-         uint8_t advertising_sid,
-         int8_t tx_power,
-         int8_t rssi,
-         uint16_t periodic_advertising_interval,
-         std::vector<uint8_t> advertising_data),
-        (override));
-    MOCK_METHOD(
-        void,
-        OnTrackAdvFoundLost,
-        (bluetooth::hci::AdvertisingFilterOnFoundOnLostInfo on_found_on_lost_info),
-        (override));
-    MOCK_METHOD(
-        void,
-        OnBatchScanReports,
-        (int client_if, int status, int report_format, int num_records, std::vector<uint8_t> data),
-        (override));
-    MOCK_METHOD(void, OnBatchScanThresholdCrossed, (int client_if), (override));
-    MOCK_METHOD(void, OnTimeout, (), (override));
-    MOCK_METHOD(void, OnFilterEnable, (Enable enable, uint8_t status), (override));
-    MOCK_METHOD(void, OnFilterParamSetup, (uint8_t available_spaces, ApcfAction action, uint8_t status), (override));
-    MOCK_METHOD(
-        void,
-        OnFilterConfigCallback,
-        (ApcfFilterType filter_type, uint8_t available_spaces, ApcfAction action, uint8_t status),
-        (override));
-    MOCK_METHOD(void, OnPeriodicSyncStarted, (int, uint8_t, uint16_t, uint8_t, AddressWithType, uint8_t, uint16_t));
-    MOCK_METHOD(void, OnPeriodicSyncReport, (uint16_t, int8_t, int8_t, uint8_t, std::vector<uint8_t>));
-    MOCK_METHOD(void, OnPeriodicSyncLost, (uint16_t));
-    MOCK_METHOD(void, OnPeriodicSyncTransferred, (int, uint8_t, Address));
-  } mock_callbacks_;
-
-  OpCode param_opcode_{OpCode::LE_SET_SCAN_PARAMETERS};
-  OpCode enable_opcode_{OpCode::LE_SET_SCAN_ENABLE};
-  bool is_filter_support_ = false;
-  bool is_batch_scan_support_ = false;
+  MockCallbacks mock_callbacks_;
 };
 
-class LeAndroidHciScanningManagerTest : public LeScanningManagerTest {
+class LeScanningManagerAndroidHciTest : public LeScanningManagerTest {
  protected:
   void SetUp() override {
-    param_opcode_ = OpCode::LE_EXTENDED_SCAN_PARAMS;
-    is_filter_support_ = true;
-    is_batch_scan_support_ = true;
     LeScanningManagerTest::SetUp();
-    sync_client_handler();
-  }
+    test_controller_->AddSupported(OpCode::LE_EXTENDED_SCAN_PARAMS);
+    test_controller_->AddSupported(OpCode::LE_ADV_FILTER);
+    test_controller_->AddSupported(OpCode::LE_BATCH_SCAN);
+    start_le_scanning_manager();
+    ASSERT_TRUE(fake_registry_.IsStarted(&HciLayer::Factory));
 
-  void HandleConfiguration() override {
+    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));
-    sync_client_handler();
-    ASSERT_EQ(param_opcode_, test_hci_layer_->GetCommand().GetOpCode());
-    test_hci_layer_->IncomingEvent(LeExtendedScanParamsCompleteBuilder::Create(1, ErrorCode::SUCCESS));
+  }
+
+  void TearDown() override {
+    LeScanningManagerTest::TearDown();
   }
 };
 
-class LeExtendedScanningManagerTest : public LeScanningManagerTest {
+class LeScanningManagerExtendedTest : public LeScanningManagerTest {
  protected:
   void SetUp() override {
-    param_opcode_ = OpCode::LE_SET_EXTENDED_SCAN_PARAMETERS;
-    enable_opcode_ = OpCode::LE_SET_EXTENDED_SCAN_ENABLE;
     LeScanningManagerTest::SetUp();
-  }
-
-  void HandleConfiguration() override {
-    ASSERT_EQ(param_opcode_, test_hci_layer_->GetCommand().GetOpCode());
-    test_hci_layer_->IncomingEvent(LeSetExtendedScanParametersCompleteBuilder::Create(1, ErrorCode::SUCCESS));
+    test_controller_->AddSupported(OpCode::LE_SET_EXTENDED_SCAN_PARAMETERS);
+    test_controller_->AddSupported(OpCode::LE_SET_EXTENDED_SCAN_ENABLE);
+    start_le_scanning_manager();
   }
 };
 
 TEST_F(LeScanningManagerTest, startup_teardown) {}
 
 TEST_F(LeScanningManagerTest, start_scan_test) {
-  // Enable scan
+  start_le_scanning_manager();
+
   test_hci_layer_->SetCommandFuture(2);
+  // Enable scan
   le_scanning_manager->Scan(true);
-  ASSERT_EQ(param_opcode_, test_hci_layer_->GetCommand().GetOpCode());
+  ASSERT_EQ(OpCode::LE_SET_SCAN_PARAMETERS, test_hci_layer_->GetCommand().GetOpCode());
   test_hci_layer_->IncomingEvent(LeSetScanParametersCompleteBuilder::Create(uint8_t{1}, ErrorCode::SUCCESS));
-  ASSERT_EQ(enable_opcode_, test_hci_layer_->GetCommand().GetOpCode());
+  ASSERT_EQ(OpCode::LE_SET_SCAN_ENABLE, test_hci_layer_->GetCommand().GetOpCode());
   test_hci_layer_->IncomingEvent(LeSetScanEnableCompleteBuilder::Create(uint8_t{1}, ErrorCode::SUCCESS));
 
-  LeAdvertisingResponse report{};
-  report.event_type_ = AdvertisingEventType::ADV_DIRECT_IND;
-  report.address_type_ = AddressType::PUBLIC_DEVICE_ADDRESS;
-  Address::FromString("12:34:56:78:9a:bc", report.address_);
-  std::vector<LengthAndData> adv_data{};
-  LengthAndData data_item{};
-  data_item.data_.push_back(static_cast<uint8_t>(GapDataType::FLAGS));
-  data_item.data_.push_back(0x34);
-  adv_data.push_back(data_item);
-  data_item.data_.push_back(static_cast<uint8_t>(GapDataType::COMPLETE_LOCAL_NAME));
-  for (auto octet : {'r', 'a', 'n', 'd', 'o', 'm', ' ', 'd', 'e', 'v', 'i', 'c', 'e'}) {
-    data_item.data_.push_back(octet);
-  }
-  adv_data.push_back(data_item);
-  report.advertising_data_ = adv_data;
-
+  LeAdvertisingResponse report = make_advertising_report();
   EXPECT_CALL(mock_callbacks_, OnScanResult);
 
   test_hci_layer_->IncomingLeMetaEvent(LeAdvertisingReportBuilder::Create({report}));
 }
 
 TEST_F(LeScanningManagerTest, is_ad_type_filter_supported_false_test) {
+  start_le_scanning_manager();
+  ASSERT_TRUE(fake_registry_.IsStarted(&HciLayer::Factory));
   ASSERT_FALSE(le_scanning_manager->IsAdTypeFilterSupported());
 }
 
 TEST_F(LeScanningManagerTest, scan_filter_add_ad_type_not_supported_test) {
-  test_hci_layer_->SetCommandFuture(1);
+  start_le_scanning_manager();
+  ASSERT_TRUE(fake_registry_.IsStarted(&HciLayer::Factory));
+  test_hci_layer_->SetCommandFuture();
   std::vector<AdvertisingPacketContentFilterCommand> filters = {};
-  AdvertisingPacketContentFilterCommand filter{};
-  filter.filter_type = ApcfFilterType::AD_TYPE;
-  filter.ad_type = 0x09;
-  filter.data = {0x12, 0x34, 0x56, 0x78};
-  filter.data_mask = {0xff, 0xff, 0xff, 0xff};
-  filters.push_back(filter);
+  filters.push_back(make_filter(hci::ApcfFilterType::AD_TYPE));
   le_scanning_manager->ScanFilterAdd(0x01, filters);
 }
 
-TEST_F(LeAndroidHciScanningManagerTest, startup_teardown) {}
+TEST_F(LeScanningManagerAndroidHciTest, startup_teardown) {}
 
-TEST_F(LeAndroidHciScanningManagerTest, start_scan_test) {
-  // Enable scan
+TEST_F(LeScanningManagerAndroidHciTest, start_scan_test) {
   test_hci_layer_->SetCommandFuture(2);
+  // Enable scan
   le_scanning_manager->Scan(true);
-  ASSERT_EQ(param_opcode_, test_hci_layer_->GetCommand().GetOpCode());
-  test_hci_layer_->IncomingEvent(LeSetScanParametersCompleteBuilder::Create(uint8_t{1}, ErrorCode::SUCCESS));
-  ASSERT_EQ(enable_opcode_, test_hci_layer_->GetCommand().GetOpCode());
-  test_hci_layer_->IncomingEvent(LeSetScanEnableCompleteBuilder::Create(uint8_t{1}, ErrorCode::SUCCESS));
+  ASSERT_EQ(OpCode::LE_EXTENDED_SCAN_PARAMS, test_hci_layer_->GetCommand().GetOpCode());
 
-  LeAdvertisingResponse report{};
-  report.event_type_ = AdvertisingEventType::ADV_DIRECT_IND;
-  report.address_type_ = AddressType::PUBLIC_DEVICE_ADDRESS;
-  Address::FromString("12:34:56:78:9a:bc", report.address_);
-  std::vector<LengthAndData> adv_data{};
-  LengthAndData data_item{};
-  data_item.data_.push_back(static_cast<uint8_t>(GapDataType::FLAGS));
-  data_item.data_.push_back(0x34);
-  adv_data.push_back(data_item);
-  data_item.data_.push_back(static_cast<uint8_t>(GapDataType::COMPLETE_LOCAL_NAME));
-  for (auto octet : {'r', 'a', 'n', 'd', 'o', 'm', ' ', 'd', 'e', 'v', 'i', 'c', 'e'}) {
-    data_item.data_.push_back(octet);
-  }
-  adv_data.push_back(data_item);
-  report.advertising_data_ = adv_data;
+  LeAdvertisingResponse report = make_advertising_report();
 
   EXPECT_CALL(mock_callbacks_, OnScanResult);
 
   test_hci_layer_->IncomingLeMetaEvent(LeAdvertisingReportBuilder::Create({report}));
 }
 
-TEST_F(LeAndroidHciScanningManagerTest, is_ad_type_filter_supported_true_test) {
-  ASSERT_TRUE(le_scanning_manager->IsAdTypeFilterSupported());
+TEST_F(LeScanningManagerAndroidHciTest, is_ad_type_filter_supported_true_test) {
+  sync_client_handler();
+  client_handler_->Post(common::BindOnce(
+      [](LeScanningManager* le_scanning_manager) { ASSERT_TRUE(le_scanning_manager->IsAdTypeFilterSupported()); },
+      le_scanning_manager));
 }
 
-TEST_F(LeAndroidHciScanningManagerTest, scan_filter_enable_test) {
-  test_hci_layer_->SetCommandFuture(1);
+TEST_F(LeScanningManagerAndroidHciTest, scan_filter_enable_test) {
   le_scanning_manager->ScanFilterEnable(true);
-  auto commandView = test_hci_layer_->GetCommand();
-  ASSERT_EQ(OpCode::LE_ADV_FILTER, commandView.GetOpCode());
-  auto filter_command_view =
-      LeAdvFilterEnableView::Create(LeAdvFilterView::Create(LeScanningCommandView::Create(commandView)));
-  ASSERT_TRUE(filter_command_view.IsValid());
-  ASSERT_EQ(filter_command_view.GetApcfOpcode(), ApcfOpcode::ENABLE);
 
   EXPECT_CALL(mock_callbacks_, OnFilterEnable);
   test_hci_layer_->IncomingEvent(
       LeAdvFilterEnableCompleteBuilder::Create(uint8_t{1}, ErrorCode::SUCCESS, Enable::ENABLED));
 }
 
-TEST_F(LeAndroidHciScanningManagerTest, scan_filter_parameter_test) {
-  test_hci_layer_->SetCommandFuture(1);
+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);
@@ -518,14 +544,10 @@
       LeAdvFilterSetFilteringParametersCompleteBuilder::Create(uint8_t{1}, ErrorCode::SUCCESS, ApcfAction::ADD, 0x0a));
 }
 
-TEST_F(LeAndroidHciScanningManagerTest, scan_filter_add_broadcaster_address_test) {
-  test_hci_layer_->SetCommandFuture(1);
+TEST_F(LeScanningManagerAndroidHciTest, scan_filter_add_broadcaster_address_test) {
+  test_hci_layer_->SetCommandFuture();
   std::vector<AdvertisingPacketContentFilterCommand> filters = {};
-  AdvertisingPacketContentFilterCommand filter{};
-  filter.filter_type = ApcfFilterType::BROADCASTER_ADDRESS;
-  filter.address = Address::kEmpty;
-  filter.application_address_type = ApcfApplicationAddressType::RANDOM;
-  filters.push_back(filter);
+  filters.push_back(make_filter(ApcfFilterType::BROADCASTER_ADDRESS));
   le_scanning_manager->ScanFilterAdd(0x01, filters);
   auto commandView = test_hci_layer_->GetCommand();
   ASSERT_EQ(OpCode::LE_ADV_FILTER, commandView.GetOpCode());
@@ -539,14 +561,10 @@
       LeAdvFilterBroadcasterAddressCompleteBuilder::Create(uint8_t{1}, ErrorCode::SUCCESS, ApcfAction::ADD, 0x0a));
 }
 
-TEST_F(LeAndroidHciScanningManagerTest, scan_filter_add_service_uuid_test) {
-  test_hci_layer_->SetCommandFuture(1);
+TEST_F(LeScanningManagerAndroidHciTest, scan_filter_add_service_uuid_test) {
+  test_hci_layer_->SetCommandFuture();
   std::vector<AdvertisingPacketContentFilterCommand> filters = {};
-  AdvertisingPacketContentFilterCommand filter{};
-  filter.filter_type = ApcfFilterType::SERVICE_UUID;
-  filter.uuid = Uuid::From32Bit(0x12345678);
-  filter.uuid_mask = Uuid::From32Bit(0xffffffff);
-  filters.push_back(filter);
+  filters.push_back(make_filter(ApcfFilterType::SERVICE_UUID));
   le_scanning_manager->ScanFilterAdd(0x01, filters);
   auto commandView = test_hci_layer_->GetCommand();
   ASSERT_EQ(OpCode::LE_ADV_FILTER, commandView.GetOpCode());
@@ -560,13 +578,10 @@
       LeAdvFilterServiceUuidCompleteBuilder::Create(uint8_t{1}, ErrorCode::SUCCESS, ApcfAction::ADD, 0x0a));
 }
 
-TEST_F(LeAndroidHciScanningManagerTest, scan_filter_add_local_name_test) {
-  test_hci_layer_->SetCommandFuture(1);
+TEST_F(LeScanningManagerAndroidHciTest, scan_filter_add_local_name_test) {
+  test_hci_layer_->SetCommandFuture();
   std::vector<AdvertisingPacketContentFilterCommand> filters = {};
-  AdvertisingPacketContentFilterCommand filter{};
-  filter.filter_type = ApcfFilterType::LOCAL_NAME;
-  filter.name = {0x01, 0x02, 0x03};
-  filters.push_back(filter);
+  filters.push_back(make_filter(ApcfFilterType::LOCAL_NAME));
   le_scanning_manager->ScanFilterAdd(0x01, filters);
   auto commandView = test_hci_layer_->GetCommand();
   ASSERT_EQ(OpCode::LE_ADV_FILTER, commandView.GetOpCode());
@@ -580,16 +595,10 @@
       LeAdvFilterLocalNameCompleteBuilder::Create(uint8_t{1}, ErrorCode::SUCCESS, ApcfAction::ADD, 0x0a));
 }
 
-TEST_F(LeAndroidHciScanningManagerTest, scan_filter_add_manufacturer_data_test) {
-  test_hci_layer_->SetCommandFuture(1);
+TEST_F(LeScanningManagerAndroidHciTest, scan_filter_add_manufacturer_data_test) {
+  test_hci_layer_->SetCommandFuture();
   std::vector<AdvertisingPacketContentFilterCommand> filters = {};
-  AdvertisingPacketContentFilterCommand filter{};
-  filter.filter_type = ApcfFilterType::MANUFACTURER_DATA;
-  filter.company = 0x12;
-  filter.company_mask = 0xff;
-  filter.data = {0x12, 0x34, 0x56, 0x78};
-  filter.data_mask = {0xff, 0xff, 0xff, 0xff};
-  filters.push_back(filter);
+  filters.push_back(make_filter(ApcfFilterType::MANUFACTURER_DATA));
   le_scanning_manager->ScanFilterAdd(0x01, filters);
   auto commandView = test_hci_layer_->GetCommand();
   ASSERT_EQ(OpCode::LE_ADV_FILTER, commandView.GetOpCode());
@@ -603,14 +612,10 @@
       LeAdvFilterManufacturerDataCompleteBuilder::Create(uint8_t{1}, ErrorCode::SUCCESS, ApcfAction::ADD, 0x0a));
 }
 
-TEST_F(LeAndroidHciScanningManagerTest, scan_filter_add_service_data_test) {
-  test_hci_layer_->SetCommandFuture(1);
+TEST_F(LeScanningManagerAndroidHciTest, scan_filter_add_service_data_test) {
+  test_hci_layer_->SetCommandFuture();
   std::vector<AdvertisingPacketContentFilterCommand> filters = {};
-  AdvertisingPacketContentFilterCommand filter{};
-  filter.filter_type = ApcfFilterType::SERVICE_DATA;
-  filter.data = {0x12, 0x34, 0x56, 0x78};
-  filter.data_mask = {0xff, 0xff, 0xff, 0xff};
-  filters.push_back(filter);
+  filters.push_back(make_filter(hci::ApcfFilterType::SERVICE_DATA));
   le_scanning_manager->ScanFilterAdd(0x01, filters);
   auto commandView = test_hci_layer_->GetCommand();
   ASSERT_EQ(OpCode::LE_ADV_FILTER, commandView.GetOpCode());
@@ -624,32 +629,26 @@
       LeAdvFilterServiceDataCompleteBuilder::Create(uint8_t{1}, ErrorCode::SUCCESS, ApcfAction::ADD, 0x0a));
 }
 
-TEST_F(LeAndroidHciScanningManagerTest, scan_filter_add_ad_type_test) {
-  test_hci_layer_->SetCommandFuture(1);
+TEST_F(LeScanningManagerAndroidHciTest, scan_filter_add_ad_type_test) {
+  sync_client_handler();
+  client_handler_->Post(common::BindOnce(
+      [](LeScanningManager* le_scanning_manager) { ASSERT_TRUE(le_scanning_manager->IsAdTypeFilterSupported()); },
+      le_scanning_manager));
+
   std::vector<AdvertisingPacketContentFilterCommand> filters = {};
-  AdvertisingPacketContentFilterCommand filter{};
-  filter.filter_type = ApcfFilterType::AD_TYPE;
-  filter.ad_type = 0x09;
-  filter.data = {0x12, 0x34, 0x56, 0x78};
-  filter.data_mask = {0xff, 0xff, 0xff, 0xff};
+  hci::AdvertisingPacketContentFilterCommand filter = make_filter(hci::ApcfFilterType::AD_TYPE);
   filters.push_back(filter);
   le_scanning_manager->ScanFilterAdd(0x01, filters);
-  auto commandView = test_hci_layer_->GetCommand();
-  ASSERT_EQ(OpCode::LE_ADV_FILTER, commandView.GetOpCode());
-  auto filter_command_view =
-      LeAdvFilterADTypeView::Create(LeAdvFilterView::Create(LeScanningCommandView::Create(commandView)));
-  ASSERT_TRUE(filter_command_view.IsValid());
-  ASSERT_EQ(filter_command_view.GetApcfOpcode(), ApcfOpcode::AD_TYPE);
+  sync_client_handler();
 
   EXPECT_CALL(mock_callbacks_, OnFilterConfigCallback);
   test_hci_layer_->IncomingEvent(
       LeAdvFilterADTypeCompleteBuilder::Create(uint8_t{1}, ErrorCode::SUCCESS, ApcfAction::ADD, 0x0a));
 }
 
-TEST_F(LeAndroidHciScanningManagerTest, read_batch_scan_result) {
-  // Enable batch scan feature
-  test_hci_layer_->SetCommandFuture(2);
+TEST_F(LeScanningManagerAndroidHciTest, read_batch_scan_result) {
   le_scanning_manager->BatchScanConifgStorage(100, 0, 95, 0x00);
+  sync_client_handler();
   ASSERT_EQ(OpCode::LE_BATCH_SCAN, test_hci_layer_->GetCommand().GetOpCode());
   test_hci_layer_->IncomingEvent(LeBatchScanEnableCompleteBuilder::Create(uint8_t{1}, ErrorCode::SUCCESS));
   ASSERT_EQ(OpCode::LE_BATCH_SCAN, test_hci_layer_->GetCommand().GetOpCode());
@@ -657,20 +656,20 @@
       LeBatchScanSetStorageParametersCompleteBuilder::Create(uint8_t{1}, ErrorCode::SUCCESS));
 
   // Enable batch scan
-  test_hci_layer_->SetCommandFuture(1);
+  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(1);
+  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(1);
+  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());
@@ -681,15 +680,15 @@
       uint8_t{1}, ErrorCode::SUCCESS, BatchScanDataRead::FULL_MODE_DATA, 0, {}));
 }
 
-TEST_F(LeExtendedScanningManagerTest, startup_teardown) {}
+TEST_F(LeScanningManagerExtendedTest, startup_teardown) {}
 
-TEST_F(LeExtendedScanningManagerTest, start_scan_test) {
+TEST_F(LeScanningManagerExtendedTest, start_scan_test) {
   // Enable scan
   test_hci_layer_->SetCommandFuture(2);
   le_scanning_manager->Scan(true);
-  ASSERT_EQ(param_opcode_, test_hci_layer_->GetCommand().GetOpCode());
+  ASSERT_EQ(OpCode::LE_SET_EXTENDED_SCAN_PARAMETERS, test_hci_layer_->GetCommand().GetOpCode());
   test_hci_layer_->IncomingEvent(LeSetExtendedScanParametersCompleteBuilder::Create(uint8_t{1}, ErrorCode::SUCCESS));
-  ASSERT_EQ(enable_opcode_, test_hci_layer_->GetCommand().GetOpCode());
+  ASSERT_EQ(OpCode::LE_SET_EXTENDED_SCAN_ENABLE, test_hci_layer_->GetCommand().GetOpCode());
   test_hci_layer_->IncomingEvent(LeSetExtendedScanEnableCompleteBuilder::Create(uint8_t{1}, ErrorCode::SUCCESS));
   LeExtendedAdvertisingResponse report{};
   report.connectable_ = 1;
@@ -714,13 +713,13 @@
   test_hci_layer_->IncomingLeMetaEvent(LeExtendedAdvertisingReportBuilder::Create({report}));
 }
 
-TEST_F(LeExtendedScanningManagerTest, drop_insignificant_bytes_test) {
+TEST_F(LeScanningManagerExtendedTest, drop_insignificant_bytes_test) {
   // Enable scan
   test_hci_layer_->SetCommandFuture(2);
   le_scanning_manager->Scan(true);
-  ASSERT_EQ(param_opcode_, test_hci_layer_->GetCommand().GetOpCode());
+  ASSERT_EQ(OpCode::LE_SET_EXTENDED_SCAN_PARAMETERS, test_hci_layer_->GetCommand().GetOpCode());
   test_hci_layer_->IncomingEvent(LeSetExtendedScanParametersCompleteBuilder::Create(uint8_t{1}, ErrorCode::SUCCESS));
-  ASSERT_EQ(enable_opcode_, test_hci_layer_->GetCommand().GetOpCode());
+  ASSERT_EQ(OpCode::LE_SET_EXTENDED_SCAN_ENABLE, test_hci_layer_->GetCommand().GetOpCode());
   test_hci_layer_->IncomingEvent(LeSetExtendedScanEnableCompleteBuilder::Create(uint8_t{1}, ErrorCode::SUCCESS));
 
   // Prepare advertisement report
diff --git a/system/gd/os/android/metrics.cc b/system/gd/os/android/metrics.cc
index d158ab4..db42fc5a 100644
--- a/system/gd/os/android/metrics.cc
+++ b/system/gd/os/android/metrics.cc
@@ -424,6 +424,76 @@
   }
 }
 
+void LogMetricBluetoothLocalSupportedFeatures(uint32_t page_num, uint64_t features) {
+  int ret = stats_write(BLUETOOTH_LOCAL_SUPPORTED_FEATURES_REPORTED, page_num, features);
+  if (ret < 0) {
+    LOG_WARN(
+        "Failed for LogMetricBluetoothLocalSupportedFeatures, "
+        "page_num %d, features %s, error %d",
+        page_num,
+        std::to_string(features).c_str(),
+        ret);
+  }
+}
+
+void LogMetricBluetoothLocalVersions(
+    uint32_t lmp_manufacturer_name,
+    uint8_t lmp_version,
+    uint32_t lmp_subversion,
+    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);
+  if (ret < 0) {
+    LOG_WARN(
+        "Failed for LogMetricBluetoothLocalVersions, "
+        "lmp_manufacturer_name %d, lmp_version %hhu, lmp_subversion %d, hci_version %hhu, hci_revision %d, error %d",
+        lmp_manufacturer_name,
+        lmp_version,
+        lmp_subversion,
+        hci_version,
+        hci_revision,
+        ret);
+  }
+}
+  
+void LogMetricBluetoothDisconnectionReasonReported(
+    uint32_t reason, const Address& address, uint32_t connection_handle) {
+  int metric_id = 0;
+  if (!address.IsEmpty()) {
+    metric_id = MetricIdManager::GetInstance().AllocateId(address);
+  }
+  int ret = stats_write(BLUETOOTH_DISCONNECTION_REASON_REPORTED, reason, metric_id, connection_handle);
+  if (ret < 0) {
+    LOG_WARN(
+        "Failed for LogMetricBluetoothDisconnectionReasonReported, "
+        "reason %d, metric_id %d, connection_handle %d, error %d",
+        reason,
+        metric_id,
+        connection_handle,
+        ret);
+  }
+}
+
+void LogMetricBluetoothRemoteSupportedFeatures(
+    const Address& address, uint32_t page, uint64_t features, uint32_t connection_handle) {
+  int metric_id = 0;
+  if (!address.IsEmpty()) {
+    metric_id = MetricIdManager::GetInstance().AllocateId(address);
+  }
+  int ret = stats_write(BLUETOOTH_REMOTE_SUPPORTED_FEATURES_REPORTED, metric_id, page, features, connection_handle);
+  if (ret < 0) {
+    LOG_WARN(
+        "Failed for LogMetricBluetoothRemoteSupportedFeatures, "
+        "metric_id %d, page %d, features %s, connection_handle %d, error %d",
+        metric_id,
+        page,
+        std::to_string(features).c_str(),
+        connection_handle,
+        ret);
+  }
+}
+
 void LogMetricBluetoothCodePathCounterMetrics(int32_t key, int64_t count) {
   int ret = stats_write(BLUETOOTH_CODE_PATH_COUNTER, key, count);
   if (ret < 0) {
diff --git a/system/gd/os/host/metrics.cc b/system/gd/os/host/metrics.cc
index b175714..955d22e 100644
--- a/system/gd/os/host/metrics.cc
+++ b/system/gd/os/host/metrics.cc
@@ -103,6 +103,21 @@
 void LogMetricBluetoothHalCrashReason(
     const Address& address, uint32_t error_code, uint32_t vendor_error_code) {}
 
+void LogMetricBluetoothLocalSupportedFeatures(uint32_t page_num, uint64_t features) {}
+
+void LogMetricBluetoothLocalVersions(
+    uint32_t lmp_manufacturer_name,
+    uint8_t lmp_version,
+    uint32_t lmp_subversion,
+    uint8_t hci_version,
+    uint32_t hci_reversion) {}
+
+void LogMetricBluetoothDisconnectionReasonReported(
+    uint32_t reason, const Address& address, uint32_t connection_handle) {}
+
+void LogMetricBluetoothRemoteSupportedFeatures(
+    const Address& address, uint32_t page, uint64_t features, uint32_t connection_handle) {}
+
 void LogMetricBluetoothCodePathCounterMetrics(int32_t key, int64_t count) {}
 }  // namespace os
 }  // namespace bluetooth
diff --git a/system/gd/os/linux/metrics.cc b/system/gd/os/linux/metrics.cc
index 37bd673..65fb181 100644
--- a/system/gd/os/linux/metrics.cc
+++ b/system/gd/os/linux/metrics.cc
@@ -103,6 +103,21 @@
 void LogMetricBluetoothHalCrashReason(
     const Address& address, uint32_t error_code, uint32_t vendor_error_code) {}
 
+void LogMetricBluetoothLocalSupportedFeatures(uint32_t page_num, uint64_t features) {}
+
+void LogMetricBluetoothLocalVersions(
+    uint32_t lmp_manufacturer_name,
+    uint8_t lmp_version,
+    uint32_t lmp_subversion,
+    uint8_t hci_version,
+    uint32_t hci_revision) {}
+
+void LogMetricBluetoothDisconnectionReasonReported(
+    uint32_t reason, const Address& address, uint32_t connection_handle) {}
+
+void LogMetricBluetoothRemoteSupportedFeatures(
+    const Address& address, uint32_t page, uint64_t features, uint32_t connection_handle) {}
+
 void LogMetricBluetoothCodePathCounterMetrics(int32_t key, int64_t count) {}
 
 }  // namespace os
diff --git a/system/gd/os/metrics.h b/system/gd/os/metrics.h
index 3b1ae51..339a2cf 100644
--- a/system/gd/os/metrics.h
+++ b/system/gd/os/metrics.h
@@ -265,6 +265,21 @@
     uint32_t error_code,
     uint32_t vendor_error_code);
 
+void LogMetricBluetoothLocalSupportedFeatures(uint32_t page_num, uint64_t features);
+
+void LogMetricBluetoothLocalVersions(
+    uint32_t lmp_manufacturer_name,
+    uint8_t lmp_version,
+    uint32_t lmp_subversion,
+    uint8_t hci_version,
+    uint32_t hci_revision);
+
+void LogMetricBluetoothDisconnectionReasonReported(
+    uint32_t reason, const hci::Address& address, uint32_t connection_handle);
+
+void LogMetricBluetoothRemoteSupportedFeatures(
+    const hci::Address& address, uint32_t page, uint64_t features, uint32_t connection_handle);
+
 void LogMetricBluetoothCodePathCounterMetrics(int32_t key, int64_t count);
 }  // namespace os
 
diff --git a/system/main/shim/btm_api.cc b/system/main/shim/btm_api.cc
index 8b761c0..2833a48 100644
--- a/system/main/shim/btm_api.cc
+++ b/system/main/shim/btm_api.cc
@@ -460,7 +460,7 @@
         LinkKey key;  // Never want to send the key to the stack
         (*bta_callbacks_->p_link_key_callback)(
             bluetooth::ToRawAddress(device.GetAddress()), 0, name, key,
-            BTM_LKEY_TYPE_COMBINATION);
+            BTM_LKEY_TYPE_COMBINATION, false /* is_ctkd */);
       }
       if (*bta_callbacks_->p_auth_complete_callback) {
         (*bta_callbacks_->p_auth_complete_callback)(
diff --git a/system/main/test/main_shim_dumpsys_test.cc b/system/main/test/main_shim_dumpsys_test.cc
index 318a513..06f89a5 100644
--- a/system/main/test/main_shim_dumpsys_test.cc
+++ b/system/main/test/main_shim_dumpsys_test.cc
@@ -48,7 +48,6 @@
 
     ModuleList modules;
     modules.add<shim::Dumpsys>();
-    modules.add<storage::StorageModule>();
 
     os::Thread* thread = new os::Thread("thread", os::Thread::Priority::NORMAL);
     stack_manager_.StartUp(&modules, thread);
diff --git a/system/stack/Android.bp b/system/stack/Android.bp
index 80244ce..8514ed0 100644
--- a/system/stack/Android.bp
+++ b/system/stack/Android.bp
@@ -890,14 +890,9 @@
         "libprotobuf-cpp-lite",
     ],
     sanitize: {
-        address: true,
-        all_undefined: true,
-        cfi: true,
-        integer_overflow: true,
-        scs: true,
-        diag: {
-            undefined : true
-        },
+      hwaddress: true,
+      integer_overflow: true,
+      scs: true,
     },
 }
 
diff --git a/system/stack/a2dp/a2dp_codec_config.cc b/system/stack/a2dp/a2dp_codec_config.cc
index 34503c8..27a42f9 100644
--- a/system/stack/a2dp/a2dp_codec_config.cc
+++ b/system/stack/a2dp/a2dp_codec_config.cc
@@ -559,6 +559,9 @@
   LOG_INFO("%s", __func__);
   std::lock_guard<std::recursive_mutex> lock(codec_mutex_);
 
+  bool opus_enabled =
+      osi_property_get_bool("persist.bluetooth.opus.enabled", false);
+
   for (int i = BTAV_A2DP_CODEC_INDEX_MIN; i < BTAV_A2DP_CODEC_INDEX_MAX; i++) {
     btav_a2dp_codec_index_t codec_index =
         static_cast<btav_a2dp_codec_index_t>(i);
@@ -571,6 +574,13 @@
       codec_priority = cp_iter->second;
     }
 
+    // If OPUS is not supported it is disabled
+    if (codec_index == BTAV_A2DP_CODEC_INDEX_SOURCE_OPUS && !opus_enabled) {
+      codec_priority = BTAV_A2DP_CODEC_PRIORITY_DISABLED;
+      LOG_INFO("%s: OPUS codec disabled, updated priority to %d", __func__,
+               codec_priority);
+    }
+
     A2dpCodecConfig* codec_config =
         A2dpCodecConfig::createCodec(codec_index, codec_priority);
     if (codec_config == nullptr) continue;
diff --git a/system/stack/acl/btm_acl.cc b/system/stack/acl/btm_acl.cc
index e242c53..6e94539 100644
--- a/system/stack/acl/btm_acl.cc
+++ b/system/stack/acl/btm_acl.cc
@@ -630,6 +630,21 @@
     return;
   }
 
+  /* if we are trying to drop encryption on an encrypted connection, drop the
+   * connection */
+  if (p->is_encrypted && !encr_enable) {
+    android_errorWriteLog(0x534e4554, "251436534");
+    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 */
diff --git a/system/stack/btm/btm_sco.h b/system/stack/btm/btm_sco.h
index 8e85f1d..a598f22 100644
--- a/system/stack/btm/btm_sco.h
+++ b/system/stack/btm/btm_sco.h
@@ -54,6 +54,13 @@
 /* Define the structures needed by sco
  */
 
+#ifndef CASE_RETURN_TEXT
+#define CASE_RETURN_TEXT(code) \
+  case code:                   \
+    return #code
+#endif
+
+/* Define the structures needed by sco */
 typedef enum : uint16_t {
   SCO_ST_UNUSED = 0,
   SCO_ST_LISTENING = 1,
@@ -68,27 +75,23 @@
 
 inline std::string sco_state_text(const tSCO_STATE& state) {
   switch (state) {
-    case SCO_ST_UNUSED:
-      return std::string("unused");
-    case SCO_ST_LISTENING:
-      return std::string("listening");
-    case SCO_ST_W4_CONN_RSP:
-      return std::string("connect_response");
-    case SCO_ST_CONNECTING:
-      return std::string("connecting");
-    case SCO_ST_CONNECTED:
-      return std::string("connected");
-    case SCO_ST_DISCONNECTING:
-      return std::string("disconnecting");
-    case SCO_ST_PEND_UNPARK:
-      return std::string("pending_unpark");
-    case SCO_ST_PEND_ROLECHANGE:
-      return std::string("pending_role_change");
-    case SCO_ST_PEND_MODECHANGE:
-      return std::string("pending_mode_change");
+    CASE_RETURN_TEXT(SCO_ST_UNUSED);
+    CASE_RETURN_TEXT(SCO_ST_LISTENING);
+    CASE_RETURN_TEXT(SCO_ST_W4_CONN_RSP);
+    CASE_RETURN_TEXT(SCO_ST_CONNECTING);
+    CASE_RETURN_TEXT(SCO_ST_CONNECTED);
+    CASE_RETURN_TEXT(SCO_ST_DISCONNECTING);
+    CASE_RETURN_TEXT(SCO_ST_PEND_UNPARK);
+    CASE_RETURN_TEXT(SCO_ST_PEND_ROLECHANGE);
+    CASE_RETURN_TEXT(SCO_ST_PEND_MODECHANGE);
+    default:
+      return std::string("unknown_sco_state: ") +
+             std::to_string(static_cast<uint16_t>(state));
   }
 }
 
+#undef CASE_RETURN_TEXT
+
 /* Define the structure that contains (e)SCO data */
 typedef struct {
   tBTM_ESCO_CBACK* p_esco_cback; /* Callback for eSCO events     */
diff --git a/system/stack/btm/btm_sec.cc b/system/stack/btm/btm_sec.cc
index ea51aa8..6c7309d 100644
--- a/system/stack/btm/btm_sec.cc
+++ b/system/stack/btm/btm_sec.cc
@@ -3941,9 +3941,9 @@
     if (btm_cb.api.p_link_key_callback) {
       BTM_TRACE_DEBUG("%s() Save LTK derived LK (key_type = %d)", __func__,
                       p_dev_rec->link_key_type);
-      (*btm_cb.api.p_link_key_callback)(p_bda, p_dev_rec->dev_class,
-                                        p_dev_rec->sec_bd_name, link_key,
-                                        p_dev_rec->link_key_type);
+      (*btm_cb.api.p_link_key_callback)(
+          p_bda, p_dev_rec->dev_class, p_dev_rec->sec_bd_name, link_key,
+          p_dev_rec->link_key_type, true /* is_ctkd */);
     }
   } else {
     if ((p_dev_rec->link_key_type == BTM_LKEY_TYPE_UNAUTH_COMB_P_256) ||
@@ -3991,9 +3991,9 @@
             " (key_type = %d)",
             p_dev_rec->link_key_type);
       } else {
-        (*btm_cb.api.p_link_key_callback)(p_bda, p_dev_rec->dev_class,
-                                          p_dev_rec->sec_bd_name, link_key,
-                                          p_dev_rec->link_key_type);
+        (*btm_cb.api.p_link_key_callback)(
+            p_bda, p_dev_rec->dev_class, p_dev_rec->sec_bd_name, link_key,
+            p_dev_rec->link_key_type, false /* is_ctkd */);
       }
     }
   }
@@ -4579,7 +4579,7 @@
   if (btm_cb.api.p_link_key_callback)
     (*btm_cb.api.p_link_key_callback)(
         p_dev_rec->bd_addr, p_dev_rec->dev_class, p_dev_rec->sec_bd_name,
-        p_dev_rec->link_key, p_dev_rec->link_key_type);
+        p_dev_rec->link_key, p_dev_rec->link_key_type, false);
 }
 
 /*******************************************************************************
@@ -4745,7 +4745,8 @@
 
   if (btm_status == BTM_SUCCESS && is_le_transport) {
     /* Link is encrypted, start EATT */
-    bluetooth::eatt::EattExtension::GetInstance()->Connect(p_dev_rec->bd_addr);
+    bluetooth::eatt::EattExtension::GetInstance()->Connect(
+        p_dev_rec->ble.pseudo_addr);
   }
 }
 
diff --git a/system/stack/eatt/eatt.h b/system/stack/eatt/eatt.h
index 40542b0..a6cfbea 100644
--- a/system/stack/eatt/eatt.h
+++ b/system/stack/eatt/eatt.h
@@ -17,7 +17,7 @@
 
 #pragma once
 
-#include <queue>
+#include <deque>
 
 #include "stack/gatt/gatt_int.h"
 #include "types/raw_address.h"
@@ -54,7 +54,7 @@
   /* indication confirmation timer */
   alarm_t* ind_confirmation_timer_;
   /* GATT client command queue */
-  std::queue<tGATT_CMD_Q> cl_cmd_q_;
+  std::deque<tGATT_CMD_Q> cl_cmd_q_;
 
   EattChannel(RawAddress& bda, uint16_t cid, uint16_t tx_mtu, uint16_t rx_mtu)
       : bda_(bda),
@@ -79,7 +79,7 @@
   void EattChannelSetState(EattChannelState state) {
     if (state_ == EattChannelState::EATT_CHANNEL_PENDING) {
       if (state == EattChannelState::EATT_CHANNEL_OPENED) {
-        cl_cmd_q_ = std::queue<tGATT_CMD_Q>();
+        cl_cmd_q_ = std::deque<tGATT_CMD_Q>();
         memset(&server_outstanding_cmd_, 0, sizeof(tGATT_SR_CMD));
         char name[64];
         sprintf(name, "eatt_ind_ack_timer_%s_cid_0x%04x",
diff --git a/system/stack/eatt/eatt_impl.h b/system/stack/eatt/eatt_impl.h
index f8a3770..8293bb6 100644
--- a/system/stack/eatt/eatt_impl.h
+++ b/system/stack/eatt/eatt_impl.h
@@ -111,6 +111,13 @@
   }
 
   void remove_channel_by_cid(eatt_device* eatt_dev, uint16_t lcid) {
+    auto channel = eatt_dev->eatt_channels[lcid];
+    if (!channel->cl_cmd_q_.empty()) {
+      LOG_WARN("Channel %c, for device %s is not empty on disconnection.", lcid,
+               channel->bda_.ToString().c_str());
+      channel->cl_cmd_q_.clear();
+    }
+
     eatt_dev->eatt_channels.erase(lcid);
 
     if (eatt_dev->eatt_channels.size() == 0) eatt_dev->eatt_tcb_ = NULL;
diff --git a/system/stack/gatt/att_protocol.cc b/system/stack/gatt/att_protocol.cc
index 3683d40..ef4a3a6 100644
--- a/system/stack/gatt/att_protocol.cc
+++ b/system/stack/gatt/att_protocol.cc
@@ -436,7 +436,8 @@
   if (gatt_tcb_is_cid_busy(tcb, p_clcb->cid) &&
       cmd_code != GATT_HANDLE_VALUE_CONF) {
     gatt_cmd_enq(tcb, p_clcb, true, cmd_code, p_cmd);
-    LOG_DEBUG("Enqueued ATT command");
+    LOG_DEBUG("Enqueued ATT command %p conn_id=0x%04x, cid=%d", p_clcb,
+              p_clcb->conn_id, p_clcb->cid);
     return GATT_CMD_STARTED;
   }
 
@@ -445,7 +446,9 @@
       p_clcb->cid, tcb.eatt, bt_transport_text(tcb.transport).c_str());
   tGATT_STATUS att_ret = attp_send_msg_to_l2cap(tcb, p_clcb->cid, p_cmd);
   if (att_ret != GATT_CONGESTED && att_ret != GATT_SUCCESS) {
-    LOG_WARN("Unable to send ATT command to l2cap layer");
+    LOG_WARN(
+        "Unable to send ATT command to l2cap layer %p conn_id=0x%04x, cid=%d",
+        p_clcb, p_clcb->conn_id, p_clcb->cid);
     return GATT_INTERNAL_ERROR;
   }
 
@@ -453,7 +456,8 @@
     return att_ret;
   }
 
-  LOG_DEBUG("Starting ATT response timer");
+  LOG_DEBUG("Starting ATT response timer %p conn_id=0x%04x, cid=%d", p_clcb,
+            p_clcb->conn_id, p_clcb->cid);
   gatt_start_rsp_timer(p_clcb);
   gatt_cmd_enq(tcb, p_clcb, false, cmd_code, NULL);
   return att_ret;
diff --git a/system/stack/gatt/gatt_api.cc b/system/stack/gatt/gatt_api.cc
index b956924..0e3c23e 100644
--- a/system/stack/gatt/gatt_api.cc
+++ b/system/stack/gatt/gatt_api.cc
@@ -702,11 +702,6 @@
     return GATT_ERROR;
   }
 
-  if (gatt_is_clcb_allocated(conn_id)) {
-    LOG_WARN("Connection is already used conn_id:%hu", conn_id);
-    return GATT_BUSY;
-  }
-
   tGATT_CLCB* p_clcb = gatt_clcb_alloc(conn_id);
   if (!p_clcb) {
     LOG_WARN("Unable to allocate connection link control block");
@@ -765,11 +760,6 @@
     return GATT_ILLEGAL_PARAMETER;
   }
 
-  if (gatt_is_clcb_allocated(conn_id)) {
-    LOG(ERROR) << __func__ << "GATT_BUSY conn_id = " << +conn_id;
-    return GATT_BUSY;
-  }
-
   tGATT_CLCB* p_clcb = gatt_clcb_alloc(conn_id);
   if (!p_clcb) {
     LOG(WARNING) << __func__ << " No resources conn_id=" << loghex(conn_id)
@@ -835,11 +825,6 @@
     return GATT_ILLEGAL_PARAMETER;
   }
 
-  if (gatt_is_clcb_allocated(conn_id)) {
-    LOG(ERROR) << "GATT_BUSY conn_id=" << loghex(conn_id);
-    return GATT_BUSY;
-  }
-
   tGATT_CLCB* p_clcb = gatt_clcb_alloc(conn_id);
   if (!p_clcb) return GATT_NO_RESOURCES;
 
@@ -942,11 +927,6 @@
     return GATT_ILLEGAL_PARAMETER;
   }
 
-  if (gatt_is_clcb_allocated(conn_id)) {
-    LOG(ERROR) << "GATT_BUSY conn_id=" << loghex(conn_id);
-    return GATT_BUSY;
-  }
-
   tGATT_CLCB* p_clcb = gatt_clcb_alloc(conn_id);
   if (!p_clcb) return GATT_NO_RESOURCES;
 
@@ -995,11 +975,6 @@
     return GATT_ILLEGAL_PARAMETER;
   }
 
-  if (gatt_is_clcb_allocated(conn_id)) {
-    LOG(ERROR) << " GATT_BUSY conn_id=" << loghex(conn_id);
-    return GATT_BUSY;
-  }
-
   tGATT_CLCB* p_clcb = gatt_clcb_alloc(conn_id);
   if (!p_clcb) return GATT_NO_RESOURCES;
 
@@ -1185,7 +1160,7 @@
   /* When an application deregisters, check remove the link associated with the
    * app */
   tGATT_TCB* p_tcb;
-  int i, j;
+  int i;
   for (i = 0, p_tcb = gatt_cb.tcb; i < GATT_MAX_PHY_CHANNEL; i++, p_tcb++) {
     if (!p_tcb->in_use) continue;
 
@@ -1193,13 +1168,15 @@
       gatt_update_app_use_link_flag(gatt_if, p_tcb, false, true);
     }
 
-    tGATT_CLCB* p_clcb;
-    for (j = 0, p_clcb = &gatt_cb.clcb[j]; j < GATT_CL_MAX_LCB; j++, p_clcb++) {
-      if (p_clcb->in_use && (p_clcb->p_reg->gatt_if == gatt_if) &&
-          (p_clcb->p_tcb->tcb_idx == p_tcb->tcb_idx)) {
-        alarm_cancel(p_clcb->gatt_rsp_timer_ent);
-        gatt_clcb_dealloc(p_clcb);
-        break;
+    for (auto clcb_it = gatt_cb.clcb_queue.begin();
+         clcb_it != gatt_cb.clcb_queue.end();) {
+      if ((clcb_it->p_reg->gatt_if == gatt_if) &&
+          (clcb_it->p_tcb->tcb_idx == p_tcb->tcb_idx)) {
+        alarm_cancel(clcb_it->gatt_rsp_timer_ent);
+        gatt_clcb_invalidate(p_tcb, &(*clcb_it));
+        clcb_it = gatt_cb.clcb_queue.erase(clcb_it);
+      } else {
+        clcb_it++;
       }
     }
   }
diff --git a/system/stack/gatt/gatt_cl.cc b/system/stack/gatt/gatt_cl.cc
index 215ea2a..9778466 100644
--- a/system/stack/gatt/gatt_cl.cc
+++ b/system/stack/gatt/gatt_cl.cc
@@ -1130,11 +1130,12 @@
 
 /** Find next command in queue and sent to server */
 bool gatt_cl_send_next_cmd_inq(tGATT_TCB& tcb) {
-  std::queue<tGATT_CMD_Q>* cl_cmd_q;
+  std::deque<tGATT_CMD_Q>* cl_cmd_q = nullptr;
 
-  while (!tcb.cl_cmd_q.empty() ||
-         EattExtension::GetInstance()->IsOutstandingMsgInSendQueue(tcb.peer_bda)) {
-    if (!tcb.cl_cmd_q.empty()) {
+  while (
+      gatt_is_outstanding_msg_in_att_send_queue(tcb) ||
+      EattExtension::GetInstance()->IsOutstandingMsgInSendQueue(tcb.peer_bda)) {
+    if (gatt_is_outstanding_msg_in_att_send_queue(tcb)) {
       cl_cmd_q = &tcb.cl_cmd_q;
     } else {
       EattChannel* channel =
@@ -1153,7 +1154,7 @@
 
     if (att_ret != GATT_SUCCESS && att_ret != GATT_CONGESTED) {
       LOG(ERROR) << __func__ << ": L2CAP sent error";
-      cl_cmd_q->pop();
+      cl_cmd_q->pop_front();
       continue;
     }
 
@@ -1185,7 +1186,7 @@
 void gatt_client_handle_server_rsp(tGATT_TCB& tcb, uint16_t cid,
                                    uint8_t op_code, uint16_t len,
                                    uint8_t* p_data) {
-  VLOG(1) << __func__ << " opcode: " << loghex(op_code);
+  VLOG(1) << __func__ << " opcode: " << loghex(op_code) << " cid" << +cid;
 
   uint16_t payload_size = gatt_tcb_get_payload_size_rx(tcb, cid);
 
@@ -1204,20 +1205,26 @@
 
   uint8_t cmd_code = 0;
   tGATT_CLCB* p_clcb = gatt_cmd_dequeue(tcb, cid, &cmd_code);
-  uint8_t rsp_code = gatt_cmd_to_rsp_code(cmd_code);
-  if (!p_clcb || (rsp_code != op_code && op_code != GATT_RSP_ERROR)) {
-    LOG(WARNING) << StringPrintf(
-        "ATT - Ignore wrong response. Receives (%02x) Request(%02x) Ignored",
-        op_code, rsp_code);
+  if (!p_clcb) {
+    LOG_WARN("ATT - clcb already not in use, ignoring response");
+    gatt_cl_send_next_cmd_inq(tcb);
     return;
   }
 
-  if (!p_clcb->in_use) {
-    LOG(WARNING) << "ATT - clcb already not in use, ignoring response";
+  uint8_t rsp_code = gatt_cmd_to_rsp_code(cmd_code);
+  if (!p_clcb) {
+    LOG_WARN("ATT - clcb already not in use, ignoring response");
     gatt_cl_send_next_cmd_inq(tcb);
     return;
   }
 
+  if (rsp_code != op_code && op_code != GATT_RSP_ERROR) {
+    LOG(WARNING) << StringPrintf(
+        "ATT - Ignore wrong response. Receives (%02x) Request(%02x) Ignored",
+        op_code, rsp_code);
+    return;
+  }
+
   gatt_stop_rsp_timer(p_clcb);
   p_clcb->retry_count = 0;
 
diff --git a/system/stack/gatt/gatt_int.h b/system/stack/gatt/gatt_int.h
index 1e9e7e1..5e6e23f 100644
--- a/system/stack/gatt/gatt_int.h
+++ b/system/stack/gatt/gatt_int.h
@@ -23,6 +23,7 @@
 #include <base/strings/stringprintf.h>
 #include <string.h>
 
+#include <deque>
 #include <list>
 #include <queue>
 #include <unordered_set>
@@ -330,7 +331,7 @@
   uint8_t prep_cnt[GATT_MAX_APPS];
   uint8_t ind_count;
 
-  std::queue<tGATT_CMD_Q> cl_cmd_q;
+  std::deque<tGATT_CMD_Q> cl_cmd_q;
   alarm_t* ind_ack_timer; /* local app confirm to indication timer */
 
   // TODO(hylo): support byte array data
@@ -369,7 +370,6 @@
   tGATT_STATUS status;     /* operation status */
   bool first_read_blob_after_read;
   tGATT_READ_INC_UUID128 read_uuid128;
-  bool in_use;
   alarm_t* gatt_rsp_timer_ent; /* peer response timer */
   uint8_t retry_count;
   uint16_t read_req_current_mtu; /* This is the MTU value that the read was
@@ -416,7 +416,14 @@
 
   fixed_queue_t* srv_chg_clt_q; /* service change clients queue */
   tGATT_REG cl_rcb[GATT_MAX_APPS];
-  tGATT_CLCB clcb[GATT_CL_MAX_LCB]; /* connection link control block*/
+
+  /* list of connection link control blocks.
+   * Since clcbs are also keep in the channels (ATT and EATT) queues while
+   * processing, we want to make sure that references to elements are not
+   * invalidated when elements are added or removed from the list. This is why
+   * std::list is used.
+   */
+  std::list<tGATT_CLCB> clcb_queue;
 
 #if (GATT_CONFORMANCE_TESTING == TRUE)
   bool enable_err_rsp;
@@ -586,6 +593,7 @@
 extern uint16_t gatt_tcb_get_att_cid(tGATT_TCB& tcb, bool eatt_support);
 extern uint16_t gatt_tcb_get_payload_size_tx(tGATT_TCB& tcb, uint16_t cid);
 extern uint16_t gatt_tcb_get_payload_size_rx(tGATT_TCB& tcb, uint16_t cid);
+extern void gatt_clcb_invalidate(tGATT_TCB* p_tcb, const tGATT_CLCB* p_clcb);
 extern void gatt_clcb_dealloc(tGATT_CLCB* p_clcb);
 
 extern void gatt_sr_copy_prep_cnt_to_cback_cnt(tGATT_TCB& p_tcb);
@@ -637,6 +645,7 @@
                                           uint8_t* p_data);
 extern void gatt_send_queue_write_cancel(tGATT_TCB& tcb, tGATT_CLCB* p_clcb,
                                          tGATT_EXEC_FLAG flag);
+extern bool gatt_is_outstanding_msg_in_att_send_queue(const tGATT_TCB& tcb);
 
 /* gatt_auth.cc */
 extern bool gatt_security_check_start(tGATT_CLCB* p_clcb);
diff --git a/system/stack/gatt/gatt_utils.cc b/system/stack/gatt/gatt_utils.cc
index 64b2f7a..8fa2406 100644
--- a/system/stack/gatt/gatt_utils.cc
+++ b/system/stack/gatt/gatt_utils.cc
@@ -27,6 +27,7 @@
 #include <base/strings/stringprintf.h>
 
 #include <cstdint>
+#include <deque>
 
 #include "bt_target.h"  // Must be first to define build configuration
 #include "osi/include/allocator.h"
@@ -948,38 +949,6 @@
 
 /*******************************************************************************
  *
- * Function         gatt_is_clcb_allocated
- *
- * Description      The function check clcb for conn_id is allocated or not
- *
- * Returns           True already allocated
- *
- ******************************************************************************/
-
-bool gatt_is_clcb_allocated(uint16_t conn_id) {
-  uint8_t i = 0;
-  uint8_t num_of_allocated = 0;
-  tGATT_IF gatt_if = GATT_GET_GATT_IF(conn_id);
-  uint8_t tcb_idx = GATT_GET_TCB_IDX(conn_id);
-  tGATT_TCB* p_tcb = gatt_get_tcb_by_idx(tcb_idx);
-  tGATT_REG* p_reg = gatt_get_regcb(gatt_if);
-  int possible_plcb = 1;
-
-  if (p_reg->eatt_support) possible_plcb += p_tcb->eatt;
-
-  /* With eatt number of active clcbs can me up to 1 + number of eatt channels
-   */
-  for (i = 0; i < GATT_CL_MAX_LCB; i++) {
-    if (gatt_cb.clcb[i].in_use && (gatt_cb.clcb[i].conn_id == conn_id)) {
-      if (++num_of_allocated == possible_plcb) return true;
-    }
-  }
-
-  return false;
-}
-
-/*******************************************************************************
- *
  * Function         gatt_tcb_is_cid_busy
  *
  * Description      The function check if channel with given cid is busy
@@ -1008,29 +977,30 @@
  *
  ******************************************************************************/
 tGATT_CLCB* gatt_clcb_alloc(uint16_t conn_id) {
-  uint8_t i = 0;
-  tGATT_CLCB* p_clcb = NULL;
+  tGATT_CLCB clcb;
   tGATT_IF gatt_if = GATT_GET_GATT_IF(conn_id);
   uint8_t tcb_idx = GATT_GET_TCB_IDX(conn_id);
   tGATT_TCB* p_tcb = gatt_get_tcb_by_idx(tcb_idx);
   tGATT_REG* p_reg = gatt_get_regcb(gatt_if);
 
-  for (i = 0; i < GATT_CL_MAX_LCB; i++) {
-    if (!gatt_cb.clcb[i].in_use) {
-      p_clcb = &gatt_cb.clcb[i];
+  clcb.conn_id = conn_id;
+  clcb.p_reg = p_reg;
+  clcb.p_tcb = p_tcb;
+  /* Use eatt only when clients wants that */
+  clcb.cid = gatt_tcb_get_att_cid(*p_tcb, p_reg->eatt_support);
 
-      p_clcb->in_use = true;
-      p_clcb->conn_id = conn_id;
-      p_clcb->p_reg = p_reg;
-      p_clcb->p_tcb = p_tcb;
+  gatt_cb.clcb_queue.emplace_back(clcb);
+  auto p_clcb = &(gatt_cb.clcb_queue.back());
 
-      /* Use eatt only when clients wants that */
-      p_clcb->cid = gatt_tcb_get_att_cid(*p_tcb, p_reg->eatt_support);
-
-      break;
-    }
+  if (gatt_cb.clcb_queue.size() > GATT_CL_MAX_LCB) {
+    /* GATT_CL_MAX_LCB is here from the historical reasons. We believe this
+     * limitation is not needed. In addition, number of clcb should not be
+     * bigger than that and also if it is bigger, we  believe it should not
+     * cause the problem. This WARN is just to monitor number of CLCB and will
+     * help in debugging in case we are wrong */
+    LOG_WARN("Number of CLCB: %zu > %d", gatt_cb.clcb_queue.size(),
+             GATT_CL_MAX_LCB);
   }
-
   return p_clcb;
 }
 
@@ -1166,14 +1136,64 @@
  *
  ******************************************************************************/
 void gatt_clcb_dealloc(tGATT_CLCB* p_clcb) {
-  if (p_clcb && p_clcb->in_use) {
+  if (p_clcb) {
     alarm_free(p_clcb->gatt_rsp_timer_ent);
-    memset(p_clcb, 0, sizeof(tGATT_CLCB));
+    for (auto clcb_it = gatt_cb.clcb_queue.begin();
+         clcb_it != gatt_cb.clcb_queue.end(); clcb_it++) {
+      if (&(*clcb_it) == p_clcb) {
+        gatt_cb.clcb_queue.erase(clcb_it);
+        return;
+      }
+    }
   }
 }
 
 /*******************************************************************************
  *
+ * Function         gatt_clcb_invalidate
+ *
+ * Description      The function invalidates already scheduled p_clcb.
+ *
+ * Returns         None
+ *
+ ******************************************************************************/
+void gatt_clcb_invalidate(tGATT_TCB* p_tcb, const tGATT_CLCB* p_clcb) {
+  std::deque<tGATT_CMD_Q>* cl_cmd_q_p;
+  uint16_t cid = p_clcb->cid;
+
+  if (cid == p_tcb->att_lcid) {
+    cl_cmd_q_p = &p_tcb->cl_cmd_q;
+  } else {
+    EattChannel* channel = EattExtension::GetInstance()->FindEattChannelByCid(
+        p_tcb->peer_bda, cid);
+    if (channel == nullptr) {
+      return;
+    }
+    cl_cmd_q_p = &channel->cl_cmd_q_;
+  }
+
+  if (cl_cmd_q_p->empty()) {
+    return;
+  }
+
+  auto iter = std::find_if(cl_cmd_q_p->begin(), cl_cmd_q_p->end(),
+                           [p_clcb](auto& el) { return el.p_clcb == p_clcb; });
+
+  if (iter == cl_cmd_q_p->end()) {
+    return;
+  }
+
+  if (iter->to_send) {
+    /* If command was not send, just remove the entire element */
+    cl_cmd_q_p->erase(iter);
+  } else {
+    /* If command has been sent, just invalidate p_clcb pointer for proper
+     * response handling */
+    iter->p_clcb = NULL;
+  }
+}
+/*******************************************************************************
+ *
  * Function         gatt_find_tcb_by_cid
  *
  * Description      The function searches for an empty entry
@@ -1208,10 +1228,10 @@
  *
  ******************************************************************************/
 uint8_t gatt_num_clcb_by_bd_addr(const RawAddress& bda) {
-  uint8_t i, num = 0;
+  uint8_t num = 0;
 
-  for (i = 0; i < GATT_CL_MAX_LCB; i++) {
-    if (gatt_cb.clcb[i].in_use && gatt_cb.clcb[i].p_tcb->peer_bda == bda) num++;
+  for (auto const& clcb : gatt_cb.clcb_queue) {
+    if (clcb.p_tcb->peer_bda == bda) num++;
   }
   return num;
 }
@@ -1449,18 +1469,18 @@
   cmd.cid = p_clcb->cid;
 
   if (p_clcb->cid == tcb.att_lcid) {
-    tcb.cl_cmd_q.push(cmd);
+    tcb.cl_cmd_q.push_back(cmd);
   } else {
     EattChannel* channel =
         EattExtension::GetInstance()->FindEattChannelByCid(tcb.peer_bda, cmd.cid);
     CHECK(channel);
-    channel->cl_cmd_q_.push(cmd);
+    channel->cl_cmd_q_.push_back(cmd);
   }
 }
 
 /** dequeue the command in the client CCB command queue */
 tGATT_CLCB* gatt_cmd_dequeue(tGATT_TCB& tcb, uint16_t cid, uint8_t* p_op_code) {
-  std::queue<tGATT_CMD_Q>* cl_cmd_q_p;
+  std::deque<tGATT_CMD_Q>* cl_cmd_q_p;
 
   if (cid == tcb.att_lcid) {
     cl_cmd_q_p = &tcb.cl_cmd_q;
@@ -1476,8 +1496,16 @@
   tGATT_CMD_Q cmd = cl_cmd_q_p->front();
   tGATT_CLCB* p_clcb = cmd.p_clcb;
   *p_op_code = cmd.op_code;
-  p_clcb->cid = cid;
-  cl_cmd_q_p->pop();
+
+  /* Note: If GATT client deregistered while the ATT request was on the way to
+   * peer, device p_clcb will be null.
+   */
+  if (p_clcb && p_clcb->cid != cid) {
+    LOG_WARN(" CID does not match (%d!=%d), conn_id=0x%04x", p_clcb->cid, cid,
+             p_clcb->conn_id);
+  }
+
+  cl_cmd_q_p->pop_front();
 
   return p_clcb;
 }
@@ -1498,6 +1526,18 @@
 
 /*******************************************************************************
  *
+ * Function         gatt_is_outstanding_msg_in_att_send_queue
+ *
+ * Description      checks if there is message on the ATT fixed channel to send
+ *
+ * Returns          true: on success; false otherwise
+ *
+ ******************************************************************************/
+bool gatt_is_outstanding_msg_in_att_send_queue(const tGATT_TCB& tcb) {
+  return (!tcb.cl_cmd_q.empty() && (tcb.cl_cmd_q.front()).to_send);
+}
+/*******************************************************************************
+ *
  * Function         gatt_end_operation
  *
  * Description      This function ends a discovery, send callback and finalize
@@ -1585,20 +1625,32 @@
   }
 
   gatt_set_ch_state(p_tcb, GATT_CH_CLOSE);
-  for (uint8_t i = 0; i < GATT_CL_MAX_LCB; i++) {
-    tGATT_CLCB* p_clcb = &gatt_cb.clcb[i];
-    if (!p_clcb->in_use || p_clcb->p_tcb != p_tcb) continue;
 
-    gatt_stop_rsp_timer(p_clcb);
-    VLOG(1) << "found p_clcb conn_id=" << +p_clcb->conn_id;
-    if (p_clcb->operation == GATTC_OPTYPE_NONE) {
-      gatt_clcb_dealloc(p_clcb);
+  /* Notify EATT about disconnection. */
+  EattExtension::GetInstance()->Disconnect(p_tcb->peer_bda);
+
+  for (auto clcb_it = gatt_cb.clcb_queue.begin();
+       clcb_it != gatt_cb.clcb_queue.end();) {
+    if (clcb_it->p_tcb != p_tcb) {
+      ++clcb_it;
       continue;
     }
 
+    gatt_stop_rsp_timer(&(*clcb_it));
+    VLOG(1) << "found p_clcb conn_id=" << +clcb_it->conn_id;
+    if (clcb_it->operation == GATTC_OPTYPE_NONE) {
+      clcb_it = gatt_cb.clcb_queue.erase(clcb_it);
+      continue;
+    }
+
+    tGATT_CLCB* p_clcb = &(*clcb_it);
+    ++clcb_it;
     gatt_end_operation(p_clcb, GATT_ERROR, NULL);
   }
 
+  /* Remove the outstanding ATT commnads if any */
+  p_tcb->cl_cmd_q.clear();
+
   alarm_free(p_tcb->ind_ack_timer);
   p_tcb->ind_ack_timer = NULL;
   alarm_free(p_tcb->conf_timer);
diff --git a/system/stack/include/security_client_callbacks.h b/system/stack/include/security_client_callbacks.h
index de59ff1..d6070cd 100644
--- a/system/stack/include/security_client_callbacks.h
+++ b/system/stack/include/security_client_callbacks.h
@@ -52,7 +52,8 @@
 typedef uint8_t(tBTM_LINK_KEY_CALLBACK)(const RawAddress& bd_addr,
                                         DEV_CLASS dev_class,
                                         tBTM_BD_NAME bd_name,
-                                        const LinkKey& key, uint8_t key_type);
+                                        const LinkKey& key, uint8_t key_type,
+                                        bool is_ctkd);
 
 /* Remote Name Resolved.  Parameters are
  *              BD Address of remote
diff --git a/system/stack/smp/smp_l2c.cc b/system/stack/smp/smp_l2c.cc
index 3aaa479..7debcea 100644
--- a/system/stack/smp/smp_l2c.cc
+++ b/system/stack/smp/smp_l2c.cc
@@ -24,6 +24,7 @@
 
 #define LOG_TAG "bluetooth"
 
+#include <base/logging.h>
 #include <string.h>
 
 #include "bt_target.h"
@@ -35,11 +36,10 @@
 #include "osi/include/log.h"
 #include "osi/include/osi.h"  // UNUSED_ATTR
 #include "smp_int.h"
+#include "stack/btm/btm_dev.h"
 #include "stack/include/bt_hdr.h"
 #include "types/raw_address.h"
 
-#include <base/logging.h>
-
 static void smp_connect_callback(uint16_t channel, const RawAddress& bd_addr,
                                  bool connected, uint16_t reason,
                                  tBT_TRANSPORT transport);
@@ -249,6 +249,24 @@
 
   if (bd_addr != p_cb->pairing_bda) return;
 
+  /* Check if we already finished SMP pairing over LE, and are waiting to
+   * check if other side returns some errors. Connection/disconnection on
+   * Classic transport shouldn't impact that.
+   */
+  tBTM_SEC_DEV_REC* p_dev_rec = btm_find_dev(p_cb->pairing_bda);
+  if (smp_get_state() == SMP_STATE_BOND_PENDING &&
+      (p_dev_rec && p_dev_rec->is_link_key_known()) &&
+      alarm_is_scheduled(p_cb->delayed_auth_timer_ent)) {
+    /* If we were to not return here, we would reset SMP control block, and
+     * delayed_auth_timer_ent would never be executed. Even though we stored all
+     * keys, stack would consider device as not bonded. It would reappear after
+     * stack restart, when we re-read record from storage. Service discovery
+     * would stay broken.
+     */
+    LOG_INFO("Classic event after CTKD on LE transport");
+    return;
+  }
+
   if (connected) {
     if (!p_cb->connect_initialized) {
       p_cb->connect_initialized = true;
diff --git a/system/stack/test/btm/stack_btm_test.cc b/system/stack/test/btm/stack_btm_test.cc
index b9285e6..ecd14fe 100644
--- a/system/stack/test/btm/stack_btm_test.cc
+++ b/system/stack/test/btm/stack_btm_test.cc
@@ -22,6 +22,7 @@
 #include <iomanip>
 #include <iostream>
 #include <map>
+#include <sstream>
 #include <vector>
 
 #include "btif/include/btif_hh.h"
@@ -397,3 +398,26 @@
 
   wipe_secrets_and_remove(device_record);
 }
+
+TEST_F(StackBtmTest, sco_state_text) {
+  std::vector<std::pair<tSCO_STATE, std::string>> states = {
+      std::make_pair(SCO_ST_UNUSED, "SCO_ST_UNUSED"),
+      std::make_pair(SCO_ST_LISTENING, "SCO_ST_LISTENING"),
+      std::make_pair(SCO_ST_W4_CONN_RSP, "SCO_ST_W4_CONN_RSP"),
+      std::make_pair(SCO_ST_CONNECTING, "SCO_ST_CONNECTING"),
+      std::make_pair(SCO_ST_CONNECTED, "SCO_ST_CONNECTED"),
+      std::make_pair(SCO_ST_DISCONNECTING, "SCO_ST_DISCONNECTING"),
+      std::make_pair(SCO_ST_PEND_UNPARK, "SCO_ST_PEND_UNPARK"),
+      std::make_pair(SCO_ST_PEND_ROLECHANGE, "SCO_ST_PEND_ROLECHANGE"),
+      std::make_pair(SCO_ST_PEND_MODECHANGE, "SCO_ST_PEND_MODECHANGE"),
+  };
+  for (const auto& state : states) {
+    ASSERT_STREQ(state.second.c_str(), sco_state_text(state.first).c_str());
+  }
+  std::ostringstream oss;
+  oss << "unknown_sco_state: " << std::numeric_limits<std::uint16_t>::max();
+  ASSERT_STREQ(oss.str().c_str(),
+               sco_state_text(static_cast<tSCO_STATE>(
+                                  std::numeric_limits<std::uint16_t>::max()))
+                   .c_str());
+}
diff --git a/system/test/common/init_flags.cc b/system/test/common/init_flags.cc
index 9b8585b..38f1382 100644
--- a/system/test/common/init_flags.cc
+++ b/system/test/common/init_flags.cc
@@ -9,6 +9,7 @@
 namespace common {
 
 bool InitFlags::logging_debug_enabled_for_all = false;
+bool InitFlags::btm_dm_flush_discovery_queue_on_search_cancel = false;
 std::unordered_map<std::string, bool>
     InitFlags::logging_debug_explicit_tag_settings = {};
 void InitFlags::Load(const char** flags) {}