diff --git a/android/app/jni/com_android_bluetooth_hfp.cpp b/android/app/jni/com_android_bluetooth_hfp.cpp
index 0e3c95035bf923eede95e282f8d74a760d6dd90c..d33669f49150ef7743d6e2e85c023cf08b4eafb7 100644
--- a/android/app/jni/com_android_bluetooth_hfp.cpp
+++ b/android/app/jni/com_android_bluetooth_hfp.cpp
@@ -237,7 +237,8 @@ class JniHeadsetCallbacks : bluetooth::headset::Callbacks {
                                  addr.get());
   }
 
-  void SwbCallback(bluetooth::headset::bthf_swb_config_t swb_config,
+  void SwbCallback(bluetooth::headset::bthf_swb_codec_t swb_codec,
+                   bluetooth::headset::bthf_swb_config_t swb_config,
                    RawAddress* bd_addr) override {
     std::shared_lock<std::shared_timed_mutex> lock(callbacks_mutex);
     CallbackEnv sCallbackEnv(__func__);
@@ -246,8 +247,8 @@ class JniHeadsetCallbacks : bluetooth::headset::Callbacks {
     ScopedLocalRef<jbyteArray> addr(sCallbackEnv.get(), marshall_bda(bd_addr));
     if (addr.get() == nullptr) return;
 
-    sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onSWB, swb_config,
-                                 addr.get());
+    sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onSWB, swb_codec,
+                                 swb_config, addr.get());
   }
 
   void AtChldCallback(bluetooth::headset::bthf_chld_type_t chld,
@@ -998,7 +999,7 @@ int register_com_android_bluetooth_hfp(JNIEnv* env) {
       {"onSendDtmf", "(I[B)V", &method_onSendDtmf},
       {"onNoiseReductionEnable", "(Z[B)V", &method_onNoiseReductionEnable},
       {"onWBS", "(I[B)V", &method_onWBS},
-      {"onSWB", "(I[B)V", &method_onSWB},
+      {"onSWB", "(II[B)V", &method_onSWB},
       {"onAtChld", "(I[B)V", &method_onAtChld},
       {"onAtCnum", "([B)V", &method_onAtCnum},
       {"onAtCind", "([B)V", &method_onAtCind},
diff --git a/android/app/src/com/android/bluetooth/hfp/HeadsetHalConstants.java b/android/app/src/com/android/bluetooth/hfp/HeadsetHalConstants.java
index 9f680122250aecd01278a38d5121ce26e3319a3a..5d5d6966bb15986bbd7d708099bf6543f8333d8f 100644
--- a/android/app/src/com/android/bluetooth/hfp/HeadsetHalConstants.java
+++ b/android/app/src/com/android/bluetooth/hfp/HeadsetHalConstants.java
@@ -75,6 +75,10 @@ public final class HeadsetHalConstants {
     static final int BTHF_WBS_NO = 1;
     static final int BTHF_WBS_YES = 2;
 
+    // match up with bthf_swb_codec_t of bt_hf.h
+    static final int BTHF_SWB_CODEC_LC3 = 0;
+    static final int BTHF_SWB_CODEC_VENDOR_APTX = 1;
+
     // match up with bthf_swb_config_t of bt_hf.h
     static final int BTHF_SWB_NONE = 0;
     static final int BTHF_SWB_NO = 1;
diff --git a/android/app/src/com/android/bluetooth/hfp/HeadsetNativeInterface.java b/android/app/src/com/android/bluetooth/hfp/HeadsetNativeInterface.java
index 4e607508595ee3b9939902a87dfd9bade02d3875..cc7e3851a1c85eaa771aa3ec162796581636df4f 100644
--- a/android/app/src/com/android/bluetooth/hfp/HeadsetNativeInterface.java
+++ b/android/app/src/com/android/bluetooth/hfp/HeadsetNativeInterface.java
@@ -163,9 +163,10 @@ public class HeadsetNativeInterface {
         sendMessageToService(event);
     }
 
-    private void onSWB(int codec, byte[] address) {
+    private void onSWB(int codec, int swb, byte[] address) {
         HeadsetStackEvent event =
-                new HeadsetStackEvent(HeadsetStackEvent.EVENT_TYPE_SWB, codec, getDevice(address));
+                new HeadsetStackEvent(
+                        HeadsetStackEvent.EVENT_TYPE_SWB, codec, swb, getDevice(address));
         sendMessageToService(event);
     }
 
diff --git a/android/app/src/com/android/bluetooth/hfp/HeadsetStateMachine.java b/android/app/src/com/android/bluetooth/hfp/HeadsetStateMachine.java
index 519ba33f9cfb90d80cc9872fb1c8a77932d104b5..126e707b74c1504f0990e91d7fffd2885dd91385 100644
--- a/android/app/src/com/android/bluetooth/hfp/HeadsetStateMachine.java
+++ b/android/app/src/com/android/bluetooth/hfp/HeadsetStateMachine.java
@@ -17,6 +17,7 @@
 package com.android.bluetooth.hfp;
 
 import static android.Manifest.permission.BLUETOOTH_CONNECT;
+
 import static com.android.modules.utils.build.SdkLevel.isAtLeastU;
 
 import android.annotation.RequiresPermission;
@@ -151,7 +152,8 @@ public class HeadsetStateMachine extends StateMachine {
     // Audio Parameters
     private boolean mHasNrecEnabled = false;
     private boolean mHasWbsEnabled = false;
-    private boolean mHasSwbEnabled = false;
+    private boolean mHasSwbLc3Enabled = false;
+    private boolean mHasSwbAptXEnabled = false;
     // AT Phone book keeps a group of states used by AT+CPBR commands
     @VisibleForTesting
     final AtPhonebook mPhonebook;
@@ -250,7 +252,8 @@ public class HeadsetStateMachine extends StateMachine {
         }
         mHasWbsEnabled = false;
         mHasNrecEnabled = false;
-        mHasSwbEnabled = false;
+        mHasSwbLc3Enabled = false;
+        mHasSwbAptXEnabled = false;
     }
 
     public void dump(StringBuilder sb) {
@@ -482,8 +485,9 @@ public class HeadsetStateMachine extends StateMachine {
             updateAgIndicatorEnableState(null);
             mNeedDialingOutReply = false;
             mHasWbsEnabled = false;
-            mHasSwbEnabled = false;
+            mHasSwbLc3Enabled = false;
             mHasNrecEnabled = false;
+            mHasSwbAptXEnabled = false;
 
             broadcastStateTransitions();
             logFailureIfNeeded();
@@ -686,7 +690,7 @@ public class HeadsetStateMachine extends StateMachine {
                             processWBSEvent(event.valueInt);
                             break;
                         case HeadsetStackEvent.EVENT_TYPE_SWB:
-                            processSWBEvent(event.valueInt);
+                            processSWBEvent(event.valueInt, event.valueInt2);
                             break;
                         case HeadsetStackEvent.EVENT_TYPE_BIND:
                             processAtBind(event.valueString, event.device);
@@ -1027,7 +1031,7 @@ public class HeadsetStateMachine extends StateMachine {
                             processWBSEvent(event.valueInt);
                             break;
                         case HeadsetStackEvent.EVENT_TYPE_SWB:
-                            processSWBEvent(event.valueInt);
+                            processSWBEvent(event.valueInt, event.valueInt2);
                             break;
                         case HeadsetStackEvent.EVENT_TYPE_AT_CHLD:
                             processAtChld(event.valueInt, event.device);
@@ -1636,11 +1640,17 @@ public class HeadsetStateMachine extends StateMachine {
 
     private void setAudioParameters() {
         AudioManager am = mSystemInterface.getAudioManager();
-        Log.i(TAG, "setAudioParameters for " + mDevice + ":"
-                + " Name=" + getCurrentDeviceName()
-                + " hasNrecEnabled=" + mHasNrecEnabled
-                + " hasWbsEnabled=" + mHasWbsEnabled);
-        am.setParameters("bt_lc3_swb=" + (mHasSwbEnabled ? "on" : "off"));
+        Log.i(
+                TAG,
+                ("setAudioParameters for " + mDevice + ":")
+                        + (" Name=" + getCurrentDeviceName())
+                        + (" hasNrecEnabled=" + mHasNrecEnabled)
+                        + (" hasWbsEnabled=" + mHasWbsEnabled)
+                        + (" hasSwbEnabled=" + mHasSwbLc3Enabled)
+                        + (" hasAptXSwbEnabled=" + mHasSwbAptXEnabled));
+        am.setParameters("bt_lc3_swb=" + (mHasSwbLc3Enabled ? "on" : "off"));
+        /* AptX bt_swb: 0 -> on, 65535 -> off */
+        am.setParameters("bt_swb=" + (mHasSwbAptXEnabled ? "0" : "65535"));
         am.setBluetoothHeadsetProperties(getCurrentDeviceName(), mHasNrecEnabled, mHasWbsEnabled);
     }
 
@@ -1797,21 +1807,48 @@ public class HeadsetStateMachine extends StateMachine {
         log("processWBSEvent: " + prevWbs + " -> " + mHasWbsEnabled);
     }
 
-    private void processSWBEvent(int swbConfig) {
-        boolean prev_swb = mHasSwbEnabled;
+    private void processSWBEvent(int swbCodec, int swbConfig) {
+        boolean prevSwbLc3 = mHasSwbLc3Enabled;
+        boolean prevSwbAptx = mHasSwbAptXEnabled;
+        boolean success = true;
+
         switch (swbConfig) {
             case HeadsetHalConstants.BTHF_SWB_YES:
-                mHasSwbEnabled = true;
+                switch (swbCodec) {
+                    case HeadsetHalConstants.BTHF_SWB_CODEC_LC3:
+                        mHasSwbLc3Enabled = true;
+                        mHasWbsEnabled = false;
+                        mHasSwbAptXEnabled = false;
+                        break;
+                    case HeadsetHalConstants.BTHF_SWB_CODEC_VENDOR_APTX:
+                        mHasSwbLc3Enabled = false;
+                        mHasWbsEnabled = false;
+                        mHasSwbAptXEnabled = true;
+                        break;
+                    default:
+                        success = false;
+                        break;
+                }
                 break;
             case HeadsetHalConstants.BTHF_SWB_NO:
             case HeadsetHalConstants.BTHF_SWB_NONE:
-                mHasSwbEnabled = false;
+                mHasSwbLc3Enabled = false;
+                mHasSwbAptXEnabled = false;
                 break;
             default:
-                Log.e(TAG, "processSWBEvent: unknown swb_config");
-                return;
+                success = false;
         }
-        log("processSWBEvent: " + prev_swb + " -> " + mHasSwbEnabled);
+
+        if (!success) {
+            Log.e(
+                    TAG,
+                    ("processSWBEvent failed: swbCodec: " + swbCodec)
+                            + (" swb_config: " + swbConfig));
+            return;
+        }
+
+        log("processSWBEvent LC3 SWB config: " + prevSwbLc3 + " -> " + mHasSwbLc3Enabled);
+        log("processSWBEvent AptX SWB config: " + prevSwbAptx + " -> " + mHasSwbAptXEnabled);
     }
 
     @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
diff --git a/android/app/tests/unit/src/com/android/bluetooth/hfp/HeadsetStateMachineTest.java b/android/app/tests/unit/src/com/android/bluetooth/hfp/HeadsetStateMachineTest.java
index 2cac8d3709729a836ec1ceff4c5dcb0f196e62df..06b7f69479d5929db1aa0a02e655a1cfa2032548 100644
--- a/android/app/tests/unit/src/com/android/bluetooth/hfp/HeadsetStateMachineTest.java
+++ b/android/app/tests/unit/src/com/android/bluetooth/hfp/HeadsetStateMachineTest.java
@@ -1567,6 +1567,82 @@ public class HeadsetStateMachineTest {
         Assert.assertFalse(setSinkAudioPolicyArgs("SINKAUDIOPOLICY,0,0,0", device));
     }
 
+    /** Test setting audio parameters according to received SWB event. SWB AptX is enabled. */
+    @Test
+    public void testSetAudioParameters_SwbAptxEnabled() {
+        setUpConnectedState();
+        mHeadsetStateMachine.sendMessage(
+                HeadsetStateMachine.STACK_EVENT,
+                new HeadsetStackEvent(
+                        HeadsetStackEvent.EVENT_TYPE_SWB,
+                        HeadsetHalConstants.BTHF_SWB_CODEC_VENDOR_APTX,
+                        HeadsetHalConstants.BTHF_SWB_YES,
+                        mTestDevice));
+
+        mHeadsetStateMachine.sendMessage(
+                HeadsetStateMachine.STACK_EVENT,
+                new HeadsetStackEvent(
+                        HeadsetStackEvent.EVENT_TYPE_AUDIO_STATE_CHANGED,
+                        HeadsetHalConstants.AUDIO_STATE_CONNECTED,
+                        mTestDevice));
+        verifyAudioSystemSetParametersInvocation(false, true);
+    }
+
+    /** Test setting audio parameters according to received SWB event. SWB LC3 is enabled. */
+    @Test
+    public void testSetAudioParameters_SwbLc3Enabled() {
+        setUpConnectedState();
+        mHeadsetStateMachine.sendMessage(
+                HeadsetStateMachine.STACK_EVENT,
+                new HeadsetStackEvent(
+                        HeadsetStackEvent.EVENT_TYPE_SWB,
+                        HeadsetHalConstants.BTHF_SWB_CODEC_LC3,
+                        HeadsetHalConstants.BTHF_SWB_YES,
+                        mTestDevice));
+
+        mHeadsetStateMachine.sendMessage(
+                HeadsetStateMachine.STACK_EVENT,
+                new HeadsetStackEvent(
+                        HeadsetStackEvent.EVENT_TYPE_AUDIO_STATE_CHANGED,
+                        HeadsetHalConstants.AUDIO_STATE_CONNECTED,
+                        mTestDevice));
+        verifyAudioSystemSetParametersInvocation(true, false);
+    }
+
+    /** Test setting audio parameters according to received SWB event. All SWB disabled. */
+    @Test
+    public void testSetAudioParameters_SwbDisabled() {
+        setUpConnectedState();
+        mHeadsetStateMachine.sendMessage(
+                HeadsetStateMachine.STACK_EVENT,
+                new HeadsetStackEvent(
+                        HeadsetStackEvent.EVENT_TYPE_SWB,
+                        HeadsetHalConstants.BTHF_SWB_CODEC_LC3,
+                        HeadsetHalConstants.BTHF_SWB_NO,
+                        mTestDevice));
+
+        mHeadsetStateMachine.sendMessage(
+                HeadsetStateMachine.STACK_EVENT,
+                new HeadsetStackEvent(
+                        HeadsetStackEvent.EVENT_TYPE_AUDIO_STATE_CHANGED,
+                        HeadsetHalConstants.AUDIO_STATE_CONNECTED,
+                        mTestDevice));
+        verifyAudioSystemSetParametersInvocation(false, false);
+    }
+
+    /**
+     * verify parameters given to audio system
+     *
+     * @param lc3Enabled if true check if SWB LC3 was enabled
+     * @param aptxEnabled if true check if SWB AptX was enabled
+     */
+    private void verifyAudioSystemSetParametersInvocation(boolean lc3Enabled, boolean aptxEnabled) {
+        verify(mAudioManager, timeout(ASYNC_CALL_TIMEOUT_MILLIS))
+                .setParameters(lc3Enabled ? "bt_lc3_swb=on" : "bt_lc3_swb=off");
+        verify(mAudioManager, timeout(ASYNC_CALL_TIMEOUT_MILLIS))
+                .setParameters(aptxEnabled ? "bt_swb=0" : "bt_swb=65535");
+    }
+
     /**
      * set sink audio policy
      * @param arg body of the AT command
diff --git a/system/bta/Android.bp b/system/bta/Android.bp
index 4db637e75c9244372de57985aae734d82036fdb3..d029bc53793865408b8eb5776db765aa95fa3457 100644
--- a/system/bta/Android.bp
+++ b/system/bta/Android.bp
@@ -65,6 +65,7 @@ cc_library_static {
         "ag/bta_ag_rfc.cc",
         "ag/bta_ag_sco.cc",
         "ag/bta_ag_sdp.cc",
+        "ag/bta_ag_swb_aptx.cc",
         "ar/bta_ar.cc",
         "av/bta_av_aact.cc",
         "av/bta_av_act.cc",
@@ -242,6 +243,7 @@ cc_test {
         ":TestMockStackPan",
         ":TestMockStackRfcomm",
         "test/bta_ag_sco_test.cc",
+        "test/bta_ag_test.cc",
         "test/bta_api_test.cc",
         "test/bta_av_test.cc",
         "test/bta_dip_test.cc",
@@ -286,7 +288,9 @@ cc_test {
         "libchrome",
         "libcom.android.sysprop.bluetooth.wrapped",
         "libevent",
+        "libflagtest",
         "libgmock",
+        "server_configurable_flags",
     ],
     data: [
         ":audio_set_configurations_bfbs",
diff --git a/system/bta/ag/bta_ag_act.cc b/system/bta/ag/bta_ag_act.cc
index e1a81dc77c6ed7a3607b54361c936319617d67ed..64b3ff51f5555c6e5065983e68355b6a56a36f03 100644
--- a/system/bta/ag/bta_ag_act.cc
+++ b/system/bta/ag/bta_ag_act.cc
@@ -24,11 +24,16 @@
 
 #include <base/logging.h>
 
+#ifdef __ANDROID__
+#include <com_android_bluetooth_flags.h>
+#endif
+
 #include <cstdint>
 #include <cstring>
 
 #include "bta/ag/bta_ag_int.h"
 #include "bta/include/bta_dm_api.h"
+#include "bta_ag_swb_aptx.h"
 
 #ifdef __ANDROID__
 #include "bta/le_audio/devices.h"
@@ -38,9 +43,9 @@
 #include "device/include/device_iot_config.h"
 #include "osi/include/osi.h"  // UNUSED_ATTR
 #include "stack/include/bt_uuid16.h"
+#include "stack/include/btm_sec_api_types.h"
 #include "stack/include/l2c_api.h"
 #include "stack/include/port_api.h"
-#include "stack/include/btm_sec_api_types.h"
 #include "stack/include/sdp_api.h"
 #include "types/raw_address.h"
 
@@ -352,6 +357,7 @@ void bta_ag_rfc_fail(tBTA_AG_SCB* p_scb, UNUSED_ATTR const tBTA_AG_DATA& data) {
   p_scb->peer_features = 0;
   p_scb->peer_codecs = BTM_SCO_CODEC_CVSD;
   p_scb->sco_codec = BTM_SCO_CODEC_CVSD;
+  p_scb->is_aptx_swb_codec = false;
   p_scb->role = 0;
   p_scb->svc_conn = false;
   p_scb->hsp_version = HSP_VERSION_1_2;
@@ -391,6 +397,8 @@ void bta_ag_rfc_close(tBTA_AG_SCB* p_scb,
   p_scb->codec_updated = false;
   p_scb->codec_fallback = false;
   p_scb->codec_msbc_settings = BTA_AG_SCO_MSBC_SETTINGS_T2;
+  p_scb->codec_aptx_settings = BTA_AG_SCO_APTX_SWB_SETTINGS_Q0;
+  p_scb->is_aptx_swb_codec = false;
   p_scb->codec_lc3_settings = BTA_AG_SCO_LC3_SETTINGS_T2;
   p_scb->role = 0;
   p_scb->svc_conn = false;
@@ -840,12 +848,20 @@ void bta_ag_svc_conn_open(tBTA_AG_SCB* p_scb,
 void bta_ag_setcodec(tBTA_AG_SCB* p_scb, const tBTA_AG_DATA& data) {
   tBTA_AG_PEER_CODEC codec_type = data.api_setcodec.codec;
   tBTA_AG_VAL val = {};
+  bool aptx_voice = false;
+#ifdef __ANDROID__
+  if (com::android::bluetooth::flags::hfp_codec_aptx_voice()) {
+    aptx_voice = codec_type == BTA_AG_SCO_APTX_SWB_SETTINGS_Q0;
+    LOG_INFO("aptx_voice=%d", aptx_voice);
+  }
+#endif
   val.hdr.handle = bta_ag_scb_to_idx(p_scb);
 
   /* Check if the requested codec type is valid */
   if ((codec_type != BTM_SCO_CODEC_NONE) &&
       (codec_type != BTM_SCO_CODEC_CVSD) &&
-      (codec_type != BTM_SCO_CODEC_MSBC) && (codec_type != BTM_SCO_CODEC_LC3)) {
+      (codec_type != BTM_SCO_CODEC_MSBC) && (codec_type != BTM_SCO_CODEC_LC3) &&
+      !aptx_voice) {
     val.num = codec_type;
     val.hdr.status = BTA_AG_FAIL_RESOURCES;
     LOG_ERROR("bta_ag_setcodec error: unsupported codec type %d", codec_type);
diff --git a/system/bta/ag/bta_ag_cmd.cc b/system/bta/ag/bta_ag_cmd.cc
index f0064baeec95a6e83e49694d56dffa384aa9f8cd..f643f13e5da80810b3c8a8af69428cbad37bec2a 100644
--- a/system/bta/ag/bta_ag_cmd.cc
+++ b/system/bta/ag/bta_ag_cmd.cc
@@ -20,6 +20,10 @@
 
 #include <base/logging.h>
 
+#ifdef __ANDROID__
+#include <com_android_bluetooth_flags.h>
+#endif
+
 #include <cstdint>
 #include <cstring>
 
@@ -27,6 +31,7 @@
 #include "bta/ag/bta_ag_int.h"
 #include "bta/include/bta_ag_api.h"
 #include "bta/include/utl.h"
+#include "bta_ag_swb_aptx.h"
 
 #ifdef __ANDROID__
 #include "bta_le_audio_api.h"
@@ -113,6 +118,10 @@ static const tBTA_AG_AT_CMD bta_ag_hfp_cmd[] = {
      BTA_AG_AT_SET | BTA_AG_AT_READ | BTA_AG_AT_TEST, BTA_AG_AT_STR, 0, 0},
     {"+BIEV", BTA_AG_AT_BIEV_EVT, BTA_AG_AT_SET, BTA_AG_AT_STR, 0, 0},
     {"+BAC", BTA_AG_AT_BAC_EVT, BTA_AG_AT_SET, BTA_AG_AT_STR, 0, 0},
+    {"+%QAC", BTA_AG_AT_QAC_EVT, BTA_AG_AT_SET, BTA_AG_AT_STR, 0, 0},
+    {"+%QCS", BTA_AG_AT_QCS_EVT, BTA_AG_AT_SET, BTA_AG_AT_INT, 0,
+     BTA_AG_CMD_MAX_VAL},
+
     /* End-of-table marker used to stop lookup iteration */
     {"", 0, 0, 0, 0, 0}};
 
@@ -165,6 +174,9 @@ static const tBTA_AG_RESULT bta_ag_result_tbl[] = {
     {"+CME ERROR: ", BTA_AG_LOCAL_RES_CMEE, BTA_AG_RES_FMT_INT},
     {"+BCS: ", BTA_AG_LOCAL_RES_BCS, BTA_AG_RES_FMT_INT},
     {"+BIND: ", BTA_AG_BIND_RES, BTA_AG_RES_FMT_STR},
+    {"+%QAC: ", BTA_AG_LOCAL_RES_QAC, BTA_AG_RES_FMT_STR},
+    {"+%QCS: ", BTA_AG_LOCAL_RES_QCS, BTA_AG_RES_FMT_INT},
+
     {"", BTA_AG_UNAT_RES, BTA_AG_RES_FMT_STR}};
 
 static const tBTA_AG_RESULT* bta_ag_result_by_code(size_t code) {
@@ -1247,11 +1259,22 @@ void bta_ag_at_hfp_cback(tBTA_AG_SCB* p_scb, uint16_t cmd, uint8_t arg_type,
 
         bool wbs_supported = hfp_hal_interface::get_wbs_supported();
         bool swb_supported = hfp_hal_interface::get_swb_supported();
+        bool aptx_voice = false;
+
+#ifdef __ANDROID__
+        if (com::android::bluetooth::flags::hfp_codec_aptx_voice()) {
+          aptx_voice = p_scb->is_aptx_swb_codec;
+          LOG_VERBOSE("BTA_AG_AT_BAC_EVT aptx_voice=%d", aptx_voice);
+        }
+#endif
 
         if (swb_supported && (p_scb->peer_codecs & BTM_SCO_CODEC_LC3) &&
             !(p_scb->disabled_codecs & BTM_SCO_CODEC_LC3)) {
           p_scb->sco_codec = BTM_SCO_CODEC_LC3;
           LOG_VERBOSE("Received AT+BAC, updating sco codec to LC3");
+        } else if (aptx_voice) {
+          p_scb->sco_codec = BTA_AG_SCO_APTX_SWB_SETTINGS_Q0;
+          LOG_VERBOSE("Received AT+BAC, updating sco codec to AptX Voice");
         } else if (wbs_supported && (p_scb->peer_codecs & BTM_SCO_CODEC_MSBC) &&
                    !(p_scb->disabled_codecs & BTM_SCO_CODEC_MSBC)) {
           p_scb->sco_codec = BTM_SCO_CODEC_MSBC;
@@ -1328,6 +1351,28 @@ void bta_ag_at_hfp_cback(tBTA_AG_SCB* p_scb, uint16_t cmd, uint8_t arg_type,
       bta_ag_sco_open(p_scb, tBTA_AG_DATA::kEmpty);
       break;
     }
+#ifdef __ANDROID__
+    case BTA_AG_AT_QAC_EVT:
+      if (com::android::bluetooth::flags::hfp_codec_aptx_voice()) {
+        p_scb->peer_codecs |= bta_ag_parse_qac(p_arg);
+        // AT+%QAC needs to be responded with +%QAC
+        bta_ag_swb_handle_vs_at_events(p_scb, cmd, int_arg, &val);
+        // followed by OK
+        bta_ag_send_ok(p_scb);
+        break;
+      }
+      FALLTHROUGH_INTENDED;
+    case BTA_AG_AT_QCS_EVT:
+      if (com::android::bluetooth::flags::hfp_codec_aptx_voice()) {
+        // AT+%QCS is a response to +%QCS sent from AG.
+        // Send OK to BT headset
+        bta_ag_send_ok(p_scb);
+        // Handle AT+%QCS
+        bta_ag_swb_handle_vs_at_events(p_scb, cmd, int_arg, &val);
+        break;
+      }
+      FALLTHROUGH_INTENDED;
+#endif
     default:
       bta_ag_send_error(p_scb, BTA_AG_ERR_OP_NOT_SUPPORTED);
       break;
@@ -1896,3 +1941,46 @@ void bta_ag_send_ring(tBTA_AG_SCB* p_scb,
   bta_sys_start_timer(p_scb->ring_timer, BTA_AG_RING_TIMEOUT_MS,
                       BTA_AG_RING_TIMEOUT_EVT, bta_ag_scb_to_idx(p_scb));
 }
+
+/*******************************************************************************
+ *
+ * Function         bta_ag_send_qcs
+ *
+ * Description      Send +%QCS AT command to peer.
+ *
+ * Returns          void
+ *
+ ******************************************************************************/
+void bta_ag_send_qcs(tBTA_AG_SCB* p_scb, tBTA_AG_DATA* p_data) {
+  uint16_t codec_uuid;
+  if (p_scb->codec_fallback) {
+    if (p_scb->peer_codecs & BTM_SCO_CODEC_MSBC) {
+      codec_uuid = UUID_CODEC_MSBC;
+    } else {
+      codec_uuid = UUID_CODEC_CVSD;
+    }
+  } else {
+    codec_uuid = BTA_AG_SCO_APTX_SWB_SETTINGS_Q0;
+  }
+
+  LOG_VERBOSE("send +QCS codec is %d", codec_uuid);
+  bta_ag_send_result(p_scb, BTA_AG_LOCAL_RES_QCS, NULL, codec_uuid);
+}
+
+/*******************************************************************************
+ *
+ * Function         bta_ag_send_qac
+ *
+ * Description      Send +%QAC AT command to peer.
+ *
+ * Returns          void
+ *
+ ******************************************************************************/
+void bta_ag_send_qac(tBTA_AG_SCB* p_scb, tBTA_AG_DATA* p_data) {
+  LOG_VERBOSE("send +QAC codecs supported");
+  bta_ag_send_result(p_scb, BTA_AG_LOCAL_RES_QAC, SWB_CODECS_SUPPORTED, 0);
+
+  if (p_scb->sco_codec == BTA_AG_SCO_APTX_SWB_SETTINGS_Q0) {
+    p_scb->is_aptx_swb_codec = true;
+  }
+}
diff --git a/system/bta/ag/bta_ag_int.h b/system/bta/ag/bta_ag_int.h
index 05d498416d74feaa9c489796121b6639bb66a3b6..57f6704e16c9d379aef77d2c7723866028d8bb09 100644
--- a/system/bta/ag/bta_ag_int.h
+++ b/system/bta/ag/bta_ag_int.h
@@ -209,6 +209,14 @@ typedef enum {
   BTA_AG_SCO_LC3_SETTINGS_T1,
 } tBTA_AG_SCO_LC3_SETTINGS;
 
+typedef enum {
+  BTA_AG_SCO_APTX_SWB_SETTINGS_Q0 = 0, /* preferred/default when codec is SWB */
+  BTA_AG_SCO_APTX_SWB_SETTINGS_Q1 = 4,
+  BTA_AG_SCO_APTX_SWB_SETTINGS_Q2 = 6,
+  BTA_AG_SCO_APTX_SWB_SETTINGS_Q3 = 7,
+  BTA_AG_SCO_APTX_SWB_SETTINGS_UNKNOWN = 0xFFFF,
+} tBTA_AG_SCO_APTX_SWB_SETTINGS;
+
 /* type for each service control block */
 struct tBTA_AG_SCB {
   char clip[BTA_AG_AT_MAX_LEN + 1];     /* number string used for CLIP */
@@ -265,6 +273,10 @@ struct tBTA_AG_SCB {
                                                     impending eSCO on WB */
   tBTA_AG_SCO_LC3_SETTINGS codec_lc3_settings;   /* settings to be used for the
                                                     impending eSCO on SWB */
+  tBTA_AG_SCO_APTX_SWB_SETTINGS
+      codec_aptx_settings; /* settings to be used for the
+                              aptX Voice SWB eSCO */
+  bool is_aptx_swb_codec;  /* Flag to determine aptX Voice SWB codec  */
 
   tBTA_AG_HF_IND
       peer_hf_indicators[BTA_AG_MAX_NUM_PEER_HF_IND]; /* Peer supported
@@ -421,5 +433,7 @@ void bta_ag_set_sco_offload_enabled(bool value);
 void bta_ag_set_sco_allowed(bool value);
 const RawAddress& bta_ag_get_active_device();
 void bta_clear_active_device();
+void bta_ag_send_qac(tBTA_AG_SCB* p_scb, tBTA_AG_DATA* p_data);
+void bta_ag_send_qcs(tBTA_AG_SCB* p_scb, tBTA_AG_DATA* p_data);
 
 #endif /* BTA_AG_INT_H */
diff --git a/system/bta/ag/bta_ag_main.cc b/system/bta/ag/bta_ag_main.cc
index 4aca16e25df017d37166c1cef26764965c3744dd..625716b20ff7fbe47f548498af18e3b749cb1935 100644
--- a/system/bta/ag/bta_ag_main.cc
+++ b/system/bta/ag/bta_ag_main.cc
@@ -160,6 +160,9 @@ static tBTA_AG_SCB* bta_ag_scb_alloc(void) {
       /* set eSCO mSBC setting to T2 as the preferred */
       p_scb->codec_msbc_settings = BTA_AG_SCO_MSBC_SETTINGS_T2;
       p_scb->codec_lc3_settings = BTA_AG_SCO_LC3_SETTINGS_T2;
+      /* set eSCO SWB setting to Q0 as the preferred */
+      p_scb->codec_aptx_settings = BTA_AG_SCO_APTX_SWB_SETTINGS_Q0;
+      p_scb->is_aptx_swb_codec = false;
       LOG_VERBOSE("bta_ag_scb_alloc %d", bta_ag_scb_to_idx(p_scb));
       break;
     }
diff --git a/system/bta/ag/bta_ag_sco.cc b/system/bta/ag/bta_ag_sco.cc
index 5015c0584c646b3de07e00beb83da54250e8fb20..39937240c65c3b65b63bcd9fc7c4eebeb0105f2f 100644
--- a/system/bta/ag/bta_ag_sco.cc
+++ b/system/bta/ag/bta_ag_sco.cc
@@ -25,11 +25,16 @@
 #include <base/functional/bind.h>
 #include <base/logging.h>
 
+#ifdef __ANDROID__
+#include <com_android_bluetooth_flags.h>
+#endif
+
 #include <cstdint>
 
 #include "bt_target.h"  // Must be first to define build configuration
 #include "bt_trace.h"   // Legacy trace logging
 #include "bta/ag/bta_ag_int.h"
+#include "bta_ag_swb_aptx.h"
 #include "common/init_flags.h"
 #include "device/include/controller.h"
 #include "main/shim/dumpsys.h"
@@ -195,9 +200,19 @@ static void bta_ag_sco_disc_cback(uint16_t sco_idx) {
   }
 
   if (handle != 0) {
+    bool aptx_voice = false;
+#ifdef __ANDROID__
+    if (com::android::bluetooth::flags::hfp_codec_aptx_voice() &&
+        bta_ag_cb.sco.p_curr_scb->is_aptx_swb_codec == true) {
+      aptx_voice = bta_ag_cb.sco.p_curr_scb->inuse_codec ==
+                   BTA_AG_SCO_APTX_SWB_SETTINGS_Q0;
+      LOG_VERBOSE("aptx_voice=%d, inuse_codec=%d", aptx_voice,
+                  bta_ag_cb.sco.p_curr_scb->inuse_codec);
+    }
+#endif
     /* Restore settings */
     if (bta_ag_cb.sco.p_curr_scb->inuse_codec == UUID_CODEC_MSBC ||
-        bta_ag_cb.sco.p_curr_scb->inuse_codec == UUID_CODEC_LC3) {
+        bta_ag_cb.sco.p_curr_scb->inuse_codec == UUID_CODEC_LC3 || aptx_voice) {
       /* Bypass vendor specific and voice settings if enhanced eSCO supported */
       if (!(controller_get_interface()
                 ->supports_enhanced_setup_synchronous_connection())) {
@@ -410,6 +425,15 @@ void bta_ag_create_sco(tBTA_AG_SCB* p_scb, bool is_orig) {
   }
 #endif
 
+#ifdef __ANDROID__
+  if (com::android::bluetooth::flags::hfp_codec_aptx_voice()) {
+    if ((p_scb->sco_codec == BTA_AG_SCO_APTX_SWB_SETTINGS_Q0) &&
+        !p_scb->codec_fallback) {
+      esco_codec = BTA_AG_SCO_APTX_SWB_SETTINGS_Q0;
+    }
+  }
+#endif
+
   if ((p_scb->sco_codec == BTM_SCO_CODEC_LC3) && !p_scb->codec_fallback &&
       hfp_hal_interface::get_swb_supported()) {
     esco_codec = UUID_CODEC_LC3;
@@ -423,6 +447,8 @@ void bta_ag_create_sco(tBTA_AG_SCB* p_scb, bool is_orig) {
     p_scb->codec_msbc_settings = BTA_AG_SCO_MSBC_SETTINGS_T2;
     /* Reset LC3 settings to T2 for the next audio connection */
     p_scb->codec_lc3_settings = BTA_AG_SCO_LC3_SETTINGS_T2;
+    /* Reset SWB settings to Q3 for the next audio connection */
+    p_scb->codec_aptx_settings = BTA_AG_SCO_APTX_SWB_SETTINGS_Q0;
   }
 
   bool offload = hfp_hal_interface::get_offload_enabled();
@@ -445,6 +471,19 @@ void bta_ag_create_sco(tBTA_AG_SCB* p_scb, bool is_orig) {
     } else {
       params = esco_parameters_for_codec(ESCO_CODEC_MSBC_T1, offload);
     }
+#ifdef __ANDROID__
+  } else if (com::android::bluetooth::flags::hfp_codec_aptx_voice() &&
+             (p_scb->is_aptx_swb_codec == true && !p_scb->codec_updated)) {
+    if (p_scb->codec_aptx_settings == BTA_AG_SCO_APTX_SWB_SETTINGS_Q3) {
+      params = esco_parameters_for_codec(ESCO_CODEC_SWB_Q3, true);
+    } else if (p_scb->codec_aptx_settings == BTA_AG_SCO_APTX_SWB_SETTINGS_Q2) {
+      params = esco_parameters_for_codec(ESCO_CODEC_SWB_Q2, true);
+    } else if (p_scb->codec_aptx_settings == BTA_AG_SCO_APTX_SWB_SETTINGS_Q1) {
+      params = esco_parameters_for_codec(ESCO_CODEC_SWB_Q1, true);
+    } else if (p_scb->codec_aptx_settings == BTA_AG_SCO_APTX_SWB_SETTINGS_Q0) {
+      params = esco_parameters_for_codec(ESCO_CODEC_SWB_Q0, true);
+    }
+#endif
   } else {
     if ((p_scb->features & BTA_AG_FEAT_ESCO_S4) &&
         (p_scb->peer_features & BTA_AG_PEER_FEAT_ESCO_S4)) {
@@ -518,6 +557,22 @@ void bta_ag_create_pending_sco(tBTA_AG_SCB* p_scb, bool is_local) {
       } else {
         params = esco_parameters_for_codec(ESCO_CODEC_LC3_T1, offload);
       }
+#ifdef __ANDROID__
+    } else if (com::android::bluetooth::flags::hfp_codec_aptx_voice() &&
+               (p_scb->is_aptx_swb_codec == true && !p_scb->codec_updated)) {
+      if (p_scb->codec_aptx_settings == BTA_AG_SCO_APTX_SWB_SETTINGS_Q3) {
+        params = esco_parameters_for_codec(ESCO_CODEC_SWB_Q3, true);
+      } else if (p_scb->codec_aptx_settings ==
+                 BTA_AG_SCO_APTX_SWB_SETTINGS_Q2) {
+        params = esco_parameters_for_codec(ESCO_CODEC_SWB_Q2, true);
+      } else if (p_scb->codec_aptx_settings ==
+                 BTA_AG_SCO_APTX_SWB_SETTINGS_Q1) {
+        params = esco_parameters_for_codec(ESCO_CODEC_SWB_Q1, true);
+      } else if (p_scb->codec_aptx_settings ==
+                 BTA_AG_SCO_APTX_SWB_SETTINGS_Q0) {
+        params = esco_parameters_for_codec(ESCO_CODEC_SWB_Q0, true);
+      }
+#endif
     } else if (esco_codec == UUID_CODEC_MSBC) {
       if (p_scb->codec_msbc_settings == BTA_AG_SCO_MSBC_SETTINGS_T2) {
         params = esco_parameters_for_codec(ESCO_CODEC_MSBC_T2, offload);
@@ -639,16 +694,47 @@ void bta_ag_codec_negotiate(tBTA_AG_SCB* p_scb) {
     LOG_INFO("Assume CVSD by default due to mask mismatch");
     p_scb->sco_codec = UUID_CODEC_CVSD;
   }
+  bool aptx_voice = false;
+#ifdef __ANDROID__
+  if (com::android::bluetooth::flags::hfp_codec_aptx_voice()) {
+    aptx_voice = p_scb->is_aptx_swb_codec;
+    LOG_VERBOSE("aptx_voice=%d, is_aptx_swb_codec=%d", aptx_voice,
+                p_scb->is_aptx_swb_codec);
+  }
+#endif
 
-  if ((p_scb->codec_updated || p_scb->codec_fallback) &&
-      (p_scb->features & BTA_AG_FEAT_CODEC) &&
-      (p_scb->peer_features & BTA_AG_PEER_FEAT_CODEC)) {
+  if (((p_scb->codec_updated || p_scb->codec_fallback) &&
+       (p_scb->features & BTA_AG_FEAT_CODEC) &&
+       (p_scb->peer_features & BTA_AG_PEER_FEAT_CODEC)) ||
+      (aptx_voice &&
+       (p_scb->peer_codecs & BTA_AG_SCO_APTX_SWB_SETTINGS_Q0_MASK))) {
     LOG_INFO("Starting codec negotiation");
     /* Change the power mode to Active until SCO open is completed. */
     bta_sys_busy(BTA_ID_AG, p_scb->app_id, p_scb->peer_addr);
 
-    /* Send +BCS to the peer */
-    bta_ag_send_bcs(p_scb);
+    if (p_scb->peer_codecs & BTA_AG_SCO_APTX_SWB_SETTINGS_Q0_MASK) {
+      if (p_scb->is_aptx_swb_codec == false) {
+        p_scb->sco_codec = BTA_AG_SCO_APTX_SWB_SETTINGS_Q0;
+        p_scb->is_aptx_swb_codec = true;
+      }
+      LOG_VERBOSE("Sending +QCS, sco_codec=%d, is_aptx_swb_codec=%d",
+                  p_scb->sco_codec, p_scb->is_aptx_swb_codec);
+      /* Send +QCS to the peer */
+      bta_ag_send_qcs(p_scb, NULL);
+    } else {
+#ifdef __ANDROID__
+      if (com::android::bluetooth::flags::hfp_codec_aptx_voice() &&
+          ((p_scb->is_aptx_swb_codec == true) &&
+           (p_scb->peer_codecs & BTA_AG_SCO_APTX_SWB_SETTINGS_Q0_MASK))) {
+        p_scb->sco_codec = BTM_SCO_CODEC_MSBC;
+        p_scb->is_aptx_swb_codec = false;
+      }
+#endif
+      LOG_VERBOSE("Sending +BCS, sco_codec=%d, is_aptx_swb_codec=%d",
+                  p_scb->sco_codec, p_scb->is_aptx_swb_codec);
+      /* Send +BCS to the peer */
+      bta_ag_send_bcs(p_scb);
+    }
 
     /* Start timer to handle timeout */
     alarm_set_on_mloop(p_scb->codec_negotiation_timer,
@@ -1360,6 +1446,8 @@ void bta_ag_sco_conn_open(tBTA_AG_SCB* p_scb,
   p_scb->codec_msbc_settings = BTA_AG_SCO_MSBC_SETTINGS_T2;
   /* reset to LC3 T2 settings as the preferred */
   p_scb->codec_lc3_settings = BTA_AG_SCO_LC3_SETTINGS_T2;
+  /* reset to SWB Q0 settings as the preferred */
+  p_scb->codec_aptx_settings = BTA_AG_SCO_APTX_SWB_SETTINGS_Q0;
 }
 
 /*******************************************************************************
@@ -1374,9 +1462,18 @@ void bta_ag_sco_conn_open(tBTA_AG_SCB* p_scb,
  ******************************************************************************/
 void bta_ag_sco_conn_close(tBTA_AG_SCB* p_scb,
                            UNUSED_ATTR const tBTA_AG_DATA& data) {
+  bool aptx_voice = false;
   /* clear current scb */
   bta_ag_cb.sco.p_curr_scb = nullptr;
   p_scb->sco_idx = BTM_INVALID_SCO_INDEX;
+#ifdef __ANDROID__
+  if (com::android::bluetooth::flags::hfp_codec_aptx_voice()) {
+    aptx_voice = p_scb->codec_fallback &&
+                 p_scb->sco_codec == BTA_AG_SCO_APTX_SWB_SETTINGS_Q0;
+    LOG_VERBOSE("aptx_voice=%d, codec_fallback=%d, sco_codec=%d", aptx_voice,
+                p_scb->codec_fallback, p_scb->sco_codec);
+  }
+#endif
 
   /* codec_fallback is set when AG is initiator and connection failed for mSBC.
    * OR if codec is msbc and T2 settings failed, then retry Safe T1 settings
@@ -1386,7 +1483,8 @@ void bta_ag_sco_conn_close(tBTA_AG_SCB* p_scb,
        (p_scb->sco_codec == BTM_SCO_CODEC_MSBC &&
         p_scb->codec_msbc_settings == BTA_AG_SCO_MSBC_SETTINGS_T1) ||
        (p_scb->sco_codec == BTM_SCO_CODEC_LC3 &&
-        p_scb->codec_lc3_settings == BTA_AG_SCO_LC3_SETTINGS_T1))) {
+        p_scb->codec_lc3_settings == BTA_AG_SCO_LC3_SETTINGS_T1) ||
+       aptx_voice)) {
     bta_ag_sco_event(p_scb, BTA_AG_SCO_REOPEN_E);
   } else {
     /* Indicate if the closing of audio is because of transfer */
@@ -1406,6 +1504,7 @@ void bta_ag_sco_conn_close(tBTA_AG_SCB* p_scb,
     bta_ag_cback_sco(p_scb, BTA_AG_AUDIO_CLOSE_EVT);
     p_scb->codec_msbc_settings = BTA_AG_SCO_MSBC_SETTINGS_T2;
     p_scb->codec_lc3_settings = BTA_AG_SCO_LC3_SETTINGS_T2;
+    p_scb->codec_aptx_settings = BTA_AG_SCO_APTX_SWB_SETTINGS_Q0;
   }
 }
 
diff --git a/system/bta/ag/bta_ag_swb_aptx.cc b/system/bta/ag/bta_ag_swb_aptx.cc
new file mode 100644
index 0000000000000000000000000000000000000000..893eff90c56e5c612dd4e581185c4bdefdff5175
--- /dev/null
+++ b/system/bta/ag/bta_ag_swb_aptx.cc
@@ -0,0 +1,120 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "bta_ag_swb_aptx.h"
+
+#include <string.h>
+#include <unistd.h>
+
+#include "bta/ag/bta_ag_int.h"
+#include "common/strings.h"
+#include "device/include/interop.h"
+#include "include/hardware/bt_hf.h"
+#include "internal_include/bt_trace.h"
+#include "stack/btm/btm_sco_hfp_hal.h"
+#include "stack/include/btm_api_types.h"
+#include "types/raw_address.h"
+#include "utl.h"
+
+void bta_ag_swb_handle_vs_at_events(tBTA_AG_SCB* p_scb, uint16_t cmd,
+                                    int16_t int_arg, tBTA_AG_VAL* val) {
+  switch (cmd) {
+    case BTA_AG_AT_QAC_EVT:
+      LOG_VERBOSE("BTA_AG_AT_QAC_EVT");
+      p_scb->codec_updated = true;
+      if (p_scb->peer_codecs & BTA_AG_SCO_APTX_SWB_SETTINGS_Q0_MASK) {
+        p_scb->sco_codec = BTA_AG_SCO_APTX_SWB_SETTINGS_Q0;
+      } else if (p_scb->peer_codecs & BTM_SCO_CODEC_MSBC) {
+        p_scb->sco_codec = UUID_CODEC_MSBC;
+      }
+      bta_ag_send_qac(p_scb, NULL);
+      LOG_VERBOSE("Received AT+QAC, updating sco codec to SWB: %d",
+                  p_scb->sco_codec);
+      val->num = p_scb->peer_codecs;
+      break;
+    case BTA_AG_AT_QCS_EVT: {
+      tBTA_AG_PEER_CODEC codec_type, codec_sent;
+      alarm_cancel(p_scb->codec_negotiation_timer);
+
+      LOG_VERBOSE("BTA_AG_AT_QCS_EVT int_arg=%d", int_arg);
+      switch (int_arg) {
+        case BTA_AG_SCO_APTX_SWB_SETTINGS_Q0:
+          codec_type = BTA_AG_SCO_APTX_SWB_SETTINGS_Q0;
+          break;
+        case BTA_AG_SCO_APTX_SWB_SETTINGS_Q1:
+          codec_type = BTA_AG_SCO_APTX_SWB_SETTINGS_Q1;
+          break;
+        case BTA_AG_SCO_APTX_SWB_SETTINGS_Q2:
+          codec_type = BTA_AG_SCO_APTX_SWB_SETTINGS_Q2;
+          break;
+        case BTA_AG_SCO_APTX_SWB_SETTINGS_Q3:
+          codec_type = BTA_AG_SCO_APTX_SWB_SETTINGS_Q3;
+          break;
+        default:
+          LOG_ERROR("Unknown codec_uuid %d", int_arg);
+          p_scb->is_aptx_swb_codec = false;
+          codec_type = BTM_SCO_CODEC_MSBC;
+          p_scb->codec_fallback = true;
+          p_scb->sco_codec = BTM_SCO_CODEC_MSBC;
+          break;
+      }
+
+      if (p_scb->codec_fallback) {
+        codec_sent = BTM_SCO_CODEC_MSBC;
+      } else {
+        codec_sent = p_scb->sco_codec;
+      }
+
+      bta_ag_sco_codec_nego(p_scb, codec_type == codec_sent);
+
+      /* send final codec info to callback */
+      val->num = codec_sent;
+      break;
+    }
+  }
+}
+
+tBTA_AG_PEER_CODEC bta_ag_parse_qac(char* p_s) {
+  tBTA_AG_PEER_CODEC retval = BTM_SCO_CODEC_NONE;
+  tBTA_AG_SCO_APTX_SWB_SETTINGS codec_mode =
+      BTA_AG_SCO_APTX_SWB_SETTINGS_UNKNOWN;
+
+  auto codec_modes =
+      bluetooth::common::StringSplit(std::string(p_s), ",", SWB_CODECS_NUMBER);
+  for (auto& codec_mode_str : codec_modes) {
+    if (!std::isdigit(*codec_mode_str.c_str())) continue;
+    codec_mode = static_cast<tBTA_AG_SCO_APTX_SWB_SETTINGS>(
+        std::atoi(codec_mode_str.c_str()));
+    switch (codec_mode) {
+      case BTA_AG_SCO_APTX_SWB_SETTINGS_Q0:
+        retval |= BTA_AG_SCO_APTX_SWB_SETTINGS_Q0_MASK;
+        break;
+      case BTA_AG_SCO_APTX_SWB_SETTINGS_Q1:
+        retval |= BTA_AG_SCO_APTX_SWB_SETTINGS_Q1_MASK;
+        break;
+      case BTA_AG_SCO_APTX_SWB_SETTINGS_Q2:
+        retval |= BTA_AG_SCO_APTX_SWB_SETTINGS_Q2_MASK;
+        break;
+      case BTA_AG_SCO_APTX_SWB_SETTINGS_Q3:
+        retval |= BTA_AG_SCO_APTX_SWB_SETTINGS_Q3_MASK;
+        break;
+      default:
+        LOG_VERBOSE("Unknown Codec UUID(%d) received\n", codec_mode);
+        break;
+    }
+  }
+  return (retval);
+}
diff --git a/system/bta/include/bta_ag_swb_aptx.h b/system/bta/include/bta_ag_swb_aptx.h
new file mode 100644
index 0000000000000000000000000000000000000000..fd2a34892bb3e4b1b9c46fdc57ec038167f9502f
--- /dev/null
+++ b/system/bta/include/bta_ag_swb_aptx.h
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _BTA_AG_SWB_H_
+#define _BTA_AG_SWB_H_
+
+#include "bta/ag/bta_ag_int.h"
+#include "device/include/esco_parameters.h"
+#include "include/hardware/bt_hf.h"
+
+/* Events originated from HF side */
+#define BTA_AG_AT_QAC_EVT 253
+#define BTA_AG_AT_QCS_EVT 254
+#define BTA_AG_LOCAL_RES_QAC 0x108
+#define BTA_AG_LOCAL_RES_QCS 0x109
+
+#define SWB_CODECS_SUPPORTED "0,4,6,7"
+#define SWB_CODECS_UNSUPPORTED "0xFFFF"
+#define SWB_CODECS_NUMBER 4
+
+void bta_ag_swb_handle_vs_at_events(tBTA_AG_SCB* p_scb, uint16_t cmd,
+                                    int16_t int_arg, tBTA_AG_VAL* val);
+tBTA_AG_PEER_CODEC bta_ag_parse_qac(char* p_s);
+
+#endif  //_BTA_AG_SWB_H_
diff --git a/system/bta/test/bta_ag_test.cc b/system/bta/test/bta_ag_test.cc
new file mode 100644
index 0000000000000000000000000000000000000000..f405288346cf947bf2bfded9dd6036d5b42ff239
--- /dev/null
+++ b/system/bta/test/bta_ag_test.cc
@@ -0,0 +1,316 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <base/functional/bind.h>
+#include <base/location.h>
+#include <com_android_bluetooth_flags.h>
+#include <flag_macros.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+#include <chrono>
+#include <iostream>
+#include <memory>
+#include <string>
+
+#include "bta/ag/bta_ag_int.h"
+#include "bta/include/bta_ag_swb_aptx.h"
+#include "bta/include/bta_api.h"
+#include "bta/include/bta_dm_api.h"
+#include "bta/include/bta_hf_client_api.h"
+#include "bta/include/bta_le_audio_api.h"
+#include "btif/include/stack_manager.h"
+#include "common/message_loop_thread.h"
+#include "os/system_properties.h"
+#include "osi/include/compat.h"
+#include "stack/btm/btm_int_types.h"
+#include "stack/include/bt_device_type.h"
+#include "stack/include/bt_name.h"
+#include "stack/include/btm_status.h"
+#include "test/common/main_handler.h"
+#include "test/common/mock_functions.h"
+#include "test/fake/fake_osi.h"
+#include "test/mock/mock_bta_sys_main.h"
+#include "test/mock/mock_device_esco_parameters.h"
+#include "test/mock/mock_osi_alarm.h"
+#include "test/mock/mock_stack_acl.h"
+
+#define TEST_BT com::android::bluetooth::flags
+
+namespace {
+
+bool bta_ag_hdl_event(const BT_HDR_RIGID* p_msg) { return true; };
+void BTA_AgDisable() { bta_sys_deregister(BTA_ID_AG); }
+
+const tBTA_SYS_REG bta_ag_reg = {bta_ag_hdl_event, BTA_AgDisable};
+
+}  // namespace
+
+const std::string kBtCodecAptxVoiceEnabled =
+    "bluetooth.hfp.codec_aptx_voice.enabled";
+
+static bool enable_aptx_voice_property(bool enable) {
+  const std::string value = enable ? "true" : "false";
+  bool result =
+      bluetooth::os::SetSystemProperty(kBtCodecAptxVoiceEnabled, value);
+  auto codec_aptx_voice_enabled =
+      bluetooth::os::GetSystemProperty(kBtCodecAptxVoiceEnabled);
+  return result && codec_aptx_voice_enabled &&
+         (codec_aptx_voice_enabled.value() == value);
+}
+
+class BtaAgTest : public testing::Test {
+ protected:
+  void SetUp() override {
+    reset_mock_function_count_map();
+    fake_osi_ = std::make_unique<test::fake::FakeOsi>();
+
+    main_thread_start_up();
+    post_on_bt_main([]() { LOG_INFO("Main thread started up"); });
+
+    bta_sys_register(BTA_ID_AG, &bta_ag_reg);
+
+    bta_ag_cb.p_cback = [](tBTA_AG_EVT event, tBTA_AG* p_data) {};
+    RawAddress::FromString("00:11:22:33:44:55", addr);
+    test::mock::device_esco_parameters::esco_parameters_for_codec.body =
+        [this](esco_codec_t codec) {
+          this->codec = codec;
+          return enh_esco_params_t{};
+        };
+  }
+  void TearDown() override {
+    test::mock::device_esco_parameters::esco_parameters_for_codec = {};
+    bta_sys_deregister(BTA_ID_AG);
+    post_on_bt_main([]() { LOG_INFO("Main thread shutting down"); });
+    main_thread_shut_down();
+  }
+
+  std::unique_ptr<test::fake::FakeOsi> fake_osi_;
+  const char test_strings[5][13] = {"0,4,6,7", "4,6,7", "test,0,4", "9,8,7",
+                                    "4,6,7,test"};
+  uint32_t tmp_num = 0xFFFF;
+  RawAddress addr;
+  esco_codec_t codec;
+};
+
+TEST_F_WITH_FLAGS(BtaAgTest, nop,
+                  REQUIRES_FLAGS_ENABLED(ACONFIG_FLAG(TEST_BT,
+                                                      hfp_codec_aptx_voice))) {
+  bool status = true;
+  ASSERT_EQ(true, status);
+}
+
+class BtaAgSwbTest : public BtaAgTest {
+ protected:
+  void SetUp() override { BtaAgTest::SetUp(); }
+  void TearDown() override { BtaAgTest::TearDown(); }
+};
+
+TEST_F_WITH_FLAGS(BtaAgSwbTest, parse_qac_at_command,
+                  REQUIRES_FLAGS_ENABLED(ACONFIG_FLAG(TEST_BT,
+                                                      hfp_codec_aptx_voice))) {
+  tBTA_AG_PEER_CODEC codec = bta_ag_parse_qac((char*)test_strings[0]);
+  codec = bta_ag_parse_qac((char*)test_strings[0]);
+  ASSERT_TRUE(codec & BTA_AG_SCO_APTX_SWB_SETTINGS_Q0_MASK);
+  ASSERT_TRUE(codec & BTA_AG_SCO_APTX_SWB_SETTINGS_Q1_MASK);
+  ASSERT_TRUE(codec & BTA_AG_SCO_APTX_SWB_SETTINGS_Q2_MASK);
+  ASSERT_TRUE(codec & BTA_AG_SCO_APTX_SWB_SETTINGS_Q3_MASK);
+
+  codec = bta_ag_parse_qac((char*)test_strings[1]);
+  ASSERT_TRUE(codec & BTA_AG_SCO_APTX_SWB_SETTINGS_Q1_MASK);
+  ASSERT_TRUE(codec & BTA_AG_SCO_APTX_SWB_SETTINGS_Q2_MASK);
+  ASSERT_TRUE(codec & BTA_AG_SCO_APTX_SWB_SETTINGS_Q3_MASK);
+
+  codec = bta_ag_parse_qac((char*)test_strings[2]);
+  ASSERT_TRUE(codec & BTA_AG_SCO_APTX_SWB_SETTINGS_Q0_MASK);
+  ASSERT_TRUE(codec & BTA_AG_SCO_APTX_SWB_SETTINGS_Q1_MASK);
+
+  codec = bta_ag_parse_qac((char*)test_strings[3]);
+  ASSERT_TRUE(codec & BTA_AG_SCO_APTX_SWB_SETTINGS_Q3_MASK);
+
+  codec = bta_ag_parse_qac((char*)test_strings[4]);
+  ASSERT_TRUE(codec & BTA_AG_SCO_APTX_SWB_SETTINGS_Q1_MASK);
+  ASSERT_TRUE(codec & BTA_AG_SCO_APTX_SWB_SETTINGS_Q2_MASK);
+  ASSERT_TRUE(codec & BTA_AG_SCO_APTX_SWB_SETTINGS_Q3_MASK);
+}
+
+class BtaAgActTest : public BtaAgTest {
+ protected:
+  void SetUp() override { BtaAgTest::SetUp(); }
+  void TearDown() override { BtaAgTest::TearDown(); }
+};
+
+TEST_F_WITH_FLAGS(BtaAgActTest, set_codec_q0_success,
+                  REQUIRES_FLAGS_ENABLED(ACONFIG_FLAG(TEST_BT,
+                                                      hfp_codec_aptx_voice))) {
+  tBTA_AG_SCB* p_scb = &bta_ag_cb.scb[0];
+  const tBTA_AG_DATA data = {.api_setcodec.codec =
+                                 BTA_AG_SCO_APTX_SWB_SETTINGS_Q0};
+
+  bta_ag_cb.p_cback = [](tBTA_AG_EVT event, tBTA_AG* p_data) {
+    tBTA_AG_VAL* val = (tBTA_AG_VAL*)p_data;
+    ASSERT_EQ(val->num, BTA_AG_SCO_APTX_SWB_SETTINGS_Q0);
+    ASSERT_EQ(val->hdr.status, BTA_AG_SUCCESS);
+  };
+
+  p_scb->peer_codecs = BTA_AG_SCO_APTX_SWB_SETTINGS_Q0;
+  p_scb->sco_codec = BTM_SCO_CODEC_NONE;
+  p_scb->codec_updated = false;
+
+  bta_ag_setcodec(p_scb, data);
+  ASSERT_EQ(p_scb->sco_codec, BTA_AG_SCO_APTX_SWB_SETTINGS_Q0);
+  ASSERT_TRUE(
+      bluetooth::os::SetSystemProperty(kBtCodecAptxVoiceEnabled, "false"));
+}
+
+TEST_F_WITH_FLAGS(BtaAgActTest, set_codec_q1_fail_unsupported,
+                  REQUIRES_FLAGS_ENABLED(ACONFIG_FLAG(TEST_BT,
+                                                      hfp_codec_aptx_voice))) {
+  tBTA_AG_SCB* p_scb = &bta_ag_cb.scb[0];
+  const tBTA_AG_DATA data = {.api_setcodec.codec =
+                                 BTA_AG_SCO_APTX_SWB_SETTINGS_Q1};
+
+  ASSERT_TRUE(enable_aptx_voice_property(true));
+
+  bta_ag_cb.p_cback = [](tBTA_AG_EVT event, tBTA_AG* p_data) {
+    tBTA_AG_VAL* val = (tBTA_AG_VAL*)p_data;
+    ASSERT_EQ(val->num, BTA_AG_SCO_APTX_SWB_SETTINGS_Q1);
+    ASSERT_EQ(val->hdr.status, BTA_AG_FAIL_RESOURCES);
+  };
+
+  p_scb->peer_codecs = BTA_AG_SCO_APTX_SWB_SETTINGS_Q0;
+  p_scb->sco_codec = BTM_SCO_CODEC_NONE;
+  p_scb->codec_updated = false;
+
+  bta_ag_setcodec(p_scb, data);
+  ASSERT_TRUE(enable_aptx_voice_property(false));
+}
+
+class BtaAgCmdTest : public BtaAgTest {
+ protected:
+  void SetUp() override { BtaAgTest::SetUp(); }
+  void TearDown() override { BtaAgTest::TearDown(); }
+};
+
+TEST_F_WITH_FLAGS(BtaAgCmdTest, at_hfp_cback__qac_ev_codec_disabled,
+                  REQUIRES_FLAGS_ENABLED(ACONFIG_FLAG(TEST_BT,
+                                                      hfp_codec_aptx_voice))) {
+  tBTA_AG_SCB p_scb = {
+      .peer_addr = addr,
+      .app_id = 0,
+  };
+
+  ASSERT_TRUE(enable_aptx_voice_property(false));
+
+  bta_ag_at_hfp_cback(&p_scb, BTA_AG_AT_QAC_EVT, 0, (char*)&test_strings[0][0],
+                      (char*)&test_strings[0][12],
+                      BTA_AG_SCO_APTX_SWB_SETTINGS_Q0);
+  ASSERT_FALSE(p_scb.codec_updated);
+  ASSERT_FALSE(p_scb.is_aptx_swb_codec);
+  ASSERT_EQ(1, get_func_call_count("PORT_WriteData"));
+}
+
+TEST_F_WITH_FLAGS(BtaAgCmdTest, at_hfp_cback__qac_ev_codec_enabled,
+                  REQUIRES_FLAGS_ENABLED(ACONFIG_FLAG(TEST_BT,
+                                                      hfp_codec_aptx_voice))) {
+  tBTA_AG_SCB p_scb = {.peer_addr = addr,
+                       .app_id = 0,
+                       .peer_codecs = BTA_AG_SCO_APTX_SWB_SETTINGS_Q0_MASK};
+
+  ASSERT_TRUE(enable_aptx_voice_property(true));
+
+  bta_ag_at_hfp_cback(&p_scb, BTA_AG_AT_QAC_EVT, 0, (char*)&test_strings[0][0],
+                      (char*)&test_strings[0][12],
+                      BTA_AG_SCO_APTX_SWB_SETTINGS_Q0);
+  ASSERT_TRUE(p_scb.codec_updated);
+  ASSERT_TRUE(p_scb.is_aptx_swb_codec);
+  ASSERT_EQ(2, get_func_call_count("PORT_WriteData"));
+  ASSERT_EQ(p_scb.sco_codec, BTA_AG_SCO_APTX_SWB_SETTINGS_Q0);
+  ASSERT_TRUE(enable_aptx_voice_property(false));
+}
+
+TEST_F_WITH_FLAGS(BtaAgCmdTest, at_hfp_cback__qcs_ev_codec_disabled,
+                  REQUIRES_FLAGS_ENABLED(ACONFIG_FLAG(TEST_BT,
+                                                      hfp_codec_aptx_voice))) {
+  tBTA_AG_SCB p_scb = {
+      .peer_addr = addr,
+      .app_id = 0,
+  };
+
+  ASSERT_TRUE(enable_aptx_voice_property(false));
+
+  bta_ag_at_hfp_cback(&p_scb, BTA_AG_AT_QCS_EVT, 0, (char*)&test_strings[0][0],
+                      (char*)&test_strings[0][12],
+                      BTA_AG_SCO_APTX_SWB_SETTINGS_Q0);
+  ASSERT_FALSE(p_scb.codec_updated);
+  ASSERT_FALSE(p_scb.is_aptx_swb_codec);
+  ASSERT_EQ(1, get_func_call_count("PORT_WriteData"));
+}
+
+TEST_F_WITH_FLAGS(BtaAgCmdTest, at_hfp_cback__qcs_ev_codec_q0_enabled,
+                  REQUIRES_FLAGS_ENABLED(ACONFIG_FLAG(TEST_BT,
+                                                      hfp_codec_aptx_voice))) {
+  tBTA_AG_SCB p_scb = {.peer_addr = addr,
+                       .sco_idx = BTM_INVALID_SCO_INDEX,
+                       .app_id = 0,
+                       .sco_codec = BTA_AG_SCO_APTX_SWB_SETTINGS_Q0,
+                       .is_aptx_swb_codec = true};
+
+  ASSERT_TRUE(enable_aptx_voice_property(true));
+
+  bta_ag_cb.sco.state = BTA_AG_SCO_CODEC_ST;
+  bta_ag_api_set_active_device(addr);
+  ASSERT_EQ(addr, bta_ag_get_active_device());
+
+  bta_ag_at_hfp_cback(&p_scb, BTA_AG_AT_QCS_EVT, 0, (char*)&test_strings[0][0],
+                      (char*)&test_strings[0][12],
+                      BTA_AG_SCO_APTX_SWB_SETTINGS_Q0);
+
+  ASSERT_EQ(1, get_func_call_count("alarm_cancel"));
+  ASSERT_EQ(2, get_func_call_count("esco_parameters_for_codec"));
+  ASSERT_EQ(1, get_func_call_count("BTM_SetEScoMode"));
+  ASSERT_EQ(1, get_func_call_count("BTM_CreateSco"));
+  ASSERT_EQ(this->codec, ESCO_CODEC_SWB_Q0);
+}
+
+TEST_F_WITH_FLAGS(BtaAgCmdTest,
+                  handle_swb_at_event__qcs_ev_codec_q1_fallback_to_q0,
+                  REQUIRES_FLAGS_ENABLED(ACONFIG_FLAG(TEST_BT,
+                                                      hfp_codec_aptx_voice))) {
+  tBTA_AG_SCB p_scb = {.peer_addr = addr,
+                       .sco_idx = BTM_INVALID_SCO_INDEX,
+                       .app_id = 0,
+                       .sco_codec = BTA_AG_SCO_APTX_SWB_SETTINGS_Q1,
+                       .codec_fallback = false,
+                       .is_aptx_swb_codec = true};
+
+  ASSERT_TRUE(enable_aptx_voice_property(true));
+
+  bta_ag_cb.sco.state = BTA_AG_SCO_CODEC_ST;
+  bta_ag_api_set_active_device(addr);
+  ASSERT_EQ(addr, bta_ag_get_active_device());
+
+  bta_ag_at_hfp_cback(&p_scb, BTA_AG_AT_QCS_EVT, 0, (char*)&test_strings[0][0],
+                      (char*)&test_strings[0][12],
+                      BTA_AG_SCO_APTX_SWB_SETTINGS_Q1);
+
+  ASSERT_EQ(1, get_func_call_count("alarm_cancel"));
+  ASSERT_EQ(2, get_func_call_count("esco_parameters_for_codec"));
+  ASSERT_EQ(1, get_func_call_count("BTM_SetEScoMode"));
+  ASSERT_EQ(1, get_func_call_count("BTM_CreateSco"));
+  ASSERT_EQ(this->codec, ESCO_CODEC_SWB_Q0);
+  ASSERT_TRUE(enable_aptx_voice_property(false));
+}
diff --git a/system/btif/Android.bp b/system/btif/Android.bp
index fe51a245f1b5c2036860ffc66be5afc2a09f6e19..dd0eec1ccbe7764424bb5db1ca0e498045b01c64 100644
--- a/system/btif/Android.bp
+++ b/system/btif/Android.bp
@@ -237,6 +237,9 @@ cc_library_static {
         "libbt_shim_bridge",
         "libstatslog_bt",
     ],
+    whole_static_libs: [
+        "bluetooth_flags_c_lib",
+    ],
     shared_libs: [
         "libcrypto",
     ],
diff --git a/system/btif/src/btif_hf.cc b/system/btif/src/btif_hf.cc
index d55bb6f15caa96e8257665625f40445ff7921da1..e140e52222501dfbb4ecc284491b6e905759f827 100644
--- a/system/btif/src/btif_hf.cc
+++ b/system/btif/src/btif_hf.cc
@@ -30,6 +30,11 @@
 #include <android_bluetooth_sysprop.h>
 #include <base/functional/callback.h>
 #include <base/logging.h>
+
+#ifdef __ANDROID__
+#include <com_android_bluetooth_flags.h>
+#endif
+
 #include <frameworks/proto_logging/stats/enums/bluetooth/enums.pb.h>
 
 #include <cstdint>
@@ -37,6 +42,7 @@
 
 #include "bta/include/bta_ag_api.h"
 #include "bta/include/utl.h"
+#include "bta_ag_swb_aptx.h"
 #include "btif/include/btif_common.h"
 #include "btif/include/btif_metrics_logging.h"
 #include "btif/include/btif_profile_queue.h"
@@ -573,23 +579,43 @@ static void btif_hf_upstreams_evt(uint16_t event, char* p_param) {
       if (p_data->val.num == BTM_SCO_CODEC_CVSD) {
         bt_hf_callbacks->WbsCallback(BTHF_WBS_NO,
                                      &btif_hf_cb[idx].connected_bda);
-        bt_hf_callbacks->SwbCallback(BTHF_SWB_NO,
+        bt_hf_callbacks->SwbCallback(BTHF_SWB_CODEC_LC3, BTHF_SWB_NO,
                                      &btif_hf_cb[idx].connected_bda);
       } else if (p_data->val.num == BTM_SCO_CODEC_MSBC) {
         bt_hf_callbacks->WbsCallback(BTHF_WBS_YES,
                                      &btif_hf_cb[idx].connected_bda);
-        bt_hf_callbacks->SwbCallback(BTHF_SWB_NO,
+        bt_hf_callbacks->SwbCallback(BTHF_SWB_CODEC_LC3, BTHF_SWB_NO,
                                      &btif_hf_cb[idx].connected_bda);
       } else if (p_data->val.num == BTM_SCO_CODEC_LC3) {
         bt_hf_callbacks->WbsCallback(BTHF_WBS_NO,
                                      &btif_hf_cb[idx].connected_bda);
-        bt_hf_callbacks->SwbCallback(BTHF_SWB_YES,
+        bt_hf_callbacks->SwbCallback(BTHF_SWB_CODEC_LC3, BTHF_SWB_YES,
                                      &btif_hf_cb[idx].connected_bda);
       } else {
         bt_hf_callbacks->WbsCallback(BTHF_WBS_NONE,
                                      &btif_hf_cb[idx].connected_bda);
-        bt_hf_callbacks->SwbCallback(BTHF_SWB_NONE,
-                                     &btif_hf_cb[idx].connected_bda);
+#ifdef __ANDROID__
+        if (com::android::bluetooth::flags::hfp_codec_aptx_voice()) {
+          LOG_VERBOSE(
+              "AG final selected SWB codec is 0x%02x 0=Q0 4=Q1 6=Q2 7=Q3",
+              p_data->val.num);
+          bthf_swb_config_t config;
+          if (p_data->val.num == BTA_AG_SCO_APTX_SWB_SETTINGS_Q0 ||
+              p_data->val.num == BTA_AG_SCO_APTX_SWB_SETTINGS_Q1 ||
+              p_data->val.num == BTA_AG_SCO_APTX_SWB_SETTINGS_Q2 ||
+              p_data->val.num == BTA_AG_SCO_APTX_SWB_SETTINGS_Q3) {
+            config = BTHF_SWB_YES;
+          } else {
+            config = BTHF_SWB_NO;
+          }
+          bt_hf_callbacks->SwbCallback(BTHF_SWB_CODEC_VENDOR_APTX, config,
+                                       &btif_hf_cb[idx].connected_bda);
+        } else
+#endif  // call SwbCallback with LC3 on non android target
+        {
+          bt_hf_callbacks->SwbCallback(BTHF_SWB_CODEC_LC3, BTHF_SWB_NONE,
+                                       &btif_hf_cb[idx].connected_bda);
+        }
       }
       break;
 
@@ -642,14 +668,15 @@ static void btif_hf_upstreams_evt(uint16_t event, char* p_param) {
       break;
 
     case BTA_AG_AT_BCS_EVT:
-      LOG_VERBOSE("%s: AG final selected codec is 0x%02x 1=CVSD 2=MSBC",
-                  __func__, p_data->val.num);
+      LOG_VERBOSE("AG final selected codec is 0x%02x 1=CVSD 2=MSBC",
+                  p_data->val.num);
       /* No BTHF_WBS_NONE case, because HF1.6 supported device can send BCS */
       /* Only CVSD is considered narrow band speech */
       bt_hf_callbacks->WbsCallback(
           (p_data->val.num == BTM_SCO_CODEC_MSBC) ? BTHF_WBS_YES : BTHF_WBS_NO,
           &btif_hf_cb[idx].connected_bda);
       bt_hf_callbacks->SwbCallback(
+          BTHF_SWB_CODEC_LC3,
           (p_data->val.num == BTM_SCO_CODEC_LC3) ? BTHF_SWB_YES : BTHF_SWB_NO,
           &btif_hf_cb[idx].connected_bda);
       break;
@@ -679,6 +706,22 @@ static void btif_hf_upstreams_evt(uint16_t event, char* p_param) {
                                        &btif_hf_cb[idx].connected_bda);
       }
       break;
+
+#ifdef __ANDROID__
+    case BTA_AG_AT_QCS_EVT:
+      if (com::android::bluetooth::flags::hfp_codec_aptx_voice()) {
+        LOG_INFO("AG final selected SWB codec is 0x%02x 0=Q0 4=Q1 6=Q2 7=Q3",
+                 p_data->val.num);
+        bt_hf_callbacks->SwbCallback(
+            BTHF_SWB_CODEC_VENDOR_APTX,
+            p_data->val.num <= BTA_AG_SCO_APTX_SWB_SETTINGS_Q3 ? BTHF_SWB_YES
+                                                               : BTHF_SWB_NO,
+            &btif_hf_cb[idx].connected_bda);
+        break;
+      }
+      FALLTHROUGH_INTENDED;
+#endif
+
     default:
       LOG(WARNING) << __func__ << ": unhandled event " << event;
       break;
diff --git a/system/device/include/esco_parameters.h b/system/device/include/esco_parameters.h
index abb5afd238f4b9005abaced99cf1b28bb7676d76..5b15ca2ff2ad6009779ea8dfc0fe3f103c34bac4 100644
--- a/system/device/include/esco_parameters.h
+++ b/system/device/include/esco_parameters.h
@@ -38,10 +38,15 @@ typedef enum {
   ESCO_CODEC_MSBC_T2,
   ESCO_CODEC_LC3_T1,
   ESCO_CODEC_LC3_T2,
+  ESCO_CODEC_SWB_Q0,
+  ESCO_CODEC_SWB_Q1,
+  ESCO_CODEC_SWB_Q2,
+  ESCO_CODEC_SWB_Q3,
   ESCO_CODEC_UNKNOWN,  // For testing
 } esco_codec_t;
 
-#define ESCO_NUM_CODECS 7
+#define ESCO_LEGACY_NUM_CODECS 7
+#define ESCO_NUM_CODECS 11
 
 // Coding Formats (BT 4.1 or later Assigned numbers)
 #define ESCO_CODING_FORMAT_ULAW ((uint8_t)0x00)     /* u-Law log    */
diff --git a/system/device/src/esco_parameters.cc b/system/device/src/esco_parameters.cc
index 8f241dbce5c3308d2cf3ea8cc54d504364dab211..684450a80bc3968e74b8919c7ad0b6213338ce52 100644
--- a/system/device/src/esco_parameters.cc
+++ b/system/device/src/esco_parameters.cc
@@ -292,7 +292,38 @@ static const enh_esco_params_t default_esco_parameters[ESCO_NUM_CODECS] = {
         .retransmission_effort = ESCO_RETRANSMISSION_QUALITY,
         .coding_format = ESCO_CODING_FORMAT_LC3,
     },
-};
+    // aptX Voice SWB
+    {.transmit_bandwidth = TXRX_64KBITS_RATE,
+     .receive_bandwidth = TXRX_64KBITS_RATE,
+     .transmit_coding_format = {.coding_format = ESCO_CODING_FORMAT_VS,
+                                .company_id = 0x000A,
+                                .vendor_specific_codec_id = 0x0000},
+     .receive_coding_format = {.coding_format = ESCO_CODING_FORMAT_VS,
+                               .company_id = 0x000A,
+                               .vendor_specific_codec_id = 0x0000},
+     .transmit_codec_frame_size = 60,
+     .receive_codec_frame_size = 60,
+     .input_bandwidth = INPUT_OUTPUT_128K_RATE,
+     .output_bandwidth = INPUT_OUTPUT_128K_RATE,
+     .input_coding_format = {.coding_format = ESCO_CODING_FORMAT_LINEAR,
+                             .company_id = 0x0000,
+                             .vendor_specific_codec_id = 0x0000},
+     .output_coding_format = {.coding_format = ESCO_CODING_FORMAT_LINEAR,
+                              .company_id = 0x0000,
+                              .vendor_specific_codec_id = 0x0000},
+     .input_coded_data_size = 16,
+     .output_coded_data_size = 16,
+     .input_pcm_data_format = ESCO_PCM_DATA_FORMAT_2_COMP,
+     .output_pcm_data_format = ESCO_PCM_DATA_FORMAT_2_COMP,
+     .input_pcm_payload_msb_position = 0,
+     .output_pcm_payload_msb_position = 0,
+     .input_data_path = ESCO_DATA_PATH_PCM,
+     .output_data_path = ESCO_DATA_PATH_PCM,
+     .input_transport_unit_size = 0x00,
+     .output_transport_unit_size = 0x00,
+     .max_latency_ms = 14,
+     .packet_types = 0x0380,
+     .retransmission_effort = ESCO_RETRANSMISSION_QUALITY}};
 
 enh_esco_params_t esco_parameters_for_codec(esco_codec_t codec, bool offload) {
   CHECK(codec >= 0) << "codec index " << (int)codec << "< 0";
@@ -322,9 +353,15 @@ enh_esco_params_t esco_parameters_for_codec(esco_codec_t codec, bool offload) {
   }
 
   if (offload) {
+    if (codec == ESCO_CODEC_SWB_Q0 || codec == ESCO_CODEC_SWB_Q1 ||
+        codec == ESCO_CODEC_SWB_Q2 || codec == ESCO_CODEC_SWB_Q3) {
+      return default_esco_parameters[ESCO_CODEC_SWB_Q0];
+    }
     return default_esco_parameters[codec];
   }
 
+  CHECK(codec < ESCO_LEGACY_NUM_CODECS)
+      << "legacy codec index " << (int)codec << " > " << ESCO_LEGACY_NUM_CODECS;
   enh_esco_params_t param = default_esco_parameters[codec];
   param.input_data_path = param.output_data_path = ESCO_DATA_PATH_HCI;
 
diff --git a/system/gd/rust/topshim/hfp/hfp_shim.cc b/system/gd/rust/topshim/hfp/hfp_shim.cc
index 11d124601abcf025da4017353e7b3dc4ef61fcac..9c02a9fd7c63ea97b4ed385e556e195220cc6714 100644
--- a/system/gd/rust/topshim/hfp/hfp_shim.cc
+++ b/system/gd/rust/topshim/hfp/hfp_shim.cc
@@ -195,9 +195,11 @@ class DBusHeadsetCallbacks : public headset::Callbacks {
     rusty::hfp_wbs_caps_update_callback(wbs == headset::BTHF_WBS_YES, *addr);
   }
 
-  void SwbCallback(headset::bthf_swb_config_t swb, RawAddress* addr) override {
-    LOG_INFO("SwbCallback %d from %s", swb, ADDRESS_TO_LOGGABLE_CSTR(*addr));
-    rusty::hfp_swb_caps_update_callback(swb == headset::BTHF_SWB_YES, *addr);
+  void SwbCallback(
+      headset::bthf_swb_codec_t codec, headset::bthf_swb_config_t swb, RawAddress* addr) override {
+    LOG_INFO("SwbCallback codec:%d, swb:%d from %s", codec, swb, ADDRESS_TO_LOGGABLE_CSTR(*addr));
+    rusty::hfp_swb_caps_update_callback(
+        (codec == headset::BTHF_SWB_CODEC_LC3 && swb == headset::BTHF_SWB_YES), *addr);
   }
 
   void AtChldCallback(headset::bthf_chld_type_t chld, RawAddress* bd_addr) override {
diff --git a/system/include/hardware/bluetooth_headset_callbacks.h b/system/include/hardware/bluetooth_headset_callbacks.h
index 2da5c1df93ae22c75118fdfaedff2fe4efc2cd0f..36ea13431d726a2e536d2ea788a2c719abfbb42e 100644
--- a/system/include/hardware/bluetooth_headset_callbacks.h
+++ b/system/include/hardware/bluetooth_headset_callbacks.h
@@ -117,10 +117,12 @@ class Callbacks {
   /**
    * Callback for AT+BCS and event from BAC
    *
+   * @param codec SWB codec
    * @param swb SWB enable, SWB disable
    * @param bd_addr remote device address
    */
-  virtual void SwbCallback(bthf_swb_config_t swb, RawAddress* bd_addr) = 0;
+  virtual void SwbCallback(bthf_swb_codec_t codec, bthf_swb_config_t swb,
+                           RawAddress* bd_addr) = 0;
 
   /**
    * Callback for call hold handling (AT+CHLD)
diff --git a/system/include/hardware/bt_hf.h b/system/include/hardware/bt_hf.h
index 9a72707d0b5ecfbb932706b29df08089ec4eea09..671e564084229ceb17179216b5e34625819fb8a1 100644
--- a/system/include/hardware/bt_hf.h
+++ b/system/include/hardware/bt_hf.h
@@ -56,8 +56,18 @@ typedef enum { BTHF_NREC_STOP, BTHF_NREC_START } bthf_nrec_t;
 /* WBS codec setting */
 typedef enum { BTHF_WBS_NONE, BTHF_WBS_NO, BTHF_WBS_YES } bthf_wbs_config_t;
 
+/* SWB codec */
+typedef enum {
+  BTHF_SWB_CODEC_LC3 = 0,
+  BTHF_SWB_CODEC_VENDOR_APTX
+} bthf_swb_codec_t;
+
 /* SWB codec setting */
-typedef enum { BTHF_SWB_NONE, BTHF_SWB_NO, BTHF_SWB_YES } bthf_swb_config_t;
+typedef enum {
+  BTHF_SWB_NONE,
+  BTHF_SWB_NO,
+  BTHF_SWB_YES,
+} bthf_swb_config_t;
 
 /* CHLD - Call held handling */
 typedef enum {
diff --git a/system/stack/include/btm_api_types.h b/system/stack/include/btm_api_types.h
index 233ee2803a4d49c01fa4e91842e0a414c23d75ab..d32b6622486b63e74159d0a4c188be9e2b93bdd0 100644
--- a/system/stack/include/btm_api_types.h
+++ b/system/stack/include/btm_api_types.h
@@ -157,6 +157,10 @@ typedef uint8_t tBTM_SCO_TYPE;
 #define BTM_SCO_CODEC_CVSD 0x0001
 #define BTM_SCO_CODEC_MSBC 0x0002
 #define BTM_SCO_CODEC_LC3 0x0004
+#define BTA_AG_SCO_APTX_SWB_SETTINGS_Q0_MASK 0x0008
+#define BTA_AG_SCO_APTX_SWB_SETTINGS_Q1_MASK 0x0016
+#define BTA_AG_SCO_APTX_SWB_SETTINGS_Q2_MASK 0x0032
+#define BTA_AG_SCO_APTX_SWB_SETTINGS_Q3_MASK 0x0064
 typedef uint16_t tBTM_SCO_CODEC_TYPE;
 
 /*******************