BtHelper: clean HFP device connection

Do not report an error when disconnecting or connecting the same
HFP device twice a this causes a mismatch between BtHelper and
AudioDeviceInventory.
Optimize HFP device disconnection by only disconnecting the relevant
audio device type when known.
Also improve log for SCO audio state changes in dumpsys.
Also cleanup @GuardedBy annotations in BtHelper

Bug: 348265701
Test: make
Flag: EXEMPT bug fix.
Change-Id: Icfa9acd8497e087430442cf299ecb75d5c9b14d3
diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
index 8de1552..a147e37 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
@@ -1863,7 +1863,10 @@
                             synchronized (mBluetoothAudioStateLock) {
                                 reapplyAudioHalBluetoothState();
                             }
-                            mBtHelper.onAudioServerDiedRestoreA2dp();
+                            final int forceForMedia = getBluetoothA2dpEnabled()
+                                    ? AudioSystem.FORCE_NONE : AudioSystem.FORCE_NO_BT_A2DP;
+                            setForceUse_Async(
+                                    AudioSystem.FOR_MEDIA, forceForMedia, "MSG_RESTORE_DEVICES");
                             updateCommunicationRoute("MSG_RESTORE_DEVICES");
                         }
                     }
diff --git a/services/core/java/com/android/server/audio/AudioDeviceInventory.java b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
index 02aa6f5..ca69f31 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceInventory.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
@@ -1757,6 +1757,15 @@
             if (AudioService.DEBUG_DEVICES) {
                 Slog.i(TAG, "deviceInfo:" + di + " is(already)Connected:" + isConnected);
             }
+            // Do not report an error in case of redundant connect or disconnect request
+            // as this can cause a state mismatch between BtHelper and AudioDeviceInventory
+            if (connect == isConnected) {
+                Log.i(TAG, "handleDeviceConnection() deviceInfo=" + di + " is already "
+                        + (connect ? "" : "dis") + "connected");
+                mmi.set(MediaMetrics.Property.STATE, connect
+                        ? MediaMetrics.Value.CONNECT : MediaMetrics.Value.DISCONNECT).record();
+                return true;
+            }
             if (connect && !isConnected) {
                 final int res;
                 if (isForTesting) {
diff --git a/services/core/java/com/android/server/audio/BtHelper.java b/services/core/java/com/android/server/audio/BtHelper.java
index 57bffa7..ce92dfb 100644
--- a/services/core/java/com/android/server/audio/BtHelper.java
+++ b/services/core/java/com/android/server/audio/BtHelper.java
@@ -83,39 +83,53 @@
     }
 
     // BluetoothHeadset API to control SCO connection
+    @GuardedBy("BtHelper.this")
     private @Nullable BluetoothHeadset mBluetoothHeadset;
 
     // Bluetooth headset device
+    @GuardedBy("mDeviceBroker.mDeviceStateLock")
     private @Nullable BluetoothDevice mBluetoothHeadsetDevice;
+
+    @GuardedBy("mDeviceBroker.mDeviceStateLock")
     private final Map<BluetoothDevice, AudioDeviceAttributes> mResolvedScoAudioDevices =
             new HashMap<>();
 
+    @GuardedBy("BtHelper.this")
     private @Nullable BluetoothHearingAid mHearingAid = null;
 
+    @GuardedBy("BtHelper.this")
     private @Nullable BluetoothLeAudio mLeAudio = null;
 
+    @GuardedBy("BtHelper.this")
     private @Nullable BluetoothLeAudioCodecConfig mLeAudioCodecConfig;
 
     // Reference to BluetoothA2dp to query for AbsoluteVolume.
+    @GuardedBy("BtHelper.this")
     private @Nullable BluetoothA2dp mA2dp = null;
 
+    @GuardedBy("BtHelper.this")
     private @Nullable BluetoothCodecConfig mA2dpCodecConfig;
 
+    @GuardedBy("BtHelper.this")
     private @AudioSystem.AudioFormatNativeEnumForBtCodec
             int mLeAudioBroadcastCodec = AudioSystem.AUDIO_FORMAT_DEFAULT;
 
     // If absolute volume is supported in AVRCP device
+    @GuardedBy("mDeviceBroker.mDeviceStateLock")
     private boolean mAvrcpAbsVolSupported = false;
 
     // Current connection state indicated by bluetooth headset
+    @GuardedBy("mDeviceBroker.mDeviceStateLock")
     private int mScoConnectionState;
 
     // Indicate if SCO audio connection is currently active and if the initiator is
     // audio service (internal) or bluetooth headset (external)
+    @GuardedBy("mDeviceBroker.mDeviceStateLock")
     private int mScoAudioState;
 
     // Indicates the mode used for SCO audio connection. The mode is virtual call if the request
     // originated from an app targeting an API version before JB MR2 and raw audio after that.
+    @GuardedBy("mDeviceBroker.mDeviceStateLock")
     private int mScoAudioMode;
 
     // SCO audio state is not active
@@ -210,7 +224,7 @@
     //----------------------------------------------------------------------
     // Interface for AudioDeviceBroker
 
-    // Called locked by ADeviceBroker.mSetModeLock -> AudioDeviceBroker.mDeviceStateLock
+    @GuardedBy("mDeviceBroker.mDeviceStateLock")
     /*package*/ synchronized void onSystemReady() {
         mScoConnectionState = android.media.AudioManager.SCO_AUDIO_STATE_ERROR;
         resetBluetoothSco();
@@ -238,17 +252,13 @@
         }
     }
 
-    /*package*/ synchronized void onAudioServerDiedRestoreA2dp() {
-        final int forMed = mDeviceBroker.getBluetoothA2dpEnabled()
-                ? AudioSystem.FORCE_NONE : AudioSystem.FORCE_NO_BT_A2DP;
-        mDeviceBroker.setForceUse_Async(AudioSystem.FOR_MEDIA, forMed, "onAudioServerDied()");
-    }
-
-    /*package*/ synchronized void setAvrcpAbsoluteVolumeSupported(boolean supported) {
+    @GuardedBy("mDeviceBroker.mDeviceStateLock")
+    /*package*/ void setAvrcpAbsoluteVolumeSupported(boolean supported) {
         mAvrcpAbsVolSupported = supported;
         Log.i(TAG, "setAvrcpAbsoluteVolumeSupported supported=" + supported);
     }
 
+    @GuardedBy("mDeviceBroker.mDeviceStateLock")
     /*package*/ synchronized void setAvrcpAbsoluteVolumeIndex(int index) {
         if (mA2dp == null) {
             if (AudioService.DEBUG_VOL) {
@@ -371,7 +381,7 @@
         return codecAndChanged;
     }
 
-    // Called locked by ADeviceBroker.mSetModeLock -> AudioDeviceBroker.mDeviceStateLock
+    @GuardedBy("mDeviceBroker.mDeviceStateLock")
     /*package*/ synchronized void onReceiveBtEvent(Intent intent) {
         final String action = intent.getAction();
 
@@ -393,12 +403,12 @@
     }
 
     /**
-     * Exclusively called from AudioDeviceBroker (with mSetModeLock held)
+     * Exclusively called from AudioDeviceBroker (with mDeviceStateLock held)
      * when handling MSG_L_RECEIVED_BT_EVENT in {@link #onReceiveBtEvent(Intent)}
      * as part of the serialization of the communication route selection
      */
-    @GuardedBy("BtHelper.this")
-    private void onScoAudioStateChanged(int state) {
+    @GuardedBy("mDeviceBroker.mDeviceStateLock")
+    private synchronized void onScoAudioStateChanged(int state) {
         boolean broadcast = false;
         int scoAudioState = AudioManager.SCO_AUDIO_STATE_ERROR;
         Log.i(TAG, "onScoAudioStateChanged  state: " + state
@@ -414,12 +424,14 @@
                     broadcast = true;
                 }
                 if (!mDeviceBroker.isScoManagedByAudio()) {
-                    mDeviceBroker.setBluetoothScoOn(true, "BtHelper.onScoAudioStateChanged");
+                    mDeviceBroker.setBluetoothScoOn(
+                            true, "BtHelper.onScoAudioStateChanged, state: " + state);
                 }
                 break;
             case BluetoothHeadset.STATE_AUDIO_DISCONNECTED:
                 if (!mDeviceBroker.isScoManagedByAudio()) {
-                    mDeviceBroker.setBluetoothScoOn(false, "BtHelper.onScoAudioStateChanged");
+                    mDeviceBroker.setBluetoothScoOn(
+                            false, "BtHelper.onScoAudioStateChanged, state: " + state);
                 }
                 scoAudioState = AudioManager.SCO_AUDIO_STATE_DISCONNECTED;
                 // There are two cases where we want to immediately reconnect audio:
@@ -466,6 +478,7 @@
      *
      * @return false if SCO isn't connected
      */
+    @GuardedBy("mDeviceBroker.mDeviceStateLock")
     /*package*/ synchronized boolean isBluetoothScoOn() {
         if (mBluetoothHeadset == null || mBluetoothHeadsetDevice == null) {
             return false;
@@ -479,19 +492,20 @@
         return false;
     }
 
-    /*package*/ synchronized boolean isBluetoothScoRequestedInternally() {
+    @GuardedBy("mDeviceBroker.mDeviceStateLock")
+    /*package*/ boolean isBluetoothScoRequestedInternally() {
         return mScoAudioState == SCO_STATE_ACTIVE_INTERNAL
               || mScoAudioState == SCO_STATE_ACTIVATE_REQ;
     }
 
-    // Called locked by ADeviceBroker.mSetModeLock -> AudioDeviceBroker.mDeviceStateLock
+    @GuardedBy("mDeviceBroker.mDeviceStateLock")
     /*package*/ synchronized boolean startBluetoothSco(int scoAudioMode,
                 @NonNull String eventSource) {
         AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent(eventSource));
         return requestScoState(BluetoothHeadset.STATE_AUDIO_CONNECTED, scoAudioMode);
     }
 
-    // Called locked by ADeviceBroker.mSetModeLock -> AudioDeviceBroker.mDeviceStateLock
+    @GuardedBy("mDeviceBroker.mDeviceStateLock")
     /*package*/ synchronized boolean stopBluetoothSco(@NonNull String eventSource) {
         AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent(eventSource));
         return requestScoState(BluetoothHeadset.STATE_AUDIO_DISCONNECTED, SCO_MODE_VIRTUAL_CALL);
@@ -551,7 +565,8 @@
         }
     }
 
-    /*package*/ synchronized void onBroadcastScoConnectionState(int state) {
+    @GuardedBy("mDeviceBroker.mDeviceStateLock")
+    /*package*/ void onBroadcastScoConnectionState(int state) {
         if (state == mScoConnectionState) {
             return;
         }
@@ -563,8 +578,8 @@
         mScoConnectionState = state;
     }
 
-    // Called locked by ADeviceBroker.mSetModeLock -> AudioDeviceBroker.mDeviceStateLock
-    /*package*/ synchronized void resetBluetoothSco() {
+    @GuardedBy("mDeviceBroker.mDeviceStateLock")
+    /*package*/ void resetBluetoothSco() {
         mScoAudioState = SCO_STATE_INACTIVE;
         broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
         mDeviceBroker.clearA2dpSuspended(false /* internalOnly */);
@@ -572,7 +587,7 @@
         mDeviceBroker.setBluetoothScoOn(false, "resetBluetoothSco");
     }
 
-    // Called locked by ADeviceBroker.mSetModeLock -> AudioDeviceBroker.mDeviceStateLock
+    @GuardedBy("mDeviceBroker.mDeviceStateLock")
     /*package*/ synchronized void onBtProfileDisconnected(int profile) {
         AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent(
                 "BT profile " + BluetoothProfile.getProfileName(profile)
@@ -634,9 +649,10 @@
         }
     }
 
+    @GuardedBy("BtHelper.this")
     MyLeAudioCallback mLeAudioCallback = null;
 
-    // Called locked by ADeviceBroker.mSetModeLock -> AudioDeviceBroker.mDeviceStateLock
+    @GuardedBy("mDeviceBroker.mDeviceStateLock")
     /*package*/ synchronized void onBtProfileConnected(int profile, BluetoothProfile proxy) {
         AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent(
                 "BT profile " + BluetoothProfile.getProfileName(profile) + " connected to proxy "
@@ -773,8 +789,8 @@
         }
     }
 
-    // Called locked by ADeviceBroker.mSetModeLock -> AudioDeviceBroker.mDeviceStateLock
-    private void onHeadsetProfileConnected(@NonNull BluetoothHeadset headset) {
+    @GuardedBy("mDeviceBroker.mDeviceStateLock")
+    private synchronized void onHeadsetProfileConnected(@NonNull BluetoothHeadset headset) {
         // Discard timeout message
         mDeviceBroker.handleCancelFailureToConnectToBtHeadsetService();
         mBluetoothHeadset = headset;
@@ -830,6 +846,7 @@
         mDeviceBroker.postBroadcastScoConnectionState(state);
     }
 
+    @GuardedBy("mDeviceBroker.mDeviceStateLock")
     @Nullable AudioDeviceAttributes getHeadsetAudioDevice() {
         if (mBluetoothHeadsetDevice == null) {
             return null;
@@ -837,6 +854,7 @@
         return getHeadsetAudioDevice(mBluetoothHeadsetDevice);
     }
 
+    @GuardedBy("mDeviceBroker.mDeviceStateLock")
     private @NonNull AudioDeviceAttributes getHeadsetAudioDevice(BluetoothDevice btDevice) {
         AudioDeviceAttributes deviceAttr = mResolvedScoAudioDevices.get(btDevice);
         if (deviceAttr != null) {
@@ -876,30 +894,44 @@
         return new AudioDeviceAttributes(nativeType, address, name);
     }
 
+    @GuardedBy("mDeviceBroker.mDeviceStateLock")
     private boolean handleBtScoActiveDeviceChange(BluetoothDevice btDevice, boolean isActive) {
         if (btDevice == null) {
             return true;
         }
-        int inDevice = AudioSystem.DEVICE_IN_BLUETOOTH_SCO_HEADSET;
-        AudioDeviceAttributes audioDevice = btHeadsetDeviceToAudioDevice(btDevice);
         boolean result = false;
+        AudioDeviceAttributes audioDevice = null; // Only used if isActive is true
+        String address = btDevice.getAddress();
+        String name = getName(btDevice);
+        // Handle output device
         if (isActive) {
-            result |= mDeviceBroker.handleDeviceConnection(audioDevice, isActive, btDevice);
+            audioDevice = btHeadsetDeviceToAudioDevice(btDevice);
+            result = mDeviceBroker.handleDeviceConnection(
+                    audioDevice, true /*connect*/, btDevice);
         } else {
-            int[] outDeviceTypes = {
+            AudioDeviceAttributes ada = mResolvedScoAudioDevices.get(btDevice);
+            if (ada != null) {
+                result = mDeviceBroker.handleDeviceConnection(
+                    ada, false /*connect*/, btDevice);
+            } else {
+                // Disconnect all possible audio device types if the disconnected device type is
+                // unknown
+                int[] outDeviceTypes = {
                     AudioSystem.DEVICE_OUT_BLUETOOTH_SCO,
                     AudioSystem.DEVICE_OUT_BLUETOOTH_SCO_HEADSET,
                     AudioSystem.DEVICE_OUT_BLUETOOTH_SCO_CARKIT
-            };
-            for (int outDeviceType : outDeviceTypes) {
-                result |= mDeviceBroker.handleDeviceConnection(new AudioDeviceAttributes(
-                        outDeviceType, audioDevice.getAddress(), audioDevice.getName()),
-                        isActive, btDevice);
+                };
+                for (int outDeviceType : outDeviceTypes) {
+                    result |= mDeviceBroker.handleDeviceConnection(new AudioDeviceAttributes(
+                            outDeviceType, address, name), false /*connect*/, btDevice);
+                }
             }
         }
+        // Handle input device
+        int inDevice = AudioSystem.DEVICE_IN_BLUETOOTH_SCO_HEADSET;
         // handleDeviceConnection() && result to make sure the method get executed
         result = mDeviceBroker.handleDeviceConnection(new AudioDeviceAttributes(
-                        inDevice, audioDevice.getAddress(), audioDevice.getName()),
+                        inDevice, address, name),
                 isActive, btDevice) && result;
         if (result) {
             if (isActive) {
@@ -916,8 +948,8 @@
         return btDevice == null ? "(null)" : btDevice.getAnonymizedAddress();
     }
 
-    // Called locked by ADeviceBroker.mSetModeLock -> AudioDeviceBroker.mDeviceStateLock
-    /*package */ synchronized void onSetBtScoActiveDevice(BluetoothDevice btDevice) {
+    @GuardedBy("mDeviceBroker.mDeviceStateLock")
+    /*package */ void onSetBtScoActiveDevice(BluetoothDevice btDevice) {
         Log.i(TAG, "onSetBtScoActiveDevice: " + getAnonymizedAddress(mBluetoothHeadsetDevice)
                 + " -> " + getAnonymizedAddress(btDevice));
         final BluetoothDevice previousActiveDevice = mBluetoothHeadsetDevice;
@@ -987,10 +1019,8 @@
 
     //----------------------------------------------------------------------
 
-    // @GuardedBy("mDeviceBroker.mSetModeLock")
-    // @GuardedBy("AudioDeviceBroker.this.mDeviceStateLock")
-    @GuardedBy("BtHelper.this")
-    private boolean requestScoState(int state, int scoAudioMode) {
+    @GuardedBy("mDeviceBroker.mDeviceStateLock")
+    private synchronized boolean requestScoState(int state, int scoAudioMode) {
         checkScoAudioState();
         if (state == BluetoothHeadset.STATE_AUDIO_CONNECTED) {
             // Make sure that the state transitions to CONNECTING even if we cannot initiate
@@ -1154,13 +1184,14 @@
         }
     }
 
-    private void checkScoAudioState() {
+    @GuardedBy("mDeviceBroker.mDeviceStateLock")
+    private synchronized void checkScoAudioState() {
         try {
             if (mBluetoothHeadset != null
                     && mBluetoothHeadsetDevice != null
                     && mScoAudioState == SCO_STATE_INACTIVE
                     && mBluetoothHeadset.getAudioState(mBluetoothHeadsetDevice)
-                    != BluetoothHeadset.STATE_AUDIO_DISCONNECTED) {
+                        != BluetoothHeadset.STATE_AUDIO_DISCONNECTED) {
                 mScoAudioState = SCO_STATE_ACTIVE_EXTERNAL;
             }
         } catch (Exception e) {
@@ -1184,7 +1215,7 @@
         return result;
     }
 
-    /*package*/ int getLeAudioDeviceGroupId(BluetoothDevice device) {
+    /*package*/ synchronized int getLeAudioDeviceGroupId(BluetoothDevice device) {
         if (mLeAudio == null || device == null) {
             return BluetoothLeAudio.GROUP_ID_INVALID;
         }
@@ -1197,7 +1228,7 @@
      * @return A List of Pair(String main_address, String identity_address). Note that the
      * addresses returned by BluetoothDevice can be null.
      */
-    /*package*/ List<Pair<String, String>> getLeAudioGroupAddresses(int groupId) {
+    /*package*/ synchronized List<Pair<String, String>> getLeAudioGroupAddresses(int groupId) {
         List<Pair<String, String>> addresses = new ArrayList<>();
         BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
         if (adapter == null || mLeAudio == null) {