Merge "BT: Set AutoConnection for TWS+ pair devices" into q-keystone-qcom-dev
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index ff6ab4f..bc10b4b 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -76,9 +76,6 @@
     <uses-permission android:name="android.permission.INTERNET" />
     <uses-permission android:name="android.permission.OVERRIDE_WIFI_CONFIG" />
 
-    <!-- Allows access to read media audio -->
-    <uses-permission android:name="android.permission.READ_MEDIA_AUDIO"/>
-
     <!-- Allows application to write to internal media storage -->
     <uses-permission android:name="android.permission.WRITE_MEDIA_STORAGE" />
 
diff --git a/src/com/android/bluetooth/a2dp/A2dpCodecConfig.java b/src/com/android/bluetooth/a2dp/A2dpCodecConfig.java
index a104b89..f6d42ea 100644
--- a/src/com/android/bluetooth/a2dp/A2dpCodecConfig.java
+++ b/src/com/android/bluetooth/a2dp/A2dpCodecConfig.java
@@ -60,7 +60,7 @@
 
     void setCodecConfigPreference(BluetoothDevice device,
                                   BluetoothCodecStatus codecStatus,
-                                  BluetoothCodecConfig codecConfig) {
+                                  BluetoothCodecConfig newCodecConfig) {
         Objects.requireNonNull(codecStatus);
 
         // Check whether the codecConfig is selectable for this Bluetooth device.
@@ -69,34 +69,36 @@
                 codec.isMandatoryCodec())) {
             // Do not set codec preference to native if the selectableCodecs not contain mandatory
             // codec. The reason could be remote codec negotiation is not completed yet.
-            Log.w(TAG, "Cannot find mandatory codec in selectableCodecs.");
+            Log.w(TAG, "setCodecConfigPreference: must have mandatory codec before changing.");
             return;
         }
-        if (!isCodecConfigSelectable(codecConfig, selectableCodecs)) {
-            Log.w(TAG, "Codec is not selectable: " + codecConfig);
+        if (!codecStatus.isCodecConfigSelectable(newCodecConfig)) {
+            Log.w(TAG, "setCodecConfigPreference: invalid codec "
+                    + Objects.toString(newCodecConfig));
             return;
         }
 
         // Check whether the codecConfig would change current codec config.
-        int prioritizedCodecType = getPrioitizedCodecType(codecConfig, selectableCodecs);
+        int prioritizedCodecType = getPrioitizedCodecType(newCodecConfig, selectableCodecs);
         BluetoothCodecConfig currentCodecConfig = codecStatus.getCodecConfig();
         if (prioritizedCodecType == currentCodecConfig.getCodecType()
-                && (currentCodecConfig.getCodecType() != codecConfig.getCodecType()
-                || currentCodecConfig.sameAudioFeedingParameters(codecConfig))) {
+                && (prioritizedCodecType != newCodecConfig.getCodecType()
+                || (currentCodecConfig.similarCodecFeedingParameters(newCodecConfig)
+                && currentCodecConfig.sameCodecSpecificParameters(newCodecConfig)))) {
             // Same codec with same parameters, no need to send this request to native.
-            Log.i(TAG, "setCodecConfigPreference: codec not changed.");
+            Log.w(TAG, "setCodecConfigPreference: codec not changed.");
             return;
         }
 
         BluetoothCodecConfig[] codecConfigArray = new BluetoothCodecConfig[1];
-        codecConfigArray[0] = codecConfig;
+        codecConfigArray[0] = newCodecConfig;
         mA2dpNativeInterface.setCodecConfigPreference(device, codecConfigArray);
     }
 
     void enableOptionalCodecs(BluetoothDevice device, BluetoothCodecConfig currentCodecConfig) {
         if (currentCodecConfig != null && !currentCodecConfig.isMandatoryCodec()) {
-            Log.i(TAG, "enableOptionalCodecs: already using optional codec: "
-                    + currentCodecConfig.getCodecType());
+            Log.i(TAG, "enableOptionalCodecs: already using optional codec "
+                    + currentCodecConfig.getCodecName());
             return;
         }
 
@@ -118,7 +120,7 @@
 
     void disableOptionalCodecs(BluetoothDevice device, BluetoothCodecConfig currentCodecConfig) {
         if (currentCodecConfig != null && currentCodecConfig.isMandatoryCodec()) {
-            Log.i(TAG, "disableOptionalCodecs: already using mandatory codec");
+            Log.i(TAG, "disableOptionalCodecs: already using mandatory codec.");
             return;
         }
 
@@ -153,20 +155,6 @@
         return prioritizedCodecConfig.getCodecType();
     }
 
-    // Check whether the codecConfig is selectable
-    private static boolean isCodecConfigSelectable(BluetoothCodecConfig codecConfig,
-            BluetoothCodecConfig[] selectableCodecs) {
-        for (BluetoothCodecConfig config : selectableCodecs) {
-            if (codecConfig.getCodecType() == config.getCodecType()
-                    && (codecConfig.getSampleRate() & config.getSampleRate()) != 0
-                    && (codecConfig.getBitsPerSample() & config.getBitsPerSample()) != 0
-                    && (codecConfig.getChannelMode() & config.getChannelMode()) != 0) {
-                return true;
-            }
-        }
-        return false;
-    }
-
     // Assign the A2DP Source codec config priorities
     private BluetoothCodecConfig[] assignCodecConfigPriorities() {
         Resources resources = mContext.getResources();
diff --git a/src/com/android/bluetooth/a2dp/A2dpService.java b/src/com/android/bluetooth/a2dp/A2dpService.java
index b11ae58..1cc276f 100644
--- a/src/com/android/bluetooth/a2dp/A2dpService.java
+++ b/src/com/android/bluetooth/a2dp/A2dpService.java
@@ -75,6 +75,7 @@
     private final Object mBtTwsLock = new Object();
     private final Object mBtAvrcpLock = new Object();
     private final Object mActiveDeviceLock = new Object();
+    private final Object mVariableLock = new Object();
 
     @VisibleForTesting
     A2dpNativeInterface mA2dpNativeInterface;
@@ -129,12 +130,18 @@
            switch (msg.what) {
                case SET_EBMONO_CFG:
                    Log.d(TAG, "setparameters to Mono");
-                   mAudioManager.setParameters("TwsChannelConfig=mono");
+                   synchronized (mVariableLock) {
+                        if(mAudioManager != null)
+                           mAudioManager.setParameters("TwsChannelConfig=mono");
+                   }
                    mTwsPlusChannelMode = "mono";
                    break;
                case SET_EBDUALMONO_CFG:
                    Log.d(TAG, "setparameters to Dual-Mono");
-                   mAudioManager.setParameters("TwsChannelConfig=dual-mono");
+                   synchronized (mVariableLock) {
+                       if(mAudioManager != null)
+                           mAudioManager.setParameters("TwsChannelConfig=dual-mono");
+                   }
                    mTwsPlusChannelMode = "dual-mono";
                    break;
               default:
@@ -164,73 +171,74 @@
 
         // Step 1: Get AdapterService, A2dpNativeInterface, AudioManager.
         // None of them can be null.
-        mAdapterService = Objects.requireNonNull(AdapterService.getAdapterService(),
+        synchronized (mVariableLock) {
+            mAdapterService = Objects.requireNonNull(AdapterService.getAdapterService(),
                 "AdapterService cannot be null when A2dpService starts");
-        mA2dpNativeInterface = Objects.requireNonNull(A2dpNativeInterface.getInstance(),
+            mA2dpNativeInterface = Objects.requireNonNull(A2dpNativeInterface.getInstance(),
                 "A2dpNativeInterface cannot be null when A2dpService starts");
-        mAudioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
-        Objects.requireNonNull(mAudioManager,
+            mAudioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
+            Objects.requireNonNull(mAudioManager,
                                "AudioManager cannot be null when A2dpService starts");
 
-        // Step 2: Get maximum number of connected audio devices
-        mMaxConnectedAudioDevices = mAdapterService.getMaxConnectedAudioDevices();
-        mSetMaxConnectedAudioDevices = mMaxConnectedAudioDevices;
-        Log.i(TAG, "Max connected audio devices set to " + mMaxConnectedAudioDevices);
-        if (mAdapterService.isVendorIntfEnabled()) {
-            String twsPlusEnabled = SystemProperties.get("persist.vendor.btstack.enable.twsplus");
-            if (!twsPlusEnabled.isEmpty() && "true".equals(twsPlusEnabled)) {
-                mIsTwsPlusEnabled = true;
-            }
-            Log.i(TAG, "mMaxConnectedAudioDevices: " + mMaxConnectedAudioDevices);
-            String twsShoEnabled = SystemProperties.get("persist.vendor.btstack.enable.twsplussho");
-            if (!twsShoEnabled.isEmpty() && "true".equals(twsShoEnabled) &&
-                (mIsTwsPlusEnabled == true) && mMaxConnectedAudioDevices <= 2) {
-                mMaxConnectedAudioDevices = 3;
-                Log.i(TAG, "TWS+ SHO enabled mMaxConnectedAudioDevices changed to: " + mMaxConnectedAudioDevices);
-            } else if (mIsTwsPlusEnabled && mMaxConnectedAudioDevices < 2) {
-                mMaxConnectedAudioDevices = 2;
-                Log.i(TAG, "TWS+ enabled mMaxConnectedAudioDevices changed to: " + mMaxConnectedAudioDevices);
-            }
+            // Step 2: Get maximum number of connected audio devices
+            mMaxConnectedAudioDevices = mAdapterService.getMaxConnectedAudioDevices();
             mSetMaxConnectedAudioDevices = mMaxConnectedAudioDevices;
-            String twsPlusMonoEnabled = SystemProperties.get("persist.vendor.btstack.twsplus.monosupport");
-            if (!twsPlusMonoEnabled.isEmpty() && "true".equals(twsPlusMonoEnabled)) {
-                mIsTwsPlusMonoSupported = true;
+            Log.i(TAG, "Max connected audio devices set to " + mMaxConnectedAudioDevices);
+            if (mAdapterService != null && mAdapterService.isVendorIntfEnabled()) {
+                String twsPlusEnabled = SystemProperties.get("persist.vendor.btstack.enable.twsplus");
+                if (!twsPlusEnabled.isEmpty() && "true".equals(twsPlusEnabled)) {
+                    mIsTwsPlusEnabled = true;
+                }
+                Log.i(TAG, "mMaxConnectedAudioDevices: " + mMaxConnectedAudioDevices);
+                String twsShoEnabled = SystemProperties.get("persist.vendor.btstack.enable.twsplussho");
+                if (!twsShoEnabled.isEmpty() && "true".equals(twsShoEnabled) &&
+                    (mIsTwsPlusEnabled == true) && mMaxConnectedAudioDevices <= 2) {
+                    mMaxConnectedAudioDevices = 3;
+                    Log.i(TAG, "TWS+ SHO enabled mMaxConnectedAudioDevices changed to: " + mMaxConnectedAudioDevices);
+                } else if (mIsTwsPlusEnabled && mMaxConnectedAudioDevices < 2) {
+                    mMaxConnectedAudioDevices = 2;
+                    Log.i(TAG, "TWS+ enabled mMaxConnectedAudioDevices changed to: " + mMaxConnectedAudioDevices);
+                }
+                mSetMaxConnectedAudioDevices = mMaxConnectedAudioDevices;
+                String twsPlusMonoEnabled = SystemProperties.get("persist.vendor.btstack.twsplus.monosupport");
+                if (!twsPlusMonoEnabled.isEmpty() && "true".equals(twsPlusMonoEnabled)) {
+                    mIsTwsPlusMonoSupported = true;
+                }
+                String TwsPlusChannelMode = SystemProperties.get("persist.vendor.btstack.twsplus.defaultchannelmode");
+                if (!TwsPlusChannelMode.isEmpty() && "mono".equals(TwsPlusChannelMode)) {
+                    mTwsPlusChannelMode = "mono";
+                }
+                Log.d(TAG, "Default TwsPlus ChannelMode: " + mTwsPlusChannelMode);
             }
-            String TwsPlusChannelMode = SystemProperties.get("persist.vendor.btstack.twsplus.defaultchannelmode");
-            if (!TwsPlusChannelMode.isEmpty() && "mono".equals(TwsPlusChannelMode)) {
-                mTwsPlusChannelMode = "mono";
-            }
-            Log.d(TAG, "Default TwsPlus ChannelMode: " + mTwsPlusChannelMode);
-        }
 
-        // Step 3: Setup AVRCP
-        if(mAdapterService.isVendorIntfEnabled())
-            mAvrcp_ext = Avrcp_ext.make(this, this, mMaxConnectedAudioDevices);
-        else
-            mAvrcp = Avrcp.make(this);
+            // Step 3: Setup AVRCP
+            if(mAdapterService != null && mAdapterService.isVendorIntfEnabled())
+                mAvrcp_ext = Avrcp_ext.make(this, this, mMaxConnectedAudioDevices);
+            else
+                mAvrcp = Avrcp.make(this);
 
-        // Step 4: Start handler thread for state machines
-        mStateMachines.clear();
-        mStateMachinesThread = new HandlerThread("A2dpService.StateMachines");
-        mStateMachinesThread.start();
+            // Step 4: Start handler thread for state machines
+            mStateMachines.clear();
+            mStateMachinesThread = new HandlerThread("A2dpService.StateMachines");
+            mStateMachinesThread.start();
 
-        // Step 5: Setup codec config
-        mA2dpCodecConfig = new A2dpCodecConfig(this, mA2dpNativeInterface);
+            // Step 5: Setup codec config
+            mA2dpCodecConfig = new A2dpCodecConfig(this, mA2dpNativeInterface);
 
-        // Step 6: Initialize native interface
-        List<BluetoothCodecConfig> mCodecConfigOffload;
-        mCodecConfigOffload = mAudioManager.getHwOffloadEncodingFormatsSupportedForA2DP();
-        BluetoothCodecConfig[] OffloadCodecConfig  = new BluetoothCodecConfig[mCodecConfigOffload.size()];
-        OffloadCodecConfig  = mCodecConfigOffload.toArray(OffloadCodecConfig);
-        mA2dpNativeInterface.init(mMaxConnectedAudioDevices,
+            // Step 6: Initialize native interface
+            List<BluetoothCodecConfig> mCodecConfigOffload;
+            mCodecConfigOffload = mAudioManager.getHwOffloadEncodingFormatsSupportedForA2DP();
+            BluetoothCodecConfig[] OffloadCodecConfig  = new BluetoothCodecConfig[mCodecConfigOffload.size()];
+            OffloadCodecConfig  = mCodecConfigOffload.toArray(OffloadCodecConfig);
+             mA2dpNativeInterface.init(mMaxConnectedAudioDevices,
                            mA2dpCodecConfig.codecConfigPriorities(),OffloadCodecConfig);
 
-        // Step 7: Check if A2DP is in offload mode
-        mA2dpOffloadEnabled = mAdapterService.isA2dpOffloadEnabled();
-        if (DBG) {
-            Log.d(TAG, "A2DP offload flag set to " + mA2dpOffloadEnabled);
-        }
-
+            // Step 7: Check if A2DP is in offload mode
+            mA2dpOffloadEnabled = mAdapterService.isA2dpOffloadEnabled();
+            if (DBG) {
+                Log.d(TAG, "A2DP offload flag set to " + mA2dpOffloadEnabled);
+            }
+         }
         // Step 8: Setup broadcast receivers
         IntentFilter filter = new IntentFilter();
         filter.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED);
@@ -260,7 +268,6 @@
 
         // Step 9: Clear active device and stop playing audio
         removeActiveDevice(true);
-
         // Step 8: Mark service as stopped
         setA2dpService(null);
 
@@ -269,11 +276,11 @@
         mConnectionStateChangedReceiver = null;
         unregisterReceiver(mBondStateChangedReceiver);
         mBondStateChangedReceiver = null;
-
         // Step 6: Cleanup native interface
-        mA2dpNativeInterface.cleanup();
-        mA2dpNativeInterface = null;
-
+        synchronized (mVariableLock) {
+            if (mA2dpNativeInterface != null)
+                mA2dpNativeInterface.cleanup();
+        }
         // Step 5: Clear codec config
         mA2dpCodecConfig = null;
 
@@ -303,21 +310,22 @@
         }
 
         // Step 2: Reset maximum number of connected audio devices
-        if (mAdapterService.isVendorIntfEnabled()) {
-            if (mIsTwsPlusEnabled) {
-                mMaxConnectedAudioDevices = 2;
+        synchronized (mVariableLock) {
+            if (mAdapterService != null && mAdapterService.isVendorIntfEnabled()) {
+                if (mIsTwsPlusEnabled) {
+                    mMaxConnectedAudioDevices = 2;
+                } else {
+                    mMaxConnectedAudioDevices = 1;
+                }
             } else {
-               mMaxConnectedAudioDevices = 1;
-            }
-        } else {
-            mMaxConnectedAudioDevices = 1;
+                mMaxConnectedAudioDevices = 1;
+            } 
+            mSetMaxConnectedAudioDevices = 1;
+            // Step 1: Clear AdapterService, A2dpNativeInterface, AudioManager
+            mAudioManager = null;
+            mA2dpNativeInterface = null;
+            mAdapterService = null;
         }
-        mSetMaxConnectedAudioDevices = 1;
-
-        // Step 1: Clear AdapterService, A2dpNativeInterface, AudioManager
-        mAudioManager = null;
-        mA2dpNativeInterface = null;
-        mAdapterService = null;
 
         return true;
     }
@@ -356,10 +364,14 @@
             Log.e(TAG, "Cannot connect to " + device + " : PRIORITY_OFF");
             return false;
         }
-        if (!BluetoothUuid.isUuidPresent(mAdapterService.getRemoteUuids(device),
-                                         BluetoothUuid.AudioSink)) {
-            Log.e(TAG, "Cannot connect to " + device + " : Remote does not have A2DP Sink UUID");
-            return false;
+        synchronized (mVariableLock) {
+            if (mAdapterService == null)
+                return false;
+            if (!BluetoothUuid.isUuidPresent(mAdapterService.getRemoteUuids(device),
+                                             BluetoothUuid.AudioSink)) {
+                Log.e(TAG, "Cannot connect to " + device + " : Remote does not have A2DP Sink UUID");
+                return false;
+            }
         }
 
         synchronized (mBtA2dpLock) {
@@ -503,15 +515,17 @@
         int tws_device = 0;
         // Count devices that are in the process of connecting or already connected
         synchronized (mBtA2dpLock) {
-            for (A2dpStateMachine sm : mStateMachines.values()) {
+             for (A2dpStateMachine sm : mStateMachines.values()) {
                 switch (sm.getConnectionState()) {
                     case BluetoothProfile.STATE_CONNECTING:
                     case BluetoothProfile.STATE_CONNECTED:
                         if (Objects.equals(device, sm.getDevice())) {
                             return true;    // Already connected or accounted for
                         }
-                        if (mAdapterService.isTwsPlusDevice(sm.getDevice()))
-                            tws_device++;
+                        synchronized (mVariableLock) {
+                            if (mAdapterService != null && mAdapterService.isTwsPlusDevice(sm.getDevice()))
+                                tws_device++;
+                        }
                         connected++;
                         break;
                     default:
@@ -521,11 +535,13 @@
         }
         Log.d(TAG,"connectionAllowedCheckMaxDevices connected = " + connected +
               "tws connected = " + tws_device);
-        if (mAdapterService.isVendorIntfEnabled() &&
-            ((tws_device > 0) || mAdapterService.isTwsPlusDevice(device) ||
-            ((tws_device > 0) && connected == mMaxConnectedAudioDevices &&
-            !mAdapterService.isTwsPlusDevice(device)))) {
-            return isConnectionAllowed(device, tws_device, connected);
+        synchronized (mVariableLock) {
+            if (mAdapterService != null &&  mAdapterService.isVendorIntfEnabled() &&
+                ((tws_device > 0) || mAdapterService.isTwsPlusDevice(device) ||
+                ((tws_device > 0) && connected == mMaxConnectedAudioDevices &&
+                !mAdapterService.isTwsPlusDevice(device)))) {
+                return isConnectionAllowed(device, tws_device, connected);
+            }
         }
         if (mSetMaxConnectedAudioDevices == 1 &&
             connected == mSetMaxConnectedAudioDevices) {
@@ -548,9 +564,13 @@
     public boolean okToConnect(BluetoothDevice device, boolean isOutgoingRequest) {
         Log.i(TAG, "okToConnect: device " + device + " isOutgoingRequest: " + isOutgoingRequest);
         // Check if this is an incoming connection in Quiet mode.
-        if (mAdapterService.isQuietModeEnabled() && !isOutgoingRequest) {
-            Log.e(TAG, "okToConnect: cannot connect to " + device + " : quiet mode enabled");
-            return false;
+        synchronized (mVariableLock) {
+            if (mAdapterService == null)
+                return false;
+            if (mAdapterService.isQuietModeEnabled() && !isOutgoingRequest) {
+                Log.e(TAG, "okToConnect: cannot connect to " + device + " : quiet mode enabled");
+                return false;
+            }
         }
         // Check if too many devices
         if (!connectionAllowedCheckMaxDevices(device)) {
@@ -562,7 +582,11 @@
         // Check priority and accept or reject the connection.
         // Note: Logic can be simplified, but keeping it this way for readability
         int priority = getPriority(device);
-        int bondState = mAdapterService.getBondState(device);
+        int bondState = BluetoothDevice.BOND_NONE;
+        synchronized (mVariableLock) {
+            if (mAdapterService != null)
+                bondState = mAdapterService.getBondState(device);
+        }
         // If priority is undefined, it is likely that service discovery has not completed and peer
         // initiated the connection. Allow this connection only if the device is bonded or bonding
         boolean serviceDiscoveryPending = (priority == BluetoothProfile.PRIORITY_UNDEFINED)
@@ -589,15 +613,21 @@
         if (states == null) {
             return devices;
         }
-        final BluetoothDevice[] bondedDevices = mAdapterService.getBondedDevices();
+        BluetoothDevice [] bondedDevices = null;
+        synchronized (mVariableLock) {
+            if (mAdapterService != null)
+                bondedDevices = mAdapterService.getBondedDevices();
+        }
         if (bondedDevices == null) {
             return devices;
         }
         synchronized (mStateMachines) {
             for (BluetoothDevice device : bondedDevices) {
-                if (!BluetoothUuid.isUuidPresent(mAdapterService.getRemoteUuids(device),
+                synchronized (mVariableLock) {
+                    if (mAdapterService != null && !BluetoothUuid.isUuidPresent(mAdapterService.getRemoteUuids(device),
                                                  BluetoothUuid.AudioSink)) {
-                    continue;
+                        continue;
+                    }
                 }
                 int connectionState = BluetoothProfile.STATE_DISCONNECTED;
                 A2dpStateMachine sm = mStateMachines.get(device);
@@ -681,16 +711,20 @@
             isBAActive = (mBatService != null) && (mBatService.isBATActive());
             Log.d(TAG," removeActiveDevice: BA active " + isBAActive);
             // If BA streaming is ongoing, we don't want to pause music player
-            if(!isBAActive) {
-                mAudioManager.handleBluetoothA2dpActiveDeviceChange(
-                        previousActiveDevice, BluetoothProfile.STATE_DISCONNECTED,
-                        BluetoothProfile.A2DP, suppressNoisyIntent, -1);
-            }
+            synchronized (mVariableLock ) {
+                if(!isBAActive && mAudioManager != null) {
+                    mAudioManager.handleBluetoothA2dpActiveDeviceChange(
+                           previousActiveDevice, BluetoothProfile.STATE_DISCONNECTED,
+                           BluetoothProfile.A2DP, suppressNoisyIntent, -1);
+                }
+           }
         }
         // Make sure the Active device in native layer is set to null and audio is off
-        if (!mA2dpNativeInterface.setActiveDevice(null)) {
-            Log.w(TAG, "setActiveDevice(null): Cannot remove active device in native "
-                    + "layer");
+        synchronized (mVariableLock ) {
+            if (mA2dpNativeInterface != null && !mA2dpNativeInterface.setActiveDevice(null)) {
+                 Log.w(TAG, "setActiveDevice(null): Cannot remove active device in native "
+                        + "layer");
+            }
         }
     }
 
@@ -712,9 +746,12 @@
             // Set the device as the active device if currently no active device.
             setActiveDevice(device);
         }
-        if (!mA2dpNativeInterface.setSilenceDevice(device, silence)) {
-            Log.e(TAG, "Cannot set " + device + " silence mode " + silence + " in native layer");
-            return false;
+        synchronized (mVariableLock) {
+            if (mA2dpNativeInterface != null &&
+               !mA2dpNativeInterface.setSilenceDevice(device, silence)) {
+                Log.e(TAG, "Cannot set " + device + " silence mode " + silence + " in native layer");
+                return false;
+            }
         }
         return true;
     }
@@ -794,12 +831,15 @@
                           + "device is not connected");
                 return false;
             }
-            if (mActiveDevice != null && mAdapterService.isTwsPlusDevice(device) &&
-                mAdapterService.isTwsPlusDevice(mActiveDevice) &&
-                !Objects.equals(device, mActiveDevice) &&
-                getConnectionState(mActiveDevice) == BluetoothProfile.STATE_CONNECTED) {
-                Log.d(TAG,"Ignore setActiveDevice request");
-                return false;
+            synchronized (mVariableLock) {
+               if (mActiveDevice != null && mAdapterService != null &&
+                    mAdapterService.isTwsPlusDevice(device) &&
+                    mAdapterService.isTwsPlusDevice(mActiveDevice) &&
+                    !Objects.equals(device, mActiveDevice) &&
+                    getConnectionState(mActiveDevice) == BluetoothProfile.STATE_CONNECTED) {
+                    Log.d(TAG,"Ignore setActiveDevice request");
+                    return false;
+                }
             }
             codecStatus = sm.getCodecStatus();
 
@@ -816,47 +856,43 @@
             Log.w(TAG, "setActiveDevice coming out of mutex lock");
         }
 
-        if (!mA2dpNativeInterface.setActiveDevice(device)) {
-            Log.e(TAG, "setActiveDevice(" + device + "): Cannot set as active in native layer");
-            return false;
+        synchronized (mVariableLock) {
+            if (mA2dpNativeInterface != null && !mA2dpNativeInterface.setActiveDevice(device)) {
+                Log.e(TAG, "setActiveDevice(" + device + "): Cannot set as active in native layer");
+                return false;
+            }
         }
+
         updateAndBroadcastActiveDevice(device);
         Log.d(TAG, "setActiveDevice(" + device + "): completed");
 
         if (deviceChanged) {
             if(mAvrcp_ext != null)
                 mAvrcp_ext.setActiveDevice(device);
-            if (mAdapterService.isTwsPlusDevice(device) &&
-                (previousActiveDevice != null && mAdapterService.isTwsPlusDevice(previousActiveDevice))) {
-                Log.d(TAG,"TWS+ active device disconnected, setting other pair as active");
-                tws_switch = true;
+            synchronized (mVariableLock) {
+                if (mAdapterService != null && mAdapterService.isTwsPlusDevice(device) &&
+                    (previousActiveDevice != null && mAdapterService.isTwsPlusDevice(previousActiveDevice))) {
+                    Log.d(TAG,"TWS+ active device disconnected, setting other pair as active");
+                    tws_switch = true;
+                }
             }
             // Send an intent with the active device codec config
             if (codecStatus != null) {
                 broadcastCodecConfig(mActiveDevice, codecStatus);
             }
             int rememberedVolume = -1;
-            if (mFactory.getAvrcpTargetService() != null) {
-                rememberedVolume = mFactory.getAvrcpTargetService()
-                        .getRememberedVolumeForDevice(mActiveDevice);
-            } else if (mAdapterService.isVendorIntfEnabled()) {
-                rememberedVolume = mAvrcp_ext.getVolume(device);
-                Log.d(TAG,"volume = " + rememberedVolume);
-            }
-            // Make sure the Audio Manager knows the previous Active device is disconnected,
-            // and the new Active device is connected.
-            // Also, mute and unmute the output during the switch to avoid audio glitches.
-            boolean wasMuted = false;
-            if (previousActiveDevice != null && !tws_switch) {
-                if (!mAudioManager.isStreamMute(AudioManager.STREAM_MUSIC)) {
-                   mAudioManager.adjustStreamVolume(AudioManager.STREAM_MUSIC,
-                                                 AudioManager.ADJUST_MUTE,
-                                                 mAudioManager.FLAG_BLUETOOTH_ABS_VOLUME);
-                   wasMuted = true;
+            synchronized (mVariableLock) {
+                if (mFactory.getAvrcpTargetService() != null) {
+                    rememberedVolume = mFactory.getAvrcpTargetService()
+                           .getRememberedVolumeForDevice(mActiveDevice);
+                } else if (mAdapterService != null && mAdapterService.isVendorIntfEnabled()) {
+                    rememberedVolume = mAvrcp_ext.getVolume(device);
+                    Log.d(TAG,"volume = " + rememberedVolume);
                 }
-            }
+                // Make sure the Audio Manager knows the previous Active device is disconnected,
+                // and the new Active device is connected.
 
-            if (!isBAActive) {
+                if (!isBAActive && mAudioManager != null) {
                 // Make sure the Audio Manager knows the previous
                 // Active device is disconnected, and the new Active
                 // device is connected.
@@ -864,19 +900,25 @@
                 // new active device so that Audio Service
                 // can reset accordingly the audio feeding parameters
                 // in the Audio HAL to the Bluetooth stack.
-                 mAudioManager.handleBluetoothA2dpActiveDeviceChange(
-                          mActiveDevice, BluetoothProfile.STATE_CONNECTED, BluetoothProfile.A2DP,
-                          true, rememberedVolume);
-            }
+                    mAudioManager.handleBluetoothA2dpActiveDeviceChange(
+                            mActiveDevice, BluetoothProfile.STATE_CONNECTED, BluetoothProfile.A2DP,
+                              true, rememberedVolume);
+                }
 
             // Inform the Audio Service about the codec configuration
             // change, so the Audio Service can reset accordingly the audio
             // feeding parameters in the Audio HAL to the Bluetooth stack.
 
-            if (wasMuted) {
-               mAudioManager.adjustStreamVolume(AudioManager.STREAM_MUSIC,
-                                          AudioManager.ADJUST_UNMUTE,
-                                          mAudioManager.FLAG_BLUETOOTH_ABS_VOLUME);
+            // Split A2dp will be enabled by default
+                boolean isSplitA2dpEnabled = true;
+                AdapterService adapterService = AdapterService.getAdapterService();
+
+                if (adapterService != null){
+                    isSplitA2dpEnabled = adapterService.isSplitA2dpEnabled();
+                    Log.v(TAG,"isSplitA2dpEnabled: " + isSplitA2dpEnabled);
+                } else {
+                    Log.e(TAG,"adapterService is null");
+                }
             }
             if (mAvrcp_ext != null && !tws_switch) {
                 mAvrcp_ext.setAbsVolumeFlag(device);
@@ -909,15 +951,23 @@
         if (DBG) {
             Log.d(TAG, "Saved priority " + device + " = " + priority);
         }
-        mAdapterService.getDatabase()
+
+        synchronized (mVariableLock) {
+            if(mAdapterService != null)
+       	        mAdapterService.getDatabase()
                 .setProfilePriority(device, BluetoothProfile.A2DP, priority);
+        }
         return true;
     }
 
     public int getPriority(BluetoothDevice device) {
         enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission");
-        return mAdapterService.getDatabase()
-                .getProfilePriority(device, BluetoothProfile.A2DP);
+        synchronized (mVariableLock) {
+            if(mAdapterService != null)
+                return mAdapterService.getDatabase()
+                    .getProfilePriority(device, BluetoothProfile.A2DP);
+        }
+        return BluetoothProfile.PRIORITY_UNDEFINED;
     }
 
     /* Absolute volume implementation */
@@ -1089,7 +1139,7 @@
             device = mActiveDevice;
         }
         if (device == null) {
-            Log.e(TAG, "Cannot set codec config preference: no active A2DP device");
+            Log.e(TAG, "setCodecConfigPreference: Invalid device");
             return;
         }
 
@@ -1115,20 +1165,21 @@
             }
         }
 
-        if (getSupportsOptionalCodecs(device) != BluetoothA2dp.OPTIONAL_CODECS_SUPPORTED) {
-            Log.e(TAG, "Cannot set codec config preference: not supported");
+        if (codecConfig == null) {
+            Log.e(TAG, "setCodecConfigPreference: Codec config can't be null");
             return;
         }
-
         BluetoothCodecStatus codecStatus = getCodecStatus(device);
         if (codecStatus == null) {
-            Log.e(TAG, "Codec status is null on " + device);
+            Log.e(TAG, "setCodecConfigPreference: Codec status is null");
             return;
         }
-        if (mAdapterService.isTwsPlusDevice(device) && ( cs4 == 0 ||
-             codecConfig.getCodecType() != BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX_ADAPTIVE )) {
-            Log.w(TAG, "Block un-supportive codec on TWS+ device: " + device);
-            return;
+        synchronized (mVariableLock) {
+            if (mAdapterService != null && mAdapterService.isTwsPlusDevice(device) && ( cs4 == 0 ||
+                 codecConfig.getCodecType() != BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX_ADAPTIVE )) {
+                Log.w(TAG, "Block un-supportive codec on TWS+ device: " + device);
+                return;
+            }
         }
         mA2dpCodecConfig.setCodecConfigPreference(device, codecStatus, codecConfig);
     }
@@ -1149,16 +1200,16 @@
             device = mActiveDevice;
         }
         if (device == null) {
-            Log.e(TAG, "Cannot enable optional codecs: no active A2DP device");
+            Log.e(TAG, "enableOptionalCodecs: Invalid device");
             return;
         }
         if (getSupportsOptionalCodecs(device) != BluetoothA2dp.OPTIONAL_CODECS_SUPPORTED) {
-            Log.e(TAG, "Cannot enable optional codecs: not supported");
+            Log.e(TAG, "enableOptionalCodecs: No optional codecs");
             return;
         }
         BluetoothCodecStatus codecStatus = getCodecStatus(device);
         if (codecStatus == null) {
-            Log.e(TAG, "Cannot enable optional codecs: codec status is null");
+            Log.e(TAG, "enableOptionalCodecs: Codec status is null");
             return;
         }
         mA2dpCodecConfig.enableOptionalCodecs(device, codecStatus.getCodecConfig());
@@ -1180,16 +1231,16 @@
             device = mActiveDevice;
         }
         if (device == null) {
-            Log.e(TAG, "Cannot disable optional codecs: no active A2DP device");
+            Log.e(TAG, "disableOptionalCodecs: Invalid device");
             return;
         }
         if (getSupportsOptionalCodecs(device) != BluetoothA2dp.OPTIONAL_CODECS_SUPPORTED) {
-            Log.e(TAG, "Cannot disable optional codecs: not supported");
+            Log.e(TAG, "disableOptionalCodecs: No optional codecs");
             return;
         }
         BluetoothCodecStatus codecStatus = getCodecStatus(device);
         if (codecStatus == null) {
-            Log.e(TAG, "Cannot disable optional codecs: codec status is null");
+            Log.e(TAG, "disableOptionalCodecs: Codec status is null");
             return;
         }
         mA2dpCodecConfig.disableOptionalCodecs(device, codecStatus.getCodecConfig());
@@ -1197,19 +1248,30 @@
 
     public int getSupportsOptionalCodecs(BluetoothDevice device) {
         enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission");
-        return mAdapterService.getDatabase().getA2dpSupportsOptionalCodecs(device);
+        synchronized (mVariableLock) {
+            if(mAdapterService != null)
+                return mAdapterService.getDatabase().getA2dpSupportsOptionalCodecs(device);
+        }
+        return BluetoothA2dp.OPTIONAL_CODECS_NOT_SUPPORTED;
     }
 
     public void setSupportsOptionalCodecs(BluetoothDevice device, boolean doesSupport) {
         enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission");
         int value = doesSupport ? BluetoothA2dp.OPTIONAL_CODECS_SUPPORTED
                 : BluetoothA2dp.OPTIONAL_CODECS_NOT_SUPPORTED;
-        mAdapterService.getDatabase().setA2dpSupportsOptionalCodecs(device, value);
+        synchronized (mVariableLock) {
+            if(mAdapterService != null)
+                mAdapterService.getDatabase().setA2dpSupportsOptionalCodecs(device, value);
+        }
     }
 
     public int getOptionalCodecsEnabled(BluetoothDevice device) {
         enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission");
-        return mAdapterService.getDatabase().getA2dpOptionalCodecsEnabled(device);
+        synchronized (mVariableLock) {
+            if(mAdapterService != null)
+                return mAdapterService.getDatabase().getA2dpOptionalCodecsEnabled(device);
+        }
+        return BluetoothA2dp.OPTIONAL_CODECS_PREF_UNKNOWN;
     }
 
     public void setOptionalCodecsEnabled(BluetoothDevice device, int value) {
@@ -1220,7 +1282,10 @@
             Log.w(TAG, "Unexpected value passed to setOptionalCodecsEnabled:" + value);
             return;
         }
-        mAdapterService.getDatabase().setA2dpOptionalCodecsEnabled(device, value);
+        synchronized (mVariableLock) {
+            if(mAdapterService != null)
+                mAdapterService.getDatabase().setA2dpOptionalCodecsEnabled(device, value);
+        }
     }
 
     // Handle messages from native (JNI) to Java
@@ -1235,40 +1300,46 @@
                     switch (stackEvent.valueInt) {
                         case A2dpStackEvent.CONNECTION_STATE_CONNECTED:
                         case A2dpStackEvent.CONNECTION_STATE_CONNECTING:
-                            // Create a new state machine only when connecting to a device
-                            if (mAdapterService.isVendorIntfEnabled())
-                                mA2dpStackEvent =  stackEvent.valueInt;
-                            if (mAdapterService.isTwsPlusDevice(device)) {
-                                sm = getOrCreateStateMachine(device);
-                                break;
-                            }
-                            if (!connectionAllowedCheckMaxDevices(device)) {
-                                Log.e(TAG, "Cannot connect to " + device
-                                        + " : too many connected devices");
-                                mA2dpNativeInterface.disconnectA2dp(device);
-                                return;
+                            synchronized (mVariableLock) {
+                                // Create a new state machine only when connecting to a device
+                                if (mAdapterService != null && mAdapterService.isVendorIntfEnabled())
+                                    mA2dpStackEvent =  stackEvent.valueInt;
+                                if (mAdapterService != null && mAdapterService.isTwsPlusDevice(device)) {
+                                    sm = getOrCreateStateMachine(device);
+                                    break;
+                                }
+                                if (!connectionAllowedCheckMaxDevices(device) && mA2dpNativeInterface != null) {
+                                    Log.e(TAG, "Cannot connect to " + device
+                                            + " : too many connected devices");
+                                    mA2dpNativeInterface.disconnectA2dp(device);
+                                    return;
+                                }
                             }
                             sm = getOrCreateStateMachine(device);
                             break;
                         default:
-                            if (mAdapterService.isVendorIntfEnabled() &&
-                                mA2dpStackEvent == A2dpStackEvent.CONNECTION_STATE_CONNECTED ||
-                                mA2dpStackEvent == A2dpStackEvent.CONNECTION_STATE_CONNECTING) {
-                                Log.d(TAG,"Reset local stack event value");
-                                mA2dpStackEvent = EVENT_TYPE_NONE;
+                            synchronized (mVariableLock) {
+                                if (mAdapterService!= null && mAdapterService.isVendorIntfEnabled() &&
+                                    mA2dpStackEvent == A2dpStackEvent.CONNECTION_STATE_CONNECTED ||
+                                    mA2dpStackEvent == A2dpStackEvent.CONNECTION_STATE_CONNECTING) {
+                                    Log.d(TAG,"Reset local stack event value");
+                                    mA2dpStackEvent = EVENT_TYPE_NONE;
+                                }
                             }
                             break;
                     }
                 }
             } else {
-                if (mAdapterService.isVendorIntfEnabled()) {
-                    switch (sm.getConnectionState()) {
-                      case BluetoothProfile.STATE_DISCONNECTED:
-                        mA2dpStackEvent = stackEvent.valueInt;
-                        break;
-                      default:
-                        mA2dpStackEvent = EVENT_TYPE_NONE;
-                        break;
+                synchronized (mVariableLock) {
+                    if (mAdapterService != null && mAdapterService.isVendorIntfEnabled()) {
+                        switch (sm.getConnectionState()) {
+                            case BluetoothProfile.STATE_DISCONNECTED:
+                                mA2dpStackEvent = stackEvent.valueInt;
+                            break;
+                            default:
+                                mA2dpStackEvent = EVENT_TYPE_NONE;
+                            break;
+                        }
                     }
                 }
             }
@@ -1296,20 +1367,24 @@
 
         // Log codec config and capability metrics
         BluetoothCodecConfig codecConfig = codecStatus.getCodecConfig();
-        StatsLog.write(StatsLog.BLUETOOTH_A2DP_CODEC_CONFIG_CHANGED,
-                mAdapterService.obfuscateAddress(device), codecConfig.getCodecType(),
-                codecConfig.getCodecPriority(), codecConfig.getSampleRate(),
-                codecConfig.getBitsPerSample(), codecConfig.getChannelMode(),
-                codecConfig.getCodecSpecific1(), codecConfig.getCodecSpecific2(),
-                codecConfig.getCodecSpecific3(), codecConfig.getCodecSpecific4());
-        BluetoothCodecConfig[] codecCapabilities = codecStatus.getCodecsSelectableCapabilities();
-        for (BluetoothCodecConfig codecCapability : codecCapabilities) {
-            StatsLog.write(StatsLog.BLUETOOTH_A2DP_CODEC_CAPABILITY_CHANGED,
-                    mAdapterService.obfuscateAddress(device), codecCapability.getCodecType(),
-                    codecCapability.getCodecPriority(), codecCapability.getSampleRate(),
-                    codecCapability.getBitsPerSample(), codecCapability.getChannelMode(),
+        synchronized (mVariableLock) {
+            if(mAdapterService != null) {
+                StatsLog.write(StatsLog.BLUETOOTH_A2DP_CODEC_CONFIG_CHANGED,
+                    mAdapterService.obfuscateAddress(device), codecConfig.getCodecType(),
+                    codecConfig.getCodecPriority(), codecConfig.getSampleRate(),
+                    codecConfig.getBitsPerSample(), codecConfig.getChannelMode(),
                     codecConfig.getCodecSpecific1(), codecConfig.getCodecSpecific2(),
                     codecConfig.getCodecSpecific3(), codecConfig.getCodecSpecific4());
+                BluetoothCodecConfig[] codecCapabilities = codecStatus.getCodecsSelectableCapabilities();
+                for (BluetoothCodecConfig codecCapability : codecCapabilities) {
+                    StatsLog.write(StatsLog.BLUETOOTH_A2DP_CODEC_CAPABILITY_CHANGED,
+                        mAdapterService.obfuscateAddress(device), codecCapability.getCodecType(),
+                        codecCapability.getCodecPriority(), codecCapability.getSampleRate(),
+                        codecCapability.getBitsPerSample(), codecCapability.getChannelMode(),
+                        codecConfig.getCodecSpecific1(), codecConfig.getCodecSpecific2(),
+                        codecConfig.getCodecSpecific3(), codecConfig.getCodecSpecific4());
+                }
+            }
         }
 
         broadcastCodecConfig(device, codecStatus);
@@ -1317,15 +1392,27 @@
         // Inform the Audio Service about the codec configuration change,
         // so the Audio Service can reset accordingly the audio feeding
         // parameters in the Audio HAL to the Bluetooth stack.
+        int rememberedVolume = -1;
         if (isActiveDevice(device) && !sameAudioFeedingParameters) {
-            mAudioManager.handleBluetoothA2dpActiveDeviceChange(device,
-                    BluetoothProfile.STATE_CONNECTED, BluetoothProfile.A2DP,
-                    true, -1);
+            if (mAvrcp_ext != null)
+                rememberedVolume = mAvrcp_ext.getVolume(device);
+            synchronized (mVariableLock) {
+                if (mAudioManager != null) {
+                    mAudioManager.handleBluetoothA2dpActiveDeviceChange(device,
+                            BluetoothProfile.STATE_CONNECTED, BluetoothProfile.A2DP,
+                            true, rememberedVolume);
+                }
+            }
         }
     }
+
     void updateTwsChannelMode(int state, BluetoothDevice device) {
         if (mIsTwsPlusMonoSupported) {
-            BluetoothDevice peerTwsDevice = mAdapterService.getTwsPlusPeerDevice(device);
+            BluetoothDevice peerTwsDevice = null;
+            synchronized (mVariableLock) {
+                if (mAdapterService != null)
+                    peerTwsDevice = mAdapterService.getTwsPlusPeerDevice(device);
+            }
             Log.d(TAG, "TwsChannelMode: " + mTwsPlusChannelMode);
             synchronized(mBtTwsLock) {
                 if ("mono".equals(mTwsPlusChannelMode)) {
@@ -1336,7 +1423,10 @@
                         mHandler.sendMessageDelayed(msg, DualMonoCfg_Timeout);
                     } else if (state == BluetoothA2dp.STATE_PLAYING) {
                         Log.d(TAG, "setparameters to Mono");
-                        mAudioManager.setParameters("TwsChannelConfig=mono");
+                        synchronized (mVariableLock) {
+                            if (mAudioManager != null)
+                                mAudioManager.setParameters("TwsChannelConfig=mono");
+                        }
                     }
                     if ((state == BluetoothA2dp.STATE_NOT_PLAYING) &&
                            isA2dpPlaying(peerTwsDevice)) {
@@ -1362,7 +1452,10 @@
                     if ((state == BluetoothA2dp.STATE_NOT_PLAYING)
                           && isA2dpPlaying(peerTwsDevice)) {
                         Log.d(TAG, "setparameters to Mono");
-                        mAudioManager.setParameters("TwsChannelConfig=mono");
+                        synchronized (mVariableLock) {
+                            if (mAudioManager != null)
+                                mAudioManager.setParameters("TwsChannelConfig=mono");
+                        }
                         mTwsPlusChannelMode = "mono";
                     }
                 }
@@ -1382,7 +1475,10 @@
         if(isBAActive) {
             return;
         }
-        mAudioManager.setParameters("reconfigA2dp=true");
+        synchronized (mVariableLock) {
+            if (mAudioManager != null)
+                mAudioManager.setParameters("reconfigA2dp=true");
+        }
     }
 
 
@@ -1428,8 +1524,11 @@
             mActiveDevice = device;
         }
 
-        StatsLog.write(StatsLog.BLUETOOTH_ACTIVE_DEVICE_CHANGED, BluetoothProfile.A2DP,
-                mAdapterService.obfuscateAddress(device));
+        synchronized (mVariableLock) {
+            if (mAdapterService != null)
+                StatsLog.write(StatsLog.BLUETOOTH_ACTIVE_DEVICE_CHANGED, BluetoothProfile.A2DP,
+                      mAdapterService.obfuscateAddress(device));
+        }
         Intent intent = new Intent(BluetoothA2dp.ACTION_ACTIVE_DEVICE_CHANGED);
         intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
         intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT
@@ -1492,6 +1591,9 @@
             if (mFactory.getAvrcpTargetService() != null) {
                 mFactory.getAvrcpTargetService().removeStoredVolumeForDevice(device);
             }
+            if (mAvrcp_ext != null) {
+                mAvrcp_ext.removeVolumeForDevice(device);
+            }
 
             removeStateMachine(device);
         }
@@ -1547,7 +1649,6 @@
             Log.i(TAG, "updateOptionalCodecsSupport: Mandatory codec is not selectable.");
             return;
         }
-
         if (previousSupport == BluetoothA2dp.OPTIONAL_CODECS_SUPPORT_UNKNOWN
                 || supportsOptional != (previousSupport
                                     == BluetoothA2dp.OPTIONAL_CODECS_SUPPORTED)) {
@@ -1578,14 +1679,20 @@
             if (toState == BluetoothProfile.STATE_CONNECTED) {
                 MetricsLogger.logProfileConnectionEvent(BluetoothMetricsProto.ProfileId.A2DP);
             }
+            int bondState = BluetoothDevice.BOND_NONE;
             // Check if the device is disconnected - if unbond, remove the state machine
             if (toState == BluetoothProfile.STATE_DISCONNECTED) {
-                int bondState = mAdapterService.getBondState(device);
+                synchronized (mVariableLock) {
+                    if(mAdapterService != null)
+                        bondState = mAdapterService.getBondState(device);
+                }
                 if (bondState == BluetoothDevice.BOND_NONE) {
                     if (mFactory.getAvrcpTargetService() != null) {
                         mFactory.getAvrcpTargetService().removeStoredVolumeForDevice(device);
                     }
-
+                    if (mAvrcp_ext != null) {
+                        mAvrcp_ext.removeVolumeForDevice(device);
+                    }
                     removeStateMachine(device);
                 }
             }
diff --git a/src/com/android/bluetooth/btservice/AbstractionLayer.java b/src/com/android/bluetooth/btservice/AbstractionLayer.java
index 6ee0d6e..30493f1 100644
--- a/src/com/android/bluetooth/btservice/AbstractionLayer.java
+++ b/src/com/android/bluetooth/btservice/AbstractionLayer.java
@@ -126,6 +126,7 @@
     public static final int PBAP = 2;
     public static final int MAP = 3;
     public static final int MAX_POW = 4;
+    public static final int HEARING_AID = 5;
 
     // Profile features supported in profile_conf
     public static final int PROFILE_VERSION =1;
@@ -134,9 +135,11 @@
     public static final int USE_SIM_SUPPORT = 4;
     public static final int MAP_EMAIL_SUPPORT = 5;
     public static final int PBAP_0102_SUPPORT = 6;
-    public static final int BR_MAX_POW_SUPPORT = 7;
-    public static final int EDR_MAX_POW_SUPPORT = 8;
-    public static final int BLE_MAX_POW_SUPPORT = 9;
+    public static final int MAP_0104_SUPPORT = 7;
+    public static final int BR_MAX_POW_SUPPORT = 8;
+    public static final int EDR_MAX_POW_SUPPORT = 9;
+    public static final int BLE_MAX_POW_SUPPORT = 10;
+    public static final int HEARING_AID_SUPPORT = 11;
 
 
     static final int BT_VENDOR_PROPERTY_TWS_PLUS_DEVICE_TYPE = 0x01;
diff --git a/src/com/android/bluetooth/btservice/AdapterProperties.java b/src/com/android/bluetooth/btservice/AdapterProperties.java
index beff7c8..c7eb53c 100644
--- a/src/com/android/bluetooth/btservice/AdapterProperties.java
+++ b/src/com/android/bluetooth/btservice/AdapterProperties.java
@@ -86,7 +86,18 @@
     private CopyOnWriteArrayList<BluetoothDevice> mBondedDevices =
             new CopyOnWriteArrayList<BluetoothDevice>();
 
-    private int mProfilesConnecting, mProfilesConnected, mProfilesDisconnecting;
+    private final class ProfilesConnectionState {
+        public int mProfilesConnecting, mProfilesConnected, mProfilesDisconnecting;
+
+        ProfilesConnectionState(int connecting, int connected, int disconnecting) {
+          mProfilesConnecting    = connecting;
+          mProfilesConnected     = connected;
+          mProfilesDisconnecting = disconnecting;
+        }
+    }
+    private final HashMap<BluetoothDevice, ProfilesConnectionState> mDevicesConnectionState =
+            new HashMap<>();
+
     private final HashMap<Integer, Pair<Integer, Integer>> mProfileConnectionState =
             new HashMap<>();
 
@@ -218,6 +229,7 @@
 
     public void init(RemoteDevices remoteDevices) {
         mProfileConnectionState.clear();
+        mDevicesConnectionState.clear();
         mRemoteDevices = remoteDevices;
 
         // Get default max connected audio devices from config.xml in frameworks/base/core
@@ -274,6 +286,7 @@
         }
         mService = null;
         mBondedDevices.clear();
+        mDevicesConnectionState.clear();
     }
 
     @Override
@@ -787,6 +800,10 @@
                     debugLog("Adding bonded device:" + device);
                     mBondedDevices.add(device);
                 }
+                if (!mDevicesConnectionState.containsKey(device)) {
+                    debugLog("Adding connection state:" + device);
+                    mDevicesConnectionState.put(device, new ProfilesConnectionState(0, 0, 0));
+                }
             } else if (state == BluetoothDevice.BOND_NONE) {
                 // remove device from list
                 if (mBondedDevices.remove(device)) {
@@ -794,6 +811,10 @@
                 } else {
                     debugLog("Failed to remove device: " + device);
                 }
+                if (mDevicesConnectionState.containsKey(device)) {
+                    debugLog("Removing connection state:" + device);
+                    mDevicesConnectionState.remove(device);
+                }
             }
         } catch (Exception ee) {
             Log.w(TAG, "onBondStateChanged: Exception ", ee);
@@ -868,7 +889,7 @@
 
             try {
                 validateConnectionState =
-                   updateCountersAndCheckForConnectionStateChange(state, prevState);
+                   updateCountersAndCheckForConnectionStateChange(device, state, prevState);
             } catch (IllegalStateException ee) {
                 Log.w(TAG, "ADAPTER_CONNECTION_STATE_CHANGE: unexpected transition for profile="
                         + profile + ", " + prevState + " -> " + state);
@@ -932,33 +953,45 @@
         }
     }
 
-    private boolean updateCountersAndCheckForConnectionStateChange(int state, int prevState) {
+    private boolean updateCountersAndCheckForConnectionStateChange(BluetoothDevice device,
+            int state, int prevState) {
+        if(!mDevicesConnectionState.containsKey(device)) {
+            Log.e(TAG, "Can't find device connection record, adding new one: " + device);
+            mDevicesConnectionState.put(device, new ProfilesConnectionState(0, 0, 0));
+        }
+        ProfilesConnectionState connstate = mDevicesConnectionState.get(device);
+
+        Log.e(TAG, "prevState=" + prevState + " -> State=" + state +
+            " mProfilesConnecting=" + connstate.mProfilesConnecting +
+            " mProfilesConnected=" + connstate.mProfilesConnected +
+            " mProfilesDisconnecting=" + connstate.mProfilesDisconnecting);
+
         switch (prevState) {
             case BluetoothProfile.STATE_CONNECTING:
-                if (mProfilesConnecting > 0) {
-                    mProfilesConnecting--;
+                if (connstate.mProfilesConnecting > 0) {
+                    connstate.mProfilesConnecting--;
                 } else {
-                    Log.e(TAG, "mProfilesConnecting " + mProfilesConnecting);
+                    Log.e(TAG, "mProfilesConnecting " + connstate.mProfilesConnecting);
                     throw new IllegalStateException(
                             "Invalid state transition, " + prevState + " -> " + state);
                 }
                 break;
 
             case BluetoothProfile.STATE_CONNECTED:
-                if (mProfilesConnected > 0) {
-                    mProfilesConnected--;
+                if (connstate.mProfilesConnected > 0) {
+                    connstate.mProfilesConnected--;
                 } else {
-                    Log.e(TAG, "mProfilesConnected " + mProfilesConnected);
+                    Log.e(TAG, "mProfilesConnected " + connstate.mProfilesConnected);
                     throw new IllegalStateException(
                             "Invalid state transition, " + prevState + " -> " + state);
                 }
                 break;
 
             case BluetoothProfile.STATE_DISCONNECTING:
-                if (mProfilesDisconnecting > 0) {
-                    mProfilesDisconnecting--;
+                if (connstate.mProfilesDisconnecting > 0) {
+                    connstate.mProfilesDisconnecting--;
                 } else {
-                    Log.e(TAG, "mProfilesDisconnecting " + mProfilesDisconnecting);
+                    Log.e(TAG, "mProfilesDisconnecting " + connstate.mProfilesDisconnecting);
                     throw new IllegalStateException(
                             "Invalid state transition, " + prevState + " -> " + state);
                 }
@@ -967,19 +1000,19 @@
 
         switch (state) {
             case BluetoothProfile.STATE_CONNECTING:
-                mProfilesConnecting++;
-                return (mProfilesConnected == 0 && mProfilesConnecting == 1);
+                connstate.mProfilesConnecting++;
+                return (connstate.mProfilesConnected == 0 && connstate.mProfilesConnecting == 1);
 
             case BluetoothProfile.STATE_CONNECTED:
-                mProfilesConnected++;
-                return (mProfilesConnected == 1);
+                connstate.mProfilesConnected++;
+                return (connstate.mProfilesConnected == 1);
 
             case BluetoothProfile.STATE_DISCONNECTING:
-                mProfilesDisconnecting++;
-                return (mProfilesConnected == 0 && mProfilesDisconnecting == 1);
+                connstate.mProfilesDisconnecting++;
+                return (connstate.mProfilesConnected == 0 && connstate.mProfilesDisconnecting == 1);
 
             case BluetoothProfile.STATE_DISCONNECTED:
-                return (mProfilesConnected == 0 && mProfilesConnecting == 0);
+                return (connstate.mProfilesConnected == 0 && connstate.mProfilesConnecting == 0);
 
             default:
                 return true;
@@ -1240,9 +1273,8 @@
             // Reset adapter and profile connection states
             setConnectionState(BluetoothAdapter.STATE_DISCONNECTED);
             mProfileConnectionState.clear();
-            mProfilesConnected = 0;
-            mProfilesConnecting = 0;
-            mProfilesDisconnecting = 0;
+            mDevicesConnectionState.clear();
+
             // adapterPropertyChangedCallback has already been received.  Set the scan mode.
             setScanMode(AbstractionLayer.BT_SCAN_MODE_CONNECTABLE);
             // This keeps NV up-to date on first-boot after flash.
diff --git a/src/com/android/bluetooth/btservice/Config.java b/src/com/android/bluetooth/btservice/Config.java
index 0e170b1..0a91e55 100644
--- a/src/com/android/bluetooth/btservice/Config.java
+++ b/src/com/android/bluetooth/btservice/Config.java
@@ -121,10 +121,12 @@
         for (ProfileConfig config : PROFILE_SERVICES_AND_FLAGS) {
             boolean supported = resources.getBoolean(config.mSupported);
 
-            if (!supported && (config.mClass == HearingAidService.class) && FeatureFlagUtils
-                                .isEnabled(ctx, FeatureFlagUtils.HEARING_AID_SETTINGS)) {
-                Log.v(TAG, "Feature Flag enables support for HearingAidService");
-                supported = true;
+            if (config.mClass == HearingAidService.class &&
+                    FeatureFlagUtils.isEnabled(ctx, FeatureFlagUtils.HEARING_AID_SETTINGS)) {
+                if (!isHearingAidSupported()) {
+                    supported = false;
+                }
+                if (supported) Log.v(TAG, "HearingAidService is enabled");
             }
 
             if (supported && !isProfileDisabled(ctx, config.mMask)) {
@@ -201,4 +203,15 @@
         // always return true for other profiles
         return true;
     }
+
+    private static boolean isHearingAidSupported() {
+        AdapterService adapterService = AdapterService.getAdapterService();
+        boolean isHearingAidSupported = false;
+        if (adapterService != null) {
+          isHearingAidSupported = adapterService.getProfileInfo(
+              AbstractionLayer.HEARING_AID, AbstractionLayer.HEARING_AID_SUPPORT);
+          Log.d(TAG, "isHearingAidSupported: " + isHearingAidSupported);
+        }
+        return isHearingAidSupported;
+    }
 }
diff --git a/src/com/android/bluetooth/hfp/HeadsetService.java b/src/com/android/bluetooth/hfp/HeadsetService.java
index c256d8d..fce6d43 100644
--- a/src/com/android/bluetooth/hfp/HeadsetService.java
+++ b/src/com/android/bluetooth/hfp/HeadsetService.java
@@ -56,6 +56,7 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Comparator;
+import java.util.concurrent.Executor;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Objects;
@@ -128,6 +129,8 @@
     private boolean mIsTwsPlusEnabled = false;
     private boolean mIsTwsPlusShoEnabled = false;
     private vendorhfservice  mVendorHf;
+    private Context mContext = null;
+    private AudioServerStateCallback mServerStateCallback = new AudioServerStateCallback();
 
     @Override
     public IProfileServiceBinder initBinder() {
@@ -226,6 +229,11 @@
             mVendorHf.enableSwb(isSwbEnabled());
         }
 
+        Log.d(TAG, "registering audio server state callback");
+        mContext = getApplicationContext();
+        Executor exec = mContext.getMainExecutor();
+        mSystemInterface.getAudioManager().setAudioServerStateCallback(exec, mServerStateCallback);
+
         Log.i(TAG, " HeadsetService Started ");
         return true;
     }
@@ -424,6 +432,30 @@
         }
     }
 
+    private class AudioServerStateCallback extends AudioManager.AudioServerStateCallback {
+        @Override
+        public void onAudioServerDown() {
+            Log.d(TAG, "notifying onAudioServerDown");
+        }
+
+        @Override
+        public void onAudioServerUp() {
+            Log.d(TAG, "notifying onAudioServerUp");
+            if (isAudioOn()) {
+                Log.d(TAG, "onAudioServerUp: Audio is On, Notify HeadsetStateMachine");
+                synchronized (mStateMachines) {
+                    for (HeadsetStateMachine stateMachine : mStateMachines.values()) {
+                        if (stateMachine.getAudioState()
+                                == BluetoothHeadset.STATE_AUDIO_CONNECTED) {
+                            stateMachine.onAudioServerUp();
+                            break;
+                        }
+                    }
+                }
+            }
+        }
+    }
+
     private final BroadcastReceiver mHeadsetReceiver = new BroadcastReceiver() {
         @Override
         public void onReceive(Context context, Intent intent) {
@@ -829,13 +861,14 @@
         return null;
     }
 
-    private BluetoothDevice getConnectedTwspDevice() {
-        List<BluetoothDevice> connDevices = getConnectedDevices();
+    private BluetoothDevice getConnectedOrConnectingTwspDevice() {
+        List<BluetoothDevice> connDevices =
+            getAllDevicesMatchingConnectionStates(CONNECTING_CONNECTED_STATES);
         int size = connDevices.size();
         for(int i = 0; i < size; i++) {
             BluetoothDevice ConnectedDevice = connDevices.get(i);
             if (mAdapterService.isTwsPlusDevice(ConnectedDevice)) {
-                logD("getConnectedTwspDevice: found" + ConnectedDevice);
+                logD("getConnectedorConnectingTwspDevice: found" + ConnectedDevice);
                 return ConnectedDevice;
             }
         }
@@ -873,12 +906,14 @@
         if (connDevices.size() == 0) {
             allowSecondHfConnection = true;
         } else {
-            BluetoothDevice connectedTwspDev = getConnectedTwspDevice();
-            if (connectedTwspDev != null) {
+            BluetoothDevice connectedOrConnectingTwspDev =
+                    getConnectedOrConnectingTwspDevice();
+            if (connectedOrConnectingTwspDev != null) {
                 // There is TWSP connected earbud
                 if (adapterService.isTwsPlusDevice(device)) {
                    if (adapterService.getTwsPlusPeerAddress
-                           (device).equals(connectedTwspDev.getAddress())) {
+                           (device).equals(
+                             connectedOrConnectingTwspDev.getAddress())) {
                        //Allow connection only if the outgoing
                        //is peer of TWS connected earbud
                        allowSecondHfConnection = true;
@@ -887,7 +922,8 @@
                    }
                 } else {
                    reservedSlotForTwspPeer = 0;
-                   if (getTwsPlusConnectedPeer(connectedTwspDev) == null) {
+                   if (getTwsPlusConnectedPeer(
+                                connectedOrConnectingTwspDev) == null) {
                        //Peer of Connected Tws+ device is not Connected
                        //yet, reserve one slot
                        reservedSlotForTwspPeer = 1;
@@ -933,8 +969,9 @@
                      "is"+ adapterService.isTwsPlusDevice(device));
             Log.v(TAG, "TWS Peer Addr: " +
                       adapterService.getTwsPlusPeerAddress(device));
-            if (connectedTwspDev != null) {
-                Log.v(TAG, "Connected device" + connectedTwspDev.getAddress());
+            if (connectedOrConnectingTwspDev != null) {
+                Log.v(TAG, "Connected or Connecting device"
+                         + connectedOrConnectingTwspDev.getAddress());
             } else {
                 Log.v(TAG, "No Connected TWSP devices");
             }
@@ -1251,7 +1288,9 @@
                             + " instead");
                     device = peerDevice;
                 } else {
-                    Log.w(TAG, "stopVoiceRecognition: both earbuds are not audio connected");
+                    Log.w(TAG, "stopVoiceRecognition: both earbuds are not audio connected, resume A2DP");
+                    mVoiceRecognitionStarted = false;
+                    mHfpA2dpSyncInterface.releaseA2DP(null);
                     return false;
                 }
             }
@@ -1272,7 +1311,13 @@
             }
             mVoiceRecognitionStarted = false;
             stateMachine.sendMessage(HeadsetStateMachine.VOICE_RECOGNITION_STOP, device);
-            stateMachine.sendMessage(HeadsetStateMachine.DISCONNECT_AUDIO, device);
+
+            if (isAudioOn()) {
+                stateMachine.sendMessage(HeadsetStateMachine.DISCONNECT_AUDIO, device);
+            } else {
+                Log.w(TAG, "SCO is not connected and VR stopped, resuming A2DP");
+                stateMachine.sendMessage(HeadsetStateMachine.RESUME_A2DP);
+            }
         }
         return true;
     }
@@ -1918,9 +1963,14 @@
                 mVoiceRecognitionTimeoutEvent = null;
             }
             if (mVoiceRecognitionStarted) {
-                if (!disconnectAudio()) {
-                    Log.w(TAG, "stopVoiceRecognitionByHeadset: failed to disconnect audio from "
+                if (isAudioOn()) {
+                    if (!disconnectAudio()) {
+                        Log.w(TAG, "stopVoiceRecognitionByHeadset: failed to disconnect audio from "
                             + fromDevice);
+                    }
+                } else {
+                    Log.w(TAG, "stopVoiceRecognitionByHeadset: No SCO connected, resume A2DP");
+                    mHfpA2dpSyncInterface.releaseA2DP(null);
                 }
                 mVoiceRecognitionStarted = false;
             }
diff --git a/src/com/android/bluetooth/hfp/HeadsetStateMachine.java b/src/com/android/bluetooth/hfp/HeadsetStateMachine.java
index 4e82d84..784dead 100644
--- a/src/com/android/bluetooth/hfp/HeadsetStateMachine.java
+++ b/src/com/android/bluetooth/hfp/HeadsetStateMachine.java
@@ -41,6 +41,7 @@
 import android.net.NetworkInfo;
 import android.net.Network;
 import android.util.StatsLog;
+import android.os.Build;
 
 import com.android.bluetooth.btservice.AdapterService;
 import com.android.bluetooth.btservice.ProfileService;
@@ -1879,6 +1880,14 @@
         return true;
     }
 
+    public void onAudioServerUp() {
+        Log.i(TAG, "onAudioSeverUp: restore audio parameters");
+        mSystemInterface.getAudioManager().setBluetoothScoOn(false);
+        mSystemInterface.getAudioManager().setParameters("A2dpSuspended=true");
+        setAudioParameters();
+        mSystemInterface.getAudioManager().setBluetoothScoOn(true);
+    }
+
     /*
      * Put the AT command, company ID, arguments, and device in an Intent and broadcast it.
      */
@@ -2629,6 +2638,12 @@
             processAtCpbr(atCommand.substring(5), commandType, device);
         } else if (atCommand.startsWith("+CSQ")) {
             mNativeInterface.atResponseCode(device, HeadsetHalConstants.AT_RESPONSE_ERROR, 0);
+        } else if (atCommand.equals("+CGMI")) {
+            mNativeInterface.atResponseString(device, "+CGMI: \"" + Build.MANUFACTURER + "\"");
+            mNativeInterface.atResponseCode(device, HeadsetHalConstants.AT_RESPONSE_OK, 0);
+        } else if (atCommand.equals("+CGMM")) {
+            mNativeInterface.atResponseString(device, "+CGMM: " + Build.MODEL);
+            mNativeInterface.atResponseCode(device, HeadsetHalConstants.AT_RESPONSE_OK, 0);
         } else {
             processVendorSpecificAt(atCommand, device);
         }
diff --git a/src/com/android/bluetooth/opp/BluetoothOppTransferActivity.java b/src/com/android/bluetooth/opp/BluetoothOppTransferActivity.java
index fc45d3f..e4d98e8 100644
--- a/src/com/android/bluetooth/opp/BluetoothOppTransferActivity.java
+++ b/src/com/android/bluetooth/opp/BluetoothOppTransferActivity.java
@@ -375,24 +375,13 @@
                             mTransInfo.mID);
                 } else if (mWhichDialog == DIALOG_SEND_COMPLETE_FAIL) {
                     // "try again"
-
                     // make current transfer "hidden"
                     BluetoothOppUtility.updateVisibilityToHidden(this, mUri);
 
                     // clear correspondent notification item
                     ((NotificationManager) getSystemService(NOTIFICATION_SERVICE)).cancel(
                             mTransInfo.mID);
-
-                    // retry the failed transfer
-                    Uri uri = BluetoothOppUtility.originalUri(Uri.parse(mTransInfo.mFileUri));
-                    BluetoothOppSendFileInfo sendFileInfo =
-                            BluetoothOppSendFileInfo.generateFileInfo(BluetoothOppTransferActivity
-                            .this, uri, mTransInfo.mFileType, false);
-                    uri = BluetoothOppUtility.generateUri(uri, sendFileInfo);
-                    BluetoothOppUtility.putSendFileInfo(uri, sendFileInfo);
-                    mTransInfo.mFileUri = uri.toString();
-                    BluetoothOppUtility.retryTransfer(this, mTransInfo);
-
+                    retryFailedTrasfer();
                     BluetoothDevice remoteDevice = mAdapter.getRemoteDevice(mTransInfo.mDestAddr);
 
                     // Display toast message
@@ -502,4 +491,22 @@
                     .setText(getString(R.string.upload_fail_cancel));
         }
     }
+
+ // Retry the failed transfer in background thread
+   private void retryFailedTrasfer() {
+        new Thread() {
+            @Override
+            public void run() {
+                super.run();
+                Uri uri = BluetoothOppUtility.originalUri(Uri.parse(mTransInfo.mFileUri));
+                BluetoothOppSendFileInfo sendFileInfo =
+                        BluetoothOppSendFileInfo.generateFileInfo(BluetoothOppTransferActivity
+                        .this, uri, mTransInfo.mFileType, false);
+                uri = BluetoothOppUtility.generateUri(uri, sendFileInfo);
+                BluetoothOppUtility.putSendFileInfo(uri, sendFileInfo);
+                mTransInfo.mFileUri = uri.toString();
+                BluetoothOppUtility.retryTransfer(BluetoothOppTransferActivity.this, mTransInfo);
+            }
+        }.start();
+    }
 }
diff --git a/src/com/android/bluetooth/sap/SapService.java b/src/com/android/bluetooth/sap/SapService.java
index 35716d8..9a9c0f8 100644
--- a/src/com/android/bluetooth/sap/SapService.java
+++ b/src/com/android/bluetooth/sap/SapService.java
@@ -165,6 +165,10 @@
             } catch (IOException e) {
                 Log.e(TAG, "Error create RfcommServerSocket ", e);
                 initSocketOK = false;
+            } catch (SecurityException e) {
+                Log.e(TAG, "Error create RfcommServerSocket ", e);
+                initSocketOK = false;
+                break;
             }
 
             if (!initSocketOK) {
diff --git a/tests/unit/src/com/android/bluetooth/a2dp/A2dpCodecConfigTest.java b/tests/unit/src/com/android/bluetooth/a2dp/A2dpCodecConfigTest.java
index c121cf7..a97a5b0 100644
--- a/tests/unit/src/com/android/bluetooth/a2dp/A2dpCodecConfigTest.java
+++ b/tests/unit/src/com/android/bluetooth/a2dp/A2dpCodecConfigTest.java
@@ -23,11 +23,14 @@
 import android.bluetooth.BluetoothCodecStatus;
 import android.bluetooth.BluetoothDevice;
 import android.content.Context;
+import android.content.res.Resources;
 
 import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.MediumTest;
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.bluetooth.R;
+
 import org.junit.After;
 import org.junit.Assert;
 import org.junit.Before;
@@ -40,75 +43,126 @@
 @RunWith(AndroidJUnit4.class)
 public class A2dpCodecConfigTest {
     private Context mTargetContext;
-    private A2dpCodecConfig mA2dpCodecConfig;
-    private BluetoothAdapter mAdapter;
-    private BluetoothCodecConfig mCodecConfigSbc;
-    private BluetoothCodecConfig mCodecConfigAac;
-    private BluetoothCodecConfig mCodecConfigAptx;
-    private BluetoothCodecConfig mCodecConfigAptxHd;
-    private BluetoothCodecConfig mCodecConfigLdac;
     private BluetoothDevice mTestDevice;
+    private A2dpCodecConfig mA2dpCodecConfig;
 
+    @Mock private Context mMockContext;
+    @Mock private Resources mMockResources;
     @Mock private A2dpNativeInterface mA2dpNativeInterface;
 
-    static final int SBC_PRIORITY_DEFAULT = 1001;
-    static final int AAC_PRIORITY_DEFAULT = 2001;
-    static final int APTX_PRIORITY_DEFAULT = 3001;
-    static final int APTX_HD_PRIORITY_DEFAULT = 4001;
-    static final int LDAC_PRIORITY_DEFAULT = 5001;
-    static final int PRIORITY_HIGH = 1000000;
+    private static final int[] sOptionalCodecTypes = new int[] {
+            BluetoothCodecConfig.SOURCE_CODEC_TYPE_AAC,
+            BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX,
+            BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX_HD,
+            BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC
+    };
 
+    // Not use the default value to make sure it reads from config
+    private static final int SBC_PRIORITY_DEFAULT = 1001;
+    private static final int AAC_PRIORITY_DEFAULT = 3001;
+    private static final int APTX_PRIORITY_DEFAULT = 5001;
+    private static final int APTX_HD_PRIORITY_DEFAULT = 7001;
+    private static final int LDAC_PRIORITY_DEFAULT = 9001;
+    private static final int PRIORITY_HIGH = 1000000;
+
+    private static final BluetoothCodecConfig[] sCodecCapabilities = new BluetoothCodecConfig[] {
+            new BluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC,
+                                     SBC_PRIORITY_DEFAULT,
+                                     BluetoothCodecConfig.SAMPLE_RATE_44100,
+                                     BluetoothCodecConfig.BITS_PER_SAMPLE_16,
+                                     BluetoothCodecConfig.CHANNEL_MODE_MONO
+                                     | BluetoothCodecConfig.CHANNEL_MODE_STEREO,
+                                     0, 0, 0, 0),       // Codec-specific fields
+            new BluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_AAC,
+                                     AAC_PRIORITY_DEFAULT,
+                                     BluetoothCodecConfig.SAMPLE_RATE_44100,
+                                     BluetoothCodecConfig.BITS_PER_SAMPLE_16,
+                                     BluetoothCodecConfig.CHANNEL_MODE_STEREO,
+                                     0, 0, 0, 0),       // Codec-specific fields
+            new BluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX,
+                                     APTX_PRIORITY_DEFAULT,
+                                     BluetoothCodecConfig.SAMPLE_RATE_44100
+                                     | BluetoothCodecConfig.SAMPLE_RATE_48000,
+                                     BluetoothCodecConfig.BITS_PER_SAMPLE_16,
+                                     BluetoothCodecConfig.CHANNEL_MODE_STEREO,
+                                     0, 0, 0, 0),       // Codec-specific fields
+            new BluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX_HD,
+                                     APTX_HD_PRIORITY_DEFAULT,
+                                     BluetoothCodecConfig.SAMPLE_RATE_44100
+                                     | BluetoothCodecConfig.SAMPLE_RATE_48000,
+                                     BluetoothCodecConfig.BITS_PER_SAMPLE_24,
+                                     BluetoothCodecConfig.CHANNEL_MODE_STEREO,
+                                     0, 0, 0, 0),       // Codec-specific fields
+            new BluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC,
+                                     LDAC_PRIORITY_DEFAULT,
+                                     BluetoothCodecConfig.SAMPLE_RATE_44100
+                                     | BluetoothCodecConfig.SAMPLE_RATE_48000
+                                     | BluetoothCodecConfig.SAMPLE_RATE_88200
+                                     | BluetoothCodecConfig.SAMPLE_RATE_96000,
+                                     BluetoothCodecConfig.BITS_PER_SAMPLE_16
+                                     | BluetoothCodecConfig.BITS_PER_SAMPLE_24
+                                     | BluetoothCodecConfig.BITS_PER_SAMPLE_32,
+                                     BluetoothCodecConfig.CHANNEL_MODE_STEREO,
+                                     0, 0, 0, 0)        // Codec-specific fields
+    };
+
+    private static final BluetoothCodecConfig[] sDefaultCodecConfigs = new BluetoothCodecConfig[] {
+            new BluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC,
+                                     SBC_PRIORITY_DEFAULT,
+                                     BluetoothCodecConfig.SAMPLE_RATE_44100,
+                                     BluetoothCodecConfig.BITS_PER_SAMPLE_16,
+                                     BluetoothCodecConfig.CHANNEL_MODE_STEREO,
+                                     0, 0, 0, 0),       // Codec-specific fields
+            new BluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_AAC,
+                                     AAC_PRIORITY_DEFAULT,
+                                     BluetoothCodecConfig.SAMPLE_RATE_44100,
+                                     BluetoothCodecConfig.BITS_PER_SAMPLE_16,
+                                     BluetoothCodecConfig.CHANNEL_MODE_STEREO,
+                                     0, 0, 0, 0),       // Codec-specific fields
+            new BluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX,
+                                     APTX_PRIORITY_DEFAULT,
+                                     BluetoothCodecConfig.SAMPLE_RATE_48000,
+                                     BluetoothCodecConfig.BITS_PER_SAMPLE_16,
+                                     BluetoothCodecConfig.CHANNEL_MODE_STEREO,
+                                     0, 0, 0, 0),       // Codec-specific fields
+            new BluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX_HD,
+                                     APTX_HD_PRIORITY_DEFAULT,
+                                     BluetoothCodecConfig.SAMPLE_RATE_48000,
+                                     BluetoothCodecConfig.BITS_PER_SAMPLE_24,
+                                     BluetoothCodecConfig.CHANNEL_MODE_STEREO,
+                                     0, 0, 0, 0),       // Codec-specific fields
+            new BluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC,
+                                     LDAC_PRIORITY_DEFAULT,
+                                     BluetoothCodecConfig.SAMPLE_RATE_96000,
+                                     BluetoothCodecConfig.BITS_PER_SAMPLE_32,
+                                     BluetoothCodecConfig.CHANNEL_MODE_STEREO,
+                                     0, 0, 0, 0)        // Codec-specific fields
+    };
 
     @Before
     public void setUp() throws Exception {
-        mTargetContext = InstrumentationRegistry.getTargetContext();
         // Set up mocks and test assets
         MockitoAnnotations.initMocks(this);
+        mTargetContext = InstrumentationRegistry.getTargetContext();
 
-        mA2dpCodecConfig = new A2dpCodecConfig(mTargetContext, mA2dpNativeInterface);
+        when(mMockContext.getResources()).thenReturn(mMockResources);
+        when(mMockResources.getInteger(R.integer.a2dp_source_codec_priority_sbc))
+                .thenReturn(SBC_PRIORITY_DEFAULT);
+        when(mMockResources.getInteger(R.integer.a2dp_source_codec_priority_aac))
+                .thenReturn(AAC_PRIORITY_DEFAULT);
+        when(mMockResources.getInteger(R.integer.a2dp_source_codec_priority_aptx))
+                .thenReturn(APTX_PRIORITY_DEFAULT);
+        when(mMockResources.getInteger(R.integer.a2dp_source_codec_priority_aptx_hd))
+                .thenReturn(APTX_HD_PRIORITY_DEFAULT);
+        when(mMockResources.getInteger(R.integer.a2dp_source_codec_priority_ldac))
+                .thenReturn(LDAC_PRIORITY_DEFAULT);
+
+        mA2dpCodecConfig = new A2dpCodecConfig(mMockContext, mA2dpNativeInterface);
         mTestDevice = BluetoothAdapter.getDefaultAdapter().getRemoteDevice("00:01:02:03:04:05");
 
         doReturn(true).when(mA2dpNativeInterface).setCodecConfigPreference(
                 any(BluetoothDevice.class),
                 any(BluetoothCodecConfig[].class));
-
-        // Create sample codec configs
-        mCodecConfigSbc = new BluetoothCodecConfig(
-            BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC,
-            SBC_PRIORITY_DEFAULT,
-            BluetoothCodecConfig.SAMPLE_RATE_44100,
-            BluetoothCodecConfig.BITS_PER_SAMPLE_16,
-            BluetoothCodecConfig.CHANNEL_MODE_STEREO,
-            0, 0, 0, 0);       // Codec-specific fields
-        mCodecConfigAac = new BluetoothCodecConfig(
-            BluetoothCodecConfig.SOURCE_CODEC_TYPE_AAC,
-            AAC_PRIORITY_DEFAULT,
-            BluetoothCodecConfig.SAMPLE_RATE_44100,
-            BluetoothCodecConfig.BITS_PER_SAMPLE_16,
-            BluetoothCodecConfig.CHANNEL_MODE_STEREO,
-            0, 0, 0, 0);       // Codec-specific fields
-        mCodecConfigAptx = new BluetoothCodecConfig(
-            BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX,
-            APTX_PRIORITY_DEFAULT,
-            BluetoothCodecConfig.SAMPLE_RATE_44100,
-            BluetoothCodecConfig.BITS_PER_SAMPLE_16,
-            BluetoothCodecConfig.CHANNEL_MODE_STEREO,
-            0, 0, 0, 0);       // Codec-specific fields
-        mCodecConfigAptxHd = new BluetoothCodecConfig(
-            BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX_HD,
-            APTX_HD_PRIORITY_DEFAULT,
-            BluetoothCodecConfig.SAMPLE_RATE_44100,
-            BluetoothCodecConfig.BITS_PER_SAMPLE_16,
-            BluetoothCodecConfig.CHANNEL_MODE_STEREO,
-            0, 0, 0, 0);       // Codec-specific fields
-        mCodecConfigLdac = new BluetoothCodecConfig(
-            BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC,
-            LDAC_PRIORITY_DEFAULT,
-            BluetoothCodecConfig.SAMPLE_RATE_44100,
-            BluetoothCodecConfig.BITS_PER_SAMPLE_16,
-            BluetoothCodecConfig.CHANNEL_MODE_STEREO,
-            0, 0, 0, 0);       // Codec-specific fields
-
     }
 
     @After
@@ -139,128 +193,383 @@
         }
     }
 
+    /**
+     * Test that we can fallback to default codec by lower the codec priority we changed before.
+     */
     @Test
     public void testSetCodecPreference_priorityHighToDefault() {
-        testSetCodecPreference_codecPriorityChangeCase(
+        testCodecPriorityChangeHelper(
                 BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC, SBC_PRIORITY_DEFAULT,
                 BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC, PRIORITY_HIGH,
                 true);
-        testSetCodecPreference_codecPriorityChangeCase(
+        testCodecPriorityChangeHelper(
                 BluetoothCodecConfig.SOURCE_CODEC_TYPE_AAC, AAC_PRIORITY_DEFAULT,
                 BluetoothCodecConfig.SOURCE_CODEC_TYPE_AAC, PRIORITY_HIGH,
                 true);
-        testSetCodecPreference_codecPriorityChangeCase(
+        testCodecPriorityChangeHelper(
                 BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX, APTX_PRIORITY_DEFAULT,
                 BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX, PRIORITY_HIGH,
                 true);
-        testSetCodecPreference_codecPriorityChangeCase(
+        testCodecPriorityChangeHelper(
                 BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX_HD, APTX_HD_PRIORITY_DEFAULT,
                 BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX_HD, PRIORITY_HIGH,
                 true);
-        testSetCodecPreference_codecPriorityChangeCase(
+        testCodecPriorityChangeHelper(
                 BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC, LDAC_PRIORITY_DEFAULT,
                 BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC, PRIORITY_HIGH,
                 false);
     }
 
+    /**
+     * Test that we can change the default codec to another by raising the codec priority.
+     * LDAC is the default highest codec, so no need to test others.
+     */
     @Test
-    public void testSetCodecPreference_priorityDefaultToHigh() {
-        testSetCodecPreference_codecPriorityChangeCase(
+    public void testSetCodecPreference_priorityDefaultToRaiseHigh() {
+        testCodecPriorityChangeHelper(
                 BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC, PRIORITY_HIGH,
                 BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC, LDAC_PRIORITY_DEFAULT,
                 true);
-        testSetCodecPreference_codecPriorityChangeCase(
+        testCodecPriorityChangeHelper(
                 BluetoothCodecConfig.SOURCE_CODEC_TYPE_AAC, PRIORITY_HIGH,
                 BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC, LDAC_PRIORITY_DEFAULT,
                 true);
-        testSetCodecPreference_codecPriorityChangeCase(
+        testCodecPriorityChangeHelper(
                 BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX, PRIORITY_HIGH,
                 BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC, LDAC_PRIORITY_DEFAULT,
                 true);
-        testSetCodecPreference_codecPriorityChangeCase(
+        testCodecPriorityChangeHelper(
                 BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX_HD, PRIORITY_HIGH,
                 BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC, LDAC_PRIORITY_DEFAULT,
                 true);
-        testSetCodecPreference_codecPriorityChangeCase(
+        testCodecPriorityChangeHelper(
                 BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC, PRIORITY_HIGH,
                 BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC, LDAC_PRIORITY_DEFAULT,
                 false);
     }
 
     @Test
-    public void testSetCodecPreference_priorityHighToHigh() {
-        testSetCodecPreference_codecPriorityChangeCase(
+    public void testSetCodecPreference_prioritySbcHighToOthersHigh() {
+        testCodecPriorityChangeHelper(
                 BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC, PRIORITY_HIGH,
                 BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC, PRIORITY_HIGH,
                 false);
-        testSetCodecPreference_codecPriorityChangeCase(
+        testCodecPriorityChangeHelper(
                 BluetoothCodecConfig.SOURCE_CODEC_TYPE_AAC, PRIORITY_HIGH,
                 BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC, PRIORITY_HIGH,
                 true);
-        testSetCodecPreference_codecPriorityChangeCase(
+        testCodecPriorityChangeHelper(
                 BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX, PRIORITY_HIGH,
                 BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC, PRIORITY_HIGH,
                 true);
-        testSetCodecPreference_codecPriorityChangeCase(
+        testCodecPriorityChangeHelper(
                 BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX_HD, PRIORITY_HIGH,
                 BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC, PRIORITY_HIGH,
                 true);
-        testSetCodecPreference_codecPriorityChangeCase(
+        testCodecPriorityChangeHelper(
                 BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC, PRIORITY_HIGH,
                 BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC, PRIORITY_HIGH,
                 true);
     }
 
     @Test
-    public void testSetCodecPreference_parametersChange() {
-        int unSupportedParameter = 200;
+    public void testSetCodecPreference_priorityAacHighToOthersHigh() {
+        testCodecPriorityChangeHelper(
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC, PRIORITY_HIGH,
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_AAC, PRIORITY_HIGH,
+                true);
+        testCodecPriorityChangeHelper(
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_AAC, PRIORITY_HIGH,
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_AAC, PRIORITY_HIGH,
+                false);
+        testCodecPriorityChangeHelper(
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX, PRIORITY_HIGH,
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_AAC, PRIORITY_HIGH,
+                true);
+        testCodecPriorityChangeHelper(
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX_HD, PRIORITY_HIGH,
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_AAC, PRIORITY_HIGH,
+                true);
+        testCodecPriorityChangeHelper(
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC, PRIORITY_HIGH,
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_AAC, PRIORITY_HIGH,
+                true);
+    }
 
-        testSetCodecPreference_parametersChangeCase(
+    @Test
+    public void testSetCodecPreference_parametersChangedInSameCodec() {
+        // The SBC default / preferred config is Stereo
+        testCodecParametersChangeHelper(
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC,
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC,
                 BluetoothCodecConfig.SAMPLE_RATE_44100,
                 BluetoothCodecConfig.BITS_PER_SAMPLE_16,
+                BluetoothCodecConfig.CHANNEL_MODE_STEREO,
                 false);
-        testSetCodecPreference_parametersChangeCase(
+        // SBC Mono is mandatory
+        testCodecParametersChangeHelper(
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC,
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC,
                 BluetoothCodecConfig.SAMPLE_RATE_44100,
-                BluetoothCodecConfig.BITS_PER_SAMPLE_24,
+                BluetoothCodecConfig.BITS_PER_SAMPLE_16,
+                BluetoothCodecConfig.CHANNEL_MODE_MONO,
                 true);
-        testSetCodecPreference_parametersChangeCase(
-                BluetoothCodecConfig.SAMPLE_RATE_44100,
-                unSupportedParameter,
+
+        // The LDAC default / preferred config within mDefaultCodecConfigs
+        testCodecParametersChangeHelper(
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC,
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC,
+                BluetoothCodecConfig.SAMPLE_RATE_96000,
+                BluetoothCodecConfig.BITS_PER_SAMPLE_32,
+                BluetoothCodecConfig.CHANNEL_MODE_STEREO,
                 false);
 
-        testSetCodecPreference_parametersChangeCase(
-                BluetoothCodecConfig.SAMPLE_RATE_48000,
+        testCodecParametersChangeHelper(
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC,
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC,
+                BluetoothCodecConfig.SAMPLE_RATE_44100,
+                BluetoothCodecConfig.BITS_PER_SAMPLE_32,
+                BluetoothCodecConfig.CHANNEL_MODE_STEREO,
+                true);
+        testCodecParametersChangeHelper(
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC,
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC,
+                BluetoothCodecConfig.SAMPLE_RATE_96000,
                 BluetoothCodecConfig.BITS_PER_SAMPLE_16,
+                BluetoothCodecConfig.CHANNEL_MODE_STEREO,
                 true);
-        testSetCodecPreference_parametersChangeCase(
-                BluetoothCodecConfig.SAMPLE_RATE_48000,
-                BluetoothCodecConfig.BITS_PER_SAMPLE_24,
+        testCodecParametersChangeHelper(
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC,
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC,
+                BluetoothCodecConfig.SAMPLE_RATE_44100,
+                BluetoothCodecConfig.BITS_PER_SAMPLE_16,
+                BluetoothCodecConfig.CHANNEL_MODE_STEREO,
                 true);
-        testSetCodecPreference_parametersChangeCase(
-                BluetoothCodecConfig.SAMPLE_RATE_48000,
-                unSupportedParameter,
+
+        // None for system default (Developer options)
+        testCodecParametersChangeHelper(
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC,
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC,
+                BluetoothCodecConfig.SAMPLE_RATE_NONE,
+                BluetoothCodecConfig.BITS_PER_SAMPLE_32,
+                BluetoothCodecConfig.CHANNEL_MODE_STEREO,
+                false);
+        testCodecParametersChangeHelper(
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC,
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC,
+                BluetoothCodecConfig.SAMPLE_RATE_96000,
+                BluetoothCodecConfig.BITS_PER_SAMPLE_NONE,
+                BluetoothCodecConfig.CHANNEL_MODE_STEREO,
+                false);
+        testCodecParametersChangeHelper(
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC,
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC,
+                BluetoothCodecConfig.SAMPLE_RATE_96000,
+                BluetoothCodecConfig.BITS_PER_SAMPLE_32,
+                BluetoothCodecConfig.CHANNEL_MODE_NONE,
                 false);
 
-        testSetCodecPreference_parametersChangeCase(
-                unSupportedParameter,
+        int unsupportedParameter = 0xc0;
+        testCodecParametersChangeHelper(
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC,
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC,
+                unsupportedParameter,
+                BluetoothCodecConfig.BITS_PER_SAMPLE_32,
+                BluetoothCodecConfig.CHANNEL_MODE_STEREO,
+                false);
+        testCodecParametersChangeHelper(
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC,
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC,
+                BluetoothCodecConfig.SAMPLE_RATE_96000,
+                unsupportedParameter,
+                BluetoothCodecConfig.CHANNEL_MODE_STEREO,
+                false);
+        testCodecParametersChangeHelper(
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC,
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC,
+                BluetoothCodecConfig.SAMPLE_RATE_96000,
+                BluetoothCodecConfig.BITS_PER_SAMPLE_32,
+                unsupportedParameter,
+                false);
+
+        int multipleSupportedParameters = 0x03;
+        testCodecParametersChangeHelper(
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC,
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC,
+                multipleSupportedParameters,
+                BluetoothCodecConfig.BITS_PER_SAMPLE_32,
+                BluetoothCodecConfig.CHANNEL_MODE_STEREO,
+                false);
+        testCodecParametersChangeHelper(
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC,
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC,
+                BluetoothCodecConfig.SAMPLE_RATE_96000,
+                multipleSupportedParameters,
+                BluetoothCodecConfig.CHANNEL_MODE_STEREO,
+                false);
+        testCodecParametersChangeHelper(
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC,
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC,
+                BluetoothCodecConfig.SAMPLE_RATE_96000,
+                BluetoothCodecConfig.BITS_PER_SAMPLE_32,
+                multipleSupportedParameters,
+                false);
+    }
+
+    @Test
+    public void testSetCodecPreference_parametersChangedToAnotherCodec() {
+        // different sample rate (44.1 kHz -> 96 kHz)
+        testCodecParametersChangeHelper(
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC,
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_AAC,
+                BluetoothCodecConfig.SAMPLE_RATE_96000,
                 BluetoothCodecConfig.BITS_PER_SAMPLE_16,
+                BluetoothCodecConfig.CHANNEL_MODE_STEREO,
+                true);
+        // different bits per channel (16 bits -> 32 bits)
+        testCodecParametersChangeHelper(
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC,
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_AAC,
+                BluetoothCodecConfig.SAMPLE_RATE_44100,
+                BluetoothCodecConfig.BITS_PER_SAMPLE_32,
+                BluetoothCodecConfig.CHANNEL_MODE_STEREO,
+                true);
+        // change all PCM parameters
+        testCodecParametersChangeHelper(
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC,
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC,
+                BluetoothCodecConfig.SAMPLE_RATE_44100,
+                BluetoothCodecConfig.BITS_PER_SAMPLE_16,
+                BluetoothCodecConfig.CHANNEL_MODE_MONO,
+                true);
+
+        // None for system default (Developer options)
+        testCodecParametersChangeHelper(
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC,
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC,
+                BluetoothCodecConfig.SAMPLE_RATE_NONE,
+                BluetoothCodecConfig.BITS_PER_SAMPLE_16,
+                BluetoothCodecConfig.CHANNEL_MODE_STEREO,
+                true);
+        testCodecParametersChangeHelper(
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC,
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC,
+                BluetoothCodecConfig.SAMPLE_RATE_44100,
+                BluetoothCodecConfig.BITS_PER_SAMPLE_NONE,
+                BluetoothCodecConfig.CHANNEL_MODE_STEREO,
+                true);
+        testCodecParametersChangeHelper(
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC,
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC,
+                BluetoothCodecConfig.SAMPLE_RATE_44100,
+                BluetoothCodecConfig.BITS_PER_SAMPLE_16,
+                BluetoothCodecConfig.CHANNEL_MODE_NONE,
+                true);
+        testCodecParametersChangeHelper(
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC,
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC,
+                BluetoothCodecConfig.SAMPLE_RATE_NONE,
+                BluetoothCodecConfig.BITS_PER_SAMPLE_NONE,
+                BluetoothCodecConfig.CHANNEL_MODE_NONE,
+                true);
+
+        int unsupportedParameter = 0xc0;
+        testCodecParametersChangeHelper(
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC,
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_AAC,
+                unsupportedParameter,
+                BluetoothCodecConfig.BITS_PER_SAMPLE_16,
+                BluetoothCodecConfig.CHANNEL_MODE_STEREO,
                 false);
-        testSetCodecPreference_parametersChangeCase(
-                unSupportedParameter,
-                BluetoothCodecConfig.BITS_PER_SAMPLE_24,
+        testCodecParametersChangeHelper(
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC,
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_AAC,
+                BluetoothCodecConfig.SAMPLE_RATE_44100,
+                unsupportedParameter,
+                BluetoothCodecConfig.CHANNEL_MODE_STEREO,
                 false);
-        testSetCodecPreference_parametersChangeCase(
-                unSupportedParameter,
-                unSupportedParameter,
+        testCodecParametersChangeHelper(
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC,
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_AAC,
+                BluetoothCodecConfig.SAMPLE_RATE_44100,
+                BluetoothCodecConfig.BITS_PER_SAMPLE_16,
+                unsupportedParameter,
+                false);
+
+        int multipleSupportedParameters = 0x03;
+        testCodecParametersChangeHelper(
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC,
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_AAC,
+                multipleSupportedParameters,
+                BluetoothCodecConfig.BITS_PER_SAMPLE_16,
+                BluetoothCodecConfig.CHANNEL_MODE_STEREO,
+                false);
+        testCodecParametersChangeHelper(
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC,
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_AAC,
+                BluetoothCodecConfig.SAMPLE_RATE_44100,
+                multipleSupportedParameters,
+                BluetoothCodecConfig.CHANNEL_MODE_STEREO,
+                false);
+        testCodecParametersChangeHelper(
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC,
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_AAC,
+                BluetoothCodecConfig.SAMPLE_RATE_44100,
+                BluetoothCodecConfig.BITS_PER_SAMPLE_16,
+                multipleSupportedParameters,
+                false);
+    }
+
+    @Test
+    public void testSetCodecPreference_ldacCodecSpecificFieldChanged() {
+        int ldacAudioQualityHigh = 1000;
+        int ldacAudioQualityABR = 1003;
+        int sbcCodecSpecificParameter = 0;
+        testCodecSpecificParametersChangeHelper(
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC,
+                ldacAudioQualityABR,
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC,
+                ldacAudioQualityABR,
+                false);
+        testCodecSpecificParametersChangeHelper(
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC,
+                ldacAudioQualityHigh,
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC,
+                ldacAudioQualityABR,
+                true);
+        testCodecSpecificParametersChangeHelper(
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC,
+                ldacAudioQualityABR,
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC,
+                sbcCodecSpecificParameter,
+                true);
+
+        // Only LDAC will check the codec specific1 field, but not SBC
+        testCodecSpecificParametersChangeHelper(
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC,
+                sbcCodecSpecificParameter,
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC,
+                ldacAudioQualityABR,
+                true);
+        testCodecSpecificParametersChangeHelper(
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC,
+                ldacAudioQualityABR,
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC,
+                ldacAudioQualityABR,
+                true);
+        testCodecSpecificParametersChangeHelper(
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC,
+                ldacAudioQualityHigh,
+                BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC,
+                sbcCodecSpecificParameter,
                 false);
     }
 
     @Test
     public void testDisableOptionalCodecs() {
-        int verifyCount = 0;
-        BluetoothCodecConfig[] codecConfigArray =
+        BluetoothCodecConfig[] codecConfigsArray =
                 new BluetoothCodecConfig[BluetoothCodecConfig.SOURCE_CODEC_TYPE_MAX];
-        codecConfigArray[0] = new BluetoothCodecConfig(
+        codecConfigsArray[0] = new BluetoothCodecConfig(
                 BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC,
                 BluetoothCodecConfig.CODEC_PRIORITY_HIGHEST,
                 BluetoothCodecConfig.SAMPLE_RATE_NONE,
@@ -268,32 +577,32 @@
                 BluetoothCodecConfig.CHANNEL_MODE_NONE,
                 0, 0, 0, 0);       // Codec-specific fields
 
-        // Test don't invoke to native when current codec is SBC
-        mA2dpCodecConfig.disableOptionalCodecs(mTestDevice, mCodecConfigSbc);
-        verify(mA2dpNativeInterface, times(verifyCount))
-                .setCodecConfigPreference(mTestDevice, codecConfigArray);
+        // shouldn't invoke to native when current codec is SBC
+        mA2dpCodecConfig.disableOptionalCodecs(
+                mTestDevice,
+                getDefaultCodecConfigByType(
+                        BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC,
+                        BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT));
+        verify(mA2dpNativeInterface, times(0)).setCodecConfigPreference(mTestDevice,
+                                                                        codecConfigsArray);
 
-        // Test invoke to native when current codec is not SBC
-        mA2dpCodecConfig.disableOptionalCodecs(mTestDevice, mCodecConfigAac);
-        verify(mA2dpNativeInterface, times(++verifyCount))
-                .setCodecConfigPreference(mTestDevice, codecConfigArray);
-        mA2dpCodecConfig.disableOptionalCodecs(mTestDevice, mCodecConfigAptx);
-        verify(mA2dpNativeInterface, times(++verifyCount))
-                .setCodecConfigPreference(mTestDevice, codecConfigArray);
-        mA2dpCodecConfig.disableOptionalCodecs(mTestDevice, mCodecConfigAptxHd);
-        verify(mA2dpNativeInterface, times(++verifyCount))
-                .setCodecConfigPreference(mTestDevice, codecConfigArray);
-        mA2dpCodecConfig.disableOptionalCodecs(mTestDevice, mCodecConfigLdac);
-        verify(mA2dpNativeInterface, times(++verifyCount))
-                .setCodecConfigPreference(mTestDevice, codecConfigArray);
+        // should invoke to native when current codec is an optional codec
+        int invokedCounter = 0;
+        for (int codecType : sOptionalCodecTypes) {
+            mA2dpCodecConfig.disableOptionalCodecs(
+                    mTestDevice,
+                    getDefaultCodecConfigByType(codecType,
+                                                BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT));
+            verify(mA2dpNativeInterface, times(++invokedCounter))
+                    .setCodecConfigPreference(mTestDevice, codecConfigsArray);
+        }
     }
 
     @Test
     public void testEnableOptionalCodecs() {
-        int verifyCount = 0;
-        BluetoothCodecConfig[] codecConfigArray =
+        BluetoothCodecConfig[] codecConfigsArray =
                 new BluetoothCodecConfig[BluetoothCodecConfig.SOURCE_CODEC_TYPE_MAX];
-        codecConfigArray[0] = new BluetoothCodecConfig(
+        codecConfigsArray[0] = new BluetoothCodecConfig(
                 BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC,
                 SBC_PRIORITY_DEFAULT,
                 BluetoothCodecConfig.SAMPLE_RATE_NONE,
@@ -301,115 +610,292 @@
                 BluetoothCodecConfig.CHANNEL_MODE_NONE,
                 0, 0, 0, 0);       // Codec-specific fields
 
-        // Test invoke to native when current codec is SBC
-        mA2dpCodecConfig.enableOptionalCodecs(mTestDevice, mCodecConfigSbc);
-        verify(mA2dpNativeInterface, times(++verifyCount))
-                .setCodecConfigPreference(mTestDevice, codecConfigArray);
+        // should invoke to native when current codec is SBC
+        mA2dpCodecConfig.enableOptionalCodecs(
+                mTestDevice,
+                getDefaultCodecConfigByType(BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC,
+                                            BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT));
+        verify(mA2dpNativeInterface, times(1))
+                .setCodecConfigPreference(mTestDevice, codecConfigsArray);
 
-        // Test don't invoke to native when current codec is not SBC
-        mA2dpCodecConfig.enableOptionalCodecs(mTestDevice, mCodecConfigAac);
-        verify(mA2dpNativeInterface, times(verifyCount))
-                .setCodecConfigPreference(mTestDevice, codecConfigArray);
-        mA2dpCodecConfig.enableOptionalCodecs(mTestDevice, mCodecConfigAptx);
-        verify(mA2dpNativeInterface, times(verifyCount))
-                .setCodecConfigPreference(mTestDevice, codecConfigArray);
-        mA2dpCodecConfig.enableOptionalCodecs(mTestDevice, mCodecConfigAptxHd);
-        verify(mA2dpNativeInterface, times(verifyCount))
-                .setCodecConfigPreference(mTestDevice, codecConfigArray);
-        mA2dpCodecConfig.enableOptionalCodecs(mTestDevice, mCodecConfigLdac);
-        verify(mA2dpNativeInterface, times(verifyCount))
-                .setCodecConfigPreference(mTestDevice, codecConfigArray);
+        // shouldn't invoke to native when current codec is already an optional
+        for (int codecType : sOptionalCodecTypes) {
+            mA2dpCodecConfig.enableOptionalCodecs(
+                    mTestDevice,
+                    getDefaultCodecConfigByType(codecType,
+                                                BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT));
+            verify(mA2dpNativeInterface, times(1))
+                    .setCodecConfigPreference(mTestDevice, codecConfigsArray);
+        }
     }
 
-    private void testSetCodecPreference_parametersChangeCase(int sampleRate, int bitPerSample,
-            boolean invokeNative) {
-        BluetoothCodecConfig[] invalidSelectableCodecs = new BluetoothCodecConfig[1];
-        invalidSelectableCodecs[0] = new BluetoothCodecConfig(
-                BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC,
-                LDAC_PRIORITY_DEFAULT,
-                (BluetoothCodecConfig.SAMPLE_RATE_44100 | BluetoothCodecConfig.SAMPLE_RATE_48000),
-                (BluetoothCodecConfig.BITS_PER_SAMPLE_16 | BluetoothCodecConfig.BITS_PER_SAMPLE_24),
-                BluetoothCodecConfig.CHANNEL_MODE_STEREO,
-                0, 0, 0, 0);       // Codec-specific fields
+    private BluetoothCodecConfig getDefaultCodecConfigByType(int codecType, int codecPriority) {
+        for (BluetoothCodecConfig codecConfig : sDefaultCodecConfigs) {
+            if (codecConfig.getCodecType() != codecType) {
+                continue;
+            }
+            return new BluetoothCodecConfig(
+                    codecConfig.getCodecType(),
+                    (codecPriority != BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT
+                     ? codecPriority : codecConfig.getCodecPriority()),
+                    codecConfig.getSampleRate(), codecConfig.getBitsPerSample(),
+                    codecConfig.getChannelMode(), codecConfig.getCodecSpecific1(),
+                    codecConfig.getCodecSpecific2(), codecConfig.getCodecSpecific3(),
+                    codecConfig.getCodecSpecific4());
+        }
+        Assert.fail("getDefaultCodecConfigByType: No such codecType=" + codecType
+                + " in sDefaultCodecConfigs");
+        return null;
+    }
+
+    private BluetoothCodecConfig getCodecCapabilitiesByType(int codecType) {
+        for (BluetoothCodecConfig codecCapabilities : sCodecCapabilities) {
+            if (codecCapabilities.getCodecType() != codecType) {
+                continue;
+            }
+            return new BluetoothCodecConfig(
+                    codecCapabilities.getCodecType(), codecCapabilities.getCodecPriority(),
+                    codecCapabilities.getSampleRate(), codecCapabilities.getBitsPerSample(),
+                    codecCapabilities.getChannelMode(), codecCapabilities.getCodecSpecific1(),
+                    codecCapabilities.getCodecSpecific2(), codecCapabilities.getCodecSpecific3(),
+                    codecCapabilities.getCodecSpecific4());
+        }
+        Assert.fail("getCodecCapabilitiesByType: No such codecType=" + codecType
+                + " in sCodecCapabilities");
+        return null;
+    }
+
+    private void testCodecParametersChangeHelper(int newCodecType, int oldCodecType,
+            int sampleRate, int bitsPerSample, int channelMode, boolean invokeNative) {
+        BluetoothCodecConfig oldCodecConfig =
+                getDefaultCodecConfigByType(oldCodecType, PRIORITY_HIGH);
+        BluetoothCodecConfig[] newCodecConfigsArray = new BluetoothCodecConfig[] {
+                new BluetoothCodecConfig(newCodecType,
+                                         PRIORITY_HIGH,
+                                         sampleRate, bitsPerSample, channelMode,
+                                         0, 0, 0, 0)       // Codec-specific fields
+        };
+
+        // Test cases: 1. no mandatory; 2. mandatory + old + new; 3. all codecs
+        BluetoothCodecConfig[] minimumCodecsArray;
+        if (!oldCodecConfig.isMandatoryCodec() && !newCodecConfigsArray[0].isMandatoryCodec()) {
+            BluetoothCodecConfig[] optionalCodecsArray;
+            if (oldCodecType != newCodecType) {
+                optionalCodecsArray = new BluetoothCodecConfig[] {
+                        getCodecCapabilitiesByType(oldCodecType),
+                        getCodecCapabilitiesByType(newCodecType)
+                };
+                minimumCodecsArray = new BluetoothCodecConfig[] {
+                        getCodecCapabilitiesByType(BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC),
+                        getCodecCapabilitiesByType(oldCodecType),
+                        getCodecCapabilitiesByType(newCodecType)
+                };
+            } else {
+                optionalCodecsArray = new BluetoothCodecConfig[]
+                        {getCodecCapabilitiesByType(oldCodecType)};
+                minimumCodecsArray = new BluetoothCodecConfig[] {
+                        getCodecCapabilitiesByType(BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC),
+                        getCodecCapabilitiesByType(oldCodecType)
+                };
+            }
+            BluetoothCodecStatus codecStatus = new BluetoothCodecStatus(oldCodecConfig,
+                                                                        sCodecCapabilities,
+                                                                        optionalCodecsArray);
+            mA2dpCodecConfig.setCodecConfigPreference(mTestDevice,
+                                                      codecStatus,
+                                                      newCodecConfigsArray[0]);
+            // no mandatory codec in selectable, and should not apply
+            verify(mA2dpNativeInterface, times(0)).setCodecConfigPreference(mTestDevice,
+                                                                            newCodecConfigsArray);
 
 
-        BluetoothCodecConfig[] selectableCodecs = new BluetoothCodecConfig[2];
-        selectableCodecs[0] = mCodecConfigSbc;
-        selectableCodecs[1] = new BluetoothCodecConfig(
-                BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC,
-                LDAC_PRIORITY_DEFAULT,
-                (BluetoothCodecConfig.SAMPLE_RATE_44100 | BluetoothCodecConfig.SAMPLE_RATE_48000),
-                (BluetoothCodecConfig.BITS_PER_SAMPLE_16 | BluetoothCodecConfig.BITS_PER_SAMPLE_24),
-                BluetoothCodecConfig.CHANNEL_MODE_STEREO,
-                0, 0, 0, 0);       // Codec-specific fields
+        } else {
+            if (oldCodecType != newCodecType) {
+                minimumCodecsArray = new BluetoothCodecConfig[] {
+                        getCodecCapabilitiesByType(oldCodecType),
+                        getCodecCapabilitiesByType(newCodecType)
+                };
+            } else {
+                minimumCodecsArray = new BluetoothCodecConfig[] {
+                        getCodecCapabilitiesByType(oldCodecType),
+                };
+            }
+        }
 
-        BluetoothCodecConfig[] codecConfigArray = new BluetoothCodecConfig[1];
-        codecConfigArray[0] = new BluetoothCodecConfig(
-                BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC,
-                LDAC_PRIORITY_DEFAULT,
-                sampleRate,
-                bitPerSample,
-                BluetoothCodecConfig.CHANNEL_MODE_STEREO,
-                0, 0, 0, 0);       // Codec-specific fields
-
-        BluetoothCodecStatus codecStatus = new BluetoothCodecStatus(mCodecConfigLdac,
-                                                                    invalidSelectableCodecs,
-                                                                    invalidSelectableCodecs);
-        mA2dpCodecConfig.setCodecConfigPreference(mTestDevice, codecStatus, codecConfigArray[0]);
-
-        codecStatus = new BluetoothCodecStatus(mCodecConfigLdac,
-                                               selectableCodecs,
-                                               selectableCodecs);
-
-        mA2dpCodecConfig.setCodecConfigPreference(mTestDevice, codecStatus, codecConfigArray[0]);
+        // 2. mandatory + old + new codecs only
+        BluetoothCodecStatus codecStatus =
+                new BluetoothCodecStatus(oldCodecConfig, sCodecCapabilities, minimumCodecsArray);
+        mA2dpCodecConfig.setCodecConfigPreference(mTestDevice,
+                                                  codecStatus,
+                                                  newCodecConfigsArray[0]);
         verify(mA2dpNativeInterface, times(invokeNative ? 1 : 0))
-                .setCodecConfigPreference(mTestDevice, codecConfigArray);
+                .setCodecConfigPreference(mTestDevice, newCodecConfigsArray);
 
-    }
-
-    private void testSetCodecPreference_codecPriorityChangeCase(int newCodecType,
-            int newCodecPriority, int oldCodecType, int oldCodecPriority, boolean invokeNative) {
-        BluetoothCodecConfig[] selectableCodecs = new BluetoothCodecConfig[5];
-        selectableCodecs[0] = mCodecConfigSbc;
-        selectableCodecs[1] = mCodecConfigAac;
-        selectableCodecs[2] = mCodecConfigAptx;
-        selectableCodecs[3] = mCodecConfigAptxHd;
-        selectableCodecs[4] = mCodecConfigLdac;
-
-        BluetoothCodecConfig[] invalidSelectableCodecs = new BluetoothCodecConfig[4];
-        invalidSelectableCodecs[0] = mCodecConfigAac;
-        invalidSelectableCodecs[1] = mCodecConfigAptx;
-        invalidSelectableCodecs[2] = mCodecConfigAptxHd;
-        invalidSelectableCodecs[3] = mCodecConfigLdac;
-
-        BluetoothCodecConfig oldCodecConfig =  new BluetoothCodecConfig(
-                oldCodecType,
-                oldCodecPriority,
-                BluetoothCodecConfig.SAMPLE_RATE_44100,
-                BluetoothCodecConfig.BITS_PER_SAMPLE_16,
-                BluetoothCodecConfig.CHANNEL_MODE_STEREO,
-                0, 0, 0, 0);       // Codec-specific fields
-
-        BluetoothCodecConfig[] codecConfigArray = new BluetoothCodecConfig[1];
-        codecConfigArray[0] = new BluetoothCodecConfig(
-            newCodecType,
-            newCodecPriority,
-            BluetoothCodecConfig.SAMPLE_RATE_44100,
-            BluetoothCodecConfig.BITS_PER_SAMPLE_16,
-            BluetoothCodecConfig.CHANNEL_MODE_STEREO,
-            0, 0, 0, 0);       // Codec-specific fields
-
-        BluetoothCodecStatus codecStatus = new BluetoothCodecStatus(oldCodecConfig,
-                                                                    invalidSelectableCodecs,
-                                                                    invalidSelectableCodecs);
-        mA2dpCodecConfig.setCodecConfigPreference(mTestDevice, codecStatus, codecConfigArray[0]);
-
+        // 3. all codecs were selectable
         codecStatus = new BluetoothCodecStatus(oldCodecConfig,
-                                               selectableCodecs,
-                                               selectableCodecs);
+                                               sCodecCapabilities,
+                                               sCodecCapabilities);
+        mA2dpCodecConfig.setCodecConfigPreference(mTestDevice,
+                                                  codecStatus,
+                                                  newCodecConfigsArray[0]);
+        verify(mA2dpNativeInterface, times(invokeNative ? 2 : 0))
+                .setCodecConfigPreference(mTestDevice, newCodecConfigsArray);
+    }
 
-        mA2dpCodecConfig.setCodecConfigPreference(mTestDevice, codecStatus, codecConfigArray[0]);
+    private void testCodecSpecificParametersChangeHelper(int newCodecType, int newCodecSpecific,
+            int oldCodecType, int oldCodecSpecific, boolean invokeNative) {
+        BluetoothCodecConfig codecDefaultTemp =
+                getDefaultCodecConfigByType(oldCodecType, PRIORITY_HIGH);
+        BluetoothCodecConfig oldCodecConfig =
+                new BluetoothCodecConfig(codecDefaultTemp.getCodecType(),
+                                         codecDefaultTemp.getCodecPriority(),
+                                         codecDefaultTemp.getSampleRate(),
+                                         codecDefaultTemp.getBitsPerSample(),
+                                         codecDefaultTemp.getChannelMode(),
+                                         oldCodecSpecific, 0, 0, 0);       // Codec-specific fields
+        codecDefaultTemp = getDefaultCodecConfigByType(newCodecType, PRIORITY_HIGH);
+        BluetoothCodecConfig[] newCodecConfigsArray = new BluetoothCodecConfig[] {
+                new BluetoothCodecConfig(codecDefaultTemp.getCodecType(),
+                                         codecDefaultTemp.getCodecPriority(),
+                                         codecDefaultTemp.getSampleRate(),
+                                         codecDefaultTemp.getBitsPerSample(),
+                                         codecDefaultTemp.getChannelMode(),
+                                         newCodecSpecific, 0, 0, 0)       // Codec-specific fields
+        };
+        BluetoothCodecStatus codecStatus = new BluetoothCodecStatus(oldCodecConfig,
+                                                                    sCodecCapabilities,
+                                                                    sCodecCapabilities);
+        mA2dpCodecConfig.setCodecConfigPreference(mTestDevice,
+                                                  codecStatus,
+                                                  newCodecConfigsArray[0]);
         verify(mA2dpNativeInterface, times(invokeNative ? 1 : 0))
-                .setCodecConfigPreference(mTestDevice, codecConfigArray);
+                .setCodecConfigPreference(mTestDevice, newCodecConfigsArray);
+    }
+
+    private void testCodecPriorityChangeHelper(int newCodecType, int newCodecPriority,
+            int oldCodecType, int oldCodecPriority, boolean shouldApplyWhenAllSelectable) {
+
+        BluetoothCodecConfig[] newCodecConfigsArray =
+                new BluetoothCodecConfig[] {
+                        getDefaultCodecConfigByType(newCodecType, newCodecPriority)
+                };
+        BluetoothCodecConfig oldCodecConfig = getDefaultCodecConfigByType(oldCodecType,
+                                                                          oldCodecPriority);
+
+        // Test cases: 1. no mandatory; 2. no new codec; 3. mandatory + old + new; 4. all codecs
+        BluetoothCodecConfig[] minimumCodecsArray;
+        boolean isMinimumCodecsArraySelectable;
+        if (!oldCodecConfig.isMandatoryCodec()) {
+            if (oldCodecType == newCodecType || newCodecConfigsArray[0].isMandatoryCodec()) {
+                // selectable: {-mandatory, +oldCodec = newCodec}, or
+                // selectable: {-mandatory = newCodec, +oldCodec}. Not applied
+                BluetoothCodecConfig[] poorCodecsArray = new BluetoothCodecConfig[]
+                        {getCodecCapabilitiesByType(oldCodecType)};
+                BluetoothCodecStatus codecStatus = new BluetoothCodecStatus(oldCodecConfig,
+                                                                            sCodecCapabilities,
+                                                                            poorCodecsArray);
+                mA2dpCodecConfig.setCodecConfigPreference(mTestDevice,
+                                                          codecStatus,
+                                                          newCodecConfigsArray[0]);
+                verify(mA2dpNativeInterface, times(0))
+                        .setCodecConfigPreference(mTestDevice, newCodecConfigsArray);
+
+                // selectable: {+mandatory, +oldCodec = newCodec}, or
+                // selectable: {+mandatory = newCodec, +oldCodec}.
+                minimumCodecsArray = new BluetoothCodecConfig[] {
+                        getCodecCapabilitiesByType(BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC),
+                        getCodecCapabilitiesByType(oldCodecType)
+                };
+            } else {
+                // selectable: {-mandatory, +oldCodec, +newCodec}. Not applied
+                BluetoothCodecConfig[] poorCodecsArray = new BluetoothCodecConfig[] {
+                        getCodecCapabilitiesByType(oldCodecType),
+                        getCodecCapabilitiesByType(newCodecType)
+                };
+                BluetoothCodecStatus codecStatus = new BluetoothCodecStatus(oldCodecConfig,
+                                                                            sCodecCapabilities,
+                                                                            poorCodecsArray);
+                mA2dpCodecConfig.setCodecConfigPreference(
+                        mTestDevice, codecStatus, newCodecConfigsArray[0]);
+                verify(mA2dpNativeInterface, times(0))
+                        .setCodecConfigPreference(mTestDevice, newCodecConfigsArray);
+
+                // selectable: {+mandatory, +oldCodec, -newCodec}. Not applied
+                poorCodecsArray = new BluetoothCodecConfig[] {
+                        getCodecCapabilitiesByType(BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC),
+                        getCodecCapabilitiesByType(oldCodecType)
+                };
+                codecStatus = new BluetoothCodecStatus(oldCodecConfig,
+                                                       sCodecCapabilities,
+                                                       poorCodecsArray);
+                mA2dpCodecConfig.setCodecConfigPreference(mTestDevice,
+                                                          codecStatus,
+                                                          newCodecConfigsArray[0]);
+                verify(mA2dpNativeInterface, times(0))
+                        .setCodecConfigPreference(mTestDevice, newCodecConfigsArray);
+
+                // selectable: {+mandatory, +oldCodec, +newCodec}.
+                minimumCodecsArray = new BluetoothCodecConfig[] {
+                      getCodecCapabilitiesByType(BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC),
+                      getCodecCapabilitiesByType(oldCodecType),
+                      getCodecCapabilitiesByType(newCodecType)
+                };
+            }
+            // oldCodec priority should be reset to default, so compare with the default
+            if (newCodecConfigsArray[0].getCodecPriority()
+                    > getDefaultCodecConfigByType(
+                            oldCodecType,
+                            BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT).getCodecPriority()
+                    && oldCodecType != newCodecType) {
+                isMinimumCodecsArraySelectable = true;
+            } else {
+                // the old codec was still the highest priority after reset to default
+                isMinimumCodecsArraySelectable = false;
+            }
+        } else if (oldCodecType != newCodecType) {
+            // selectable: {+mandatory = oldCodec, -newCodec}. Not applied
+            BluetoothCodecConfig[] poorCodecsArray = new BluetoothCodecConfig[]
+                    {getCodecCapabilitiesByType(oldCodecType)};
+            BluetoothCodecStatus codecStatus =
+                    new BluetoothCodecStatus(oldCodecConfig, sCodecCapabilities, poorCodecsArray);
+            mA2dpCodecConfig.setCodecConfigPreference(mTestDevice,
+                                                      codecStatus,
+                                                      newCodecConfigsArray[0]);
+            verify(mA2dpNativeInterface, times(0))
+                    .setCodecConfigPreference(mTestDevice, newCodecConfigsArray);
+
+            // selectable: {+mandatory = oldCodec, +newCodec}.
+            minimumCodecsArray = new BluetoothCodecConfig[] {
+                    getCodecCapabilitiesByType(oldCodecType),
+                    getCodecCapabilitiesByType(newCodecType)
+            };
+            isMinimumCodecsArraySelectable = true;
+        } else {
+            // selectable: {mandatory = oldCodec = newCodec}.
+            minimumCodecsArray = new BluetoothCodecConfig[]
+                    {getCodecCapabilitiesByType(BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC)};
+            isMinimumCodecsArraySelectable = false;
+        }
+
+        // 3. mandatory + old + new codecs only
+        int invokedCounter = (isMinimumCodecsArraySelectable ? 1 : 0);
+        BluetoothCodecStatus codecStatus =
+                new BluetoothCodecStatus(oldCodecConfig, sCodecCapabilities, minimumCodecsArray);
+        mA2dpCodecConfig.setCodecConfigPreference(mTestDevice,
+                                                  codecStatus,
+                                                  newCodecConfigsArray[0]);
+        verify(mA2dpNativeInterface, times(invokedCounter))
+                .setCodecConfigPreference(mTestDevice, newCodecConfigsArray);
+
+        // 4. all codecs were selectable
+        invokedCounter += (shouldApplyWhenAllSelectable ? 1 : 0);
+        codecStatus = new BluetoothCodecStatus(oldCodecConfig,
+                                               sCodecCapabilities,
+                                               sCodecCapabilities);
+        mA2dpCodecConfig.setCodecConfigPreference(mTestDevice,
+                                                  codecStatus,
+                                                  newCodecConfigsArray[0]);
+        verify(mA2dpNativeInterface, times(invokedCounter))
+                .setCodecConfigPreference(mTestDevice, newCodecConfigsArray);
     }
 }