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/system/bta/Android.bp b/system/bta/Android.bp
index d415a4988a0a2449ed168b1f2aab8dbd55c5d205..91b390c1ae9cc0cb4b4a4f26c61692ec5ac30574 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",
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/btif/Android.bp b/system/btif/Android.bp
index 28d5720169962f6084484d2096fad3ef338d3c07..9eaa4bf85aba3af0c79a6cb25fe9a4e71e9d6556 100644
--- a/system/btif/Android.bp
+++ b/system/btif/Android.bp
@@ -236,6 +236,9 @@ cc_library_static {
         "libbt_shim_bridge",
         "libstatslog_bt",
     ],
+    whole_static_libs: [
+        "bluetooth_flags_c_lib",
+    ],
     shared_libs: [
         "libcrypto",
     ],
@@ -616,6 +619,7 @@ cc_test {
         "libhidlbase",
         "liblog",
         "libutils",
+        "server_configurable_flags",
     ],
     static_libs: [
         "android.hardware.bluetooth.a2dp@1.0",
@@ -756,6 +760,7 @@ cc_test {
         "libhidlbase",
         "liblog",
         "libutils",
+        "server_configurable_flags",
     ],
     static_libs: [
         "android.hardware.bluetooth.a2dp@1.0",
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 861dfd90ccd88308af4f969bcb3a84223eca6e7a..7a786047888b1caef597de1174709210aca0aedd 100644
--- a/system/device/src/esco_parameters.cc
+++ b/system/device/src/esco_parameters.cc
@@ -289,16 +289,53 @@ 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";
   CHECK(codec < ESCO_NUM_CODECS)
       << "codec index " << (int)codec << " > " << ESCO_NUM_CODECS;
   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;
 
 /*******************