Loading android/app/src/com/android/bluetooth/btservice/ActiveDeviceManager.java +115 −15 Original line number Diff line number Diff line Loading @@ -49,6 +49,7 @@ import com.android.internal.annotations.VisibleForTesting; import java.util.ArrayList; import java.util.List; import java.util.Objects; import java.util.concurrent.RejectedExecutionException; /** * The active device manager is responsible for keeping track of the Loading Loading @@ -130,15 +131,18 @@ class ActiveDeviceManager { private Handler mHandler = null; private final AudioManager mAudioManager; private final AudioManagerAudioDeviceCallback mAudioManagerAudioDeviceCallback; private final AudioManagerOnModeChangedListener mAudioManagerOnModeChangedListener; private final List<BluetoothDevice> mA2dpConnectedDevices = new ArrayList<>(); private final List<BluetoothDevice> mHfpConnectedDevices = new ArrayList<>(); private final List<BluetoothDevice> mHearingAidConnectedDevices = new ArrayList<>(); private final List<BluetoothDevice> mLeAudioConnectedDevices = new ArrayList<>(); private final List<BluetoothDevice> mLeHearingAidConnectedDevices = new ArrayList<>(); private List<BluetoothDevice> mPendingLeHearingAidActiveDevice = new ArrayList<>(); private final List<BluetoothDevice> mPendingLeHearingAidActiveDevice = new ArrayList<>(); private BluetoothDevice mA2dpActiveDevice = null; private BluetoothDevice mPendingA2dpActiveDevice = null; private BluetoothDevice mHfpActiveDevice = null; private BluetoothDevice mPendingHfpActiveDevice = null; private BluetoothDevice mHearingAidActiveDevice = null; private BluetoothDevice mLeAudioActiveDevice = null; private BluetoothDevice mLeHearingAidActiveDevice = null; Loading Loading @@ -245,16 +249,43 @@ class ActiveDeviceManager { if (mA2dpConnectedDevices.contains(device)) { break; // The device is already connected } // New connected A2DP device mA2dpConnectedDevices.add(device); if (mHearingAidActiveDevice == null && mLeHearingAidActiveDevice == null) { // New connected device: select it as active // Lazy active A2DP if it is not being used. // This will prevent the deactivation of LE audio // earlier than the activation of HFP. switch (mAudioManager.getMode()) { case AudioManager.MODE_NORMAL: break; case AudioManager.MODE_RINGTONE: { HeadsetService headsetService = mFactory.getHeadsetService(); if (mHfpActiveDevice == null && headsetService != null && headsetService.isInbandRingingEnabled()) { mPendingA2dpActiveDevice = device; } break; } case AudioManager.MODE_IN_CALL: case AudioManager.MODE_IN_COMMUNICATION: case AudioManager.MODE_CALL_SCREENING: case AudioManager.MODE_CALL_REDIRECT: case AudioManager.MODE_COMMUNICATION_REDIRECT: { if (mHfpActiveDevice == null) { mPendingA2dpActiveDevice = device; } } } if (mPendingA2dpActiveDevice == null) { // select the device as active if not lazy active setA2dpActiveDevice(device); setLeAudioActiveDevice(null); } } break; } if (prevState == BluetoothProfile.STATE_CONNECTED) { // Device disconnected // A2DP device disconnected if (DBG) { Log.d(TAG, "handleMessage(MESSAGE_A2DP_ACTION_CONNECTION_STATE_CHANGED): " Loading Loading @@ -308,16 +339,37 @@ class ActiveDeviceManager { if (mHfpConnectedDevices.contains(device)) { break; // The device is already connected } // New connected HFP device. mHfpConnectedDevices.add(device); if (mHearingAidActiveDevice == null && mLeHearingAidActiveDevice == null) { // New connected device: select it as active // Lazy active HFP if it is not being used. // This will prevent the deactivation of LE audio // earlier than the activation of A2DP. switch (mAudioManager.getMode()) { case AudioManager.MODE_NORMAL: if (mA2dpActiveDevice == null) { mPendingHfpActiveDevice = device; } break; case AudioManager.MODE_RINGTONE: { HeadsetService headsetService = mFactory.getHeadsetService(); if (headsetService == null || !headsetService.isInbandRingingEnabled()) { mPendingHfpActiveDevice = device; } break; } } if (mPendingHfpActiveDevice == null) { // select the device as active if not lazy active setHfpActiveDevice(device); setLeAudioActiveDevice(null); } } break; } if (prevState == BluetoothProfile.STATE_CONNECTED) { // Device disconnected // HFP device disconnected if (DBG) { Log.d(TAG, "handleMessage(MESSAGE_HFP_ACTION_CONNECTION_STATE_CHANGED): " Loading Loading @@ -371,7 +423,7 @@ class ActiveDeviceManager { break; // The device is already connected } mHearingAidConnectedDevices.add(device); // New connected device: select it as active // New connected hearing aid device: select it as active setHearingAidActiveDevice(device); setA2dpActiveDevice(null); setHfpActiveDevice(null); Loading @@ -379,7 +431,7 @@ class ActiveDeviceManager { break; } if (prevState == BluetoothProfile.STATE_CONNECTED) { // Device disconnected // Hearing aid device disconnected if (DBG) { Log.d(TAG, "handleMessage(MESSAGE_HEARING_AID_ACTION_CONNECTION_STATE" + "_CHANGED): device " + device + " disconnected"); Loading Loading @@ -435,7 +487,7 @@ class ActiveDeviceManager { mLeAudioConnectedDevices.add(device); if (mHearingAidActiveDevice == null && mLeHearingAidActiveDevice == null && mPendingLeHearingAidActiveDevice.isEmpty()) { // New connected device: select it as active // New connected LE audio device: select it as active setLeAudioActiveDevice(device); setA2dpActiveDevice(null); setHfpActiveDevice(null); Loading @@ -448,7 +500,7 @@ class ActiveDeviceManager { break; } if (prevState == BluetoothProfile.STATE_CONNECTED) { // Device disconnected // LE audio device disconnected if (DBG) { Log.d(TAG, "handleMessage(MESSAGE_LE_AUDIO_ACTION_CONNECTION_STATE" + "_CHANGED): device " + device + " disconnected"); Loading Loading @@ -511,7 +563,7 @@ class ActiveDeviceManager { } else if (Objects.equals(mLeAudioActiveDevice, device)) { mLeHearingAidActiveDevice = device; } else { // New connected device: select it as active // New connected LE hearing aid device: select it as active setLeHearingAidActiveDevice(device); setHearingAidActiveDevice(null); setA2dpActiveDevice(null); Loading @@ -520,7 +572,7 @@ class ActiveDeviceManager { break; } if (prevState == BluetoothProfile.STATE_CONNECTED) { // Device disconnected // LE hearing aid device disconnected if (DBG) { Log.d(TAG, "handleMessage(MESSAGE_HAP_ACTION_CONNECTION_STATE" + "_CHANGED): device " + device + " disconnected"); Loading Loading @@ -558,6 +610,40 @@ class ActiveDeviceManager { } } private class AudioManagerOnModeChangedListener implements AudioManager.OnModeChangedListener { public void onModeChanged(int mode) { switch (mode) { case AudioManager.MODE_NORMAL: { if (mPendingA2dpActiveDevice != null) { setA2dpActiveDevice(mPendingA2dpActiveDevice); setLeAudioActiveDevice(null); } break; } case AudioManager.MODE_RINGTONE: { final HeadsetService headsetService = mFactory.getHeadsetService(); if (headsetService != null && headsetService.isInbandRingingEnabled() && mPendingHfpActiveDevice != null) { setHfpActiveDevice(mPendingHfpActiveDevice); setLeAudioActiveDevice(null); } break; } case AudioManager.MODE_IN_CALL: case AudioManager.MODE_IN_COMMUNICATION: case AudioManager.MODE_CALL_SCREENING: case AudioManager.MODE_CALL_REDIRECT: case AudioManager.MODE_COMMUNICATION_REDIRECT: { if (mPendingHfpActiveDevice != null) { setHfpActiveDevice(mPendingHfpActiveDevice); setLeAudioActiveDevice(null); } break; } } } } /** Notifications of audio device connection and disconnection events. */ @SuppressLint("AndroidFrameworkRequiresPermission") private class AudioManagerAudioDeviceCallback extends AudioDeviceCallback { Loading Loading @@ -604,6 +690,7 @@ class ActiveDeviceManager { mFactory = factory; mAudioManager = service.getSystemService(AudioManager.class); mAudioManagerAudioDeviceCallback = new AudioManagerAudioDeviceCallback(); mAudioManagerOnModeChangedListener = new AudioManagerOnModeChangedListener(); } void start() { Loading @@ -630,6 +717,11 @@ class ActiveDeviceManager { mAdapterService.registerReceiver(mReceiver, filter); mAudioManager.registerAudioDeviceCallback(mAudioManagerAudioDeviceCallback, mHandler); mAudioManager.addOnModeChangedListener(command -> { if (!mHandler.post(command)) { throw new RejectedExecutionException(mHandler + " is shutting down"); } }, mAudioManagerOnModeChangedListener); } void cleanup() { Loading Loading @@ -672,6 +764,10 @@ class ActiveDeviceManager { return; } mA2dpActiveDevice = device; mPendingA2dpActiveDevice = null; if (mPendingHfpActiveDevice != null) { setHfpActiveDevice(mPendingHfpActiveDevice); } } @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) Loading @@ -687,6 +783,10 @@ class ActiveDeviceManager { return; } mHfpActiveDevice = device; mPendingHfpActiveDevice = null; if (mPendingA2dpActiveDevice != null) { setA2dpActiveDevice(mPendingA2dpActiveDevice); } } private void setHearingAidActiveDevice(BluetoothDevice device) { Loading android/app/tests/unit/src/com/android/bluetooth/btservice/ActiveDeviceManagerTest.java +11 −1 Original line number Diff line number Diff line Loading @@ -236,6 +236,8 @@ public class ActiveDeviceManagerTest { */ @Test public void onlyHeadsetConnected_setHeadsetActive() { when(mAudioManager.getMode()).thenReturn(AudioManager.MODE_IN_CALL); headsetConnected(mHeadsetDevice); verify(mHeadsetService, timeout(TIMEOUT_MS)).setActiveDevice(mHeadsetDevice); } Loading @@ -245,6 +247,8 @@ public class ActiveDeviceManagerTest { */ @Test public void secondHeadsetConnected_setSecondHeadsetActive() { when(mAudioManager.getMode()).thenReturn(AudioManager.MODE_IN_CALL); headsetConnected(mHeadsetDevice); verify(mHeadsetService, timeout(TIMEOUT_MS)).setActiveDevice(mHeadsetDevice); Loading @@ -257,6 +261,8 @@ public class ActiveDeviceManagerTest { */ @Test public void lastHeadsetDisconnected_clearHeadsetActive() { when(mAudioManager.getMode()).thenReturn(AudioManager.MODE_IN_CALL); headsetConnected(mHeadsetDevice); verify(mHeadsetService, timeout(TIMEOUT_MS)).setActiveDevice(mHeadsetDevice); Loading @@ -269,6 +275,8 @@ public class ActiveDeviceManagerTest { */ @Test public void headsetActiveDeviceSelected_setActive() { when(mAudioManager.getMode()).thenReturn(AudioManager.MODE_IN_CALL); headsetConnected(mHeadsetDevice); verify(mHeadsetService, timeout(TIMEOUT_MS)).setActiveDevice(mHeadsetDevice); Loading @@ -288,6 +296,8 @@ public class ActiveDeviceManagerTest { */ @Test public void headsetSecondDeviceDisconnected_fallbackDeviceActive() { when(mAudioManager.getMode()).thenReturn(AudioManager.MODE_IN_CALL); headsetConnected(mSecondaryAudioDevice); verify(mHeadsetService, timeout(TIMEOUT_MS)).setActiveDevice(mSecondaryAudioDevice); Loading Loading @@ -501,7 +511,7 @@ public class ActiveDeviceManagerTest { public void leAudioActive_setHeadsetActiveExplicitly() { Assume.assumeTrue("Ignore test when LeAudioService is not enabled", LeAudioService.isEnabled()); when(mAudioManager.getMode()).thenReturn(AudioManager.MODE_IN_CALL); leAudioActiveDeviceChanged(mLeAudioDevice); headsetConnected(mA2dpHeadsetDevice); headsetActiveDeviceChanged(mA2dpHeadsetDevice); Loading Loading
android/app/src/com/android/bluetooth/btservice/ActiveDeviceManager.java +115 −15 Original line number Diff line number Diff line Loading @@ -49,6 +49,7 @@ import com.android.internal.annotations.VisibleForTesting; import java.util.ArrayList; import java.util.List; import java.util.Objects; import java.util.concurrent.RejectedExecutionException; /** * The active device manager is responsible for keeping track of the Loading Loading @@ -130,15 +131,18 @@ class ActiveDeviceManager { private Handler mHandler = null; private final AudioManager mAudioManager; private final AudioManagerAudioDeviceCallback mAudioManagerAudioDeviceCallback; private final AudioManagerOnModeChangedListener mAudioManagerOnModeChangedListener; private final List<BluetoothDevice> mA2dpConnectedDevices = new ArrayList<>(); private final List<BluetoothDevice> mHfpConnectedDevices = new ArrayList<>(); private final List<BluetoothDevice> mHearingAidConnectedDevices = new ArrayList<>(); private final List<BluetoothDevice> mLeAudioConnectedDevices = new ArrayList<>(); private final List<BluetoothDevice> mLeHearingAidConnectedDevices = new ArrayList<>(); private List<BluetoothDevice> mPendingLeHearingAidActiveDevice = new ArrayList<>(); private final List<BluetoothDevice> mPendingLeHearingAidActiveDevice = new ArrayList<>(); private BluetoothDevice mA2dpActiveDevice = null; private BluetoothDevice mPendingA2dpActiveDevice = null; private BluetoothDevice mHfpActiveDevice = null; private BluetoothDevice mPendingHfpActiveDevice = null; private BluetoothDevice mHearingAidActiveDevice = null; private BluetoothDevice mLeAudioActiveDevice = null; private BluetoothDevice mLeHearingAidActiveDevice = null; Loading Loading @@ -245,16 +249,43 @@ class ActiveDeviceManager { if (mA2dpConnectedDevices.contains(device)) { break; // The device is already connected } // New connected A2DP device mA2dpConnectedDevices.add(device); if (mHearingAidActiveDevice == null && mLeHearingAidActiveDevice == null) { // New connected device: select it as active // Lazy active A2DP if it is not being used. // This will prevent the deactivation of LE audio // earlier than the activation of HFP. switch (mAudioManager.getMode()) { case AudioManager.MODE_NORMAL: break; case AudioManager.MODE_RINGTONE: { HeadsetService headsetService = mFactory.getHeadsetService(); if (mHfpActiveDevice == null && headsetService != null && headsetService.isInbandRingingEnabled()) { mPendingA2dpActiveDevice = device; } break; } case AudioManager.MODE_IN_CALL: case AudioManager.MODE_IN_COMMUNICATION: case AudioManager.MODE_CALL_SCREENING: case AudioManager.MODE_CALL_REDIRECT: case AudioManager.MODE_COMMUNICATION_REDIRECT: { if (mHfpActiveDevice == null) { mPendingA2dpActiveDevice = device; } } } if (mPendingA2dpActiveDevice == null) { // select the device as active if not lazy active setA2dpActiveDevice(device); setLeAudioActiveDevice(null); } } break; } if (prevState == BluetoothProfile.STATE_CONNECTED) { // Device disconnected // A2DP device disconnected if (DBG) { Log.d(TAG, "handleMessage(MESSAGE_A2DP_ACTION_CONNECTION_STATE_CHANGED): " Loading Loading @@ -308,16 +339,37 @@ class ActiveDeviceManager { if (mHfpConnectedDevices.contains(device)) { break; // The device is already connected } // New connected HFP device. mHfpConnectedDevices.add(device); if (mHearingAidActiveDevice == null && mLeHearingAidActiveDevice == null) { // New connected device: select it as active // Lazy active HFP if it is not being used. // This will prevent the deactivation of LE audio // earlier than the activation of A2DP. switch (mAudioManager.getMode()) { case AudioManager.MODE_NORMAL: if (mA2dpActiveDevice == null) { mPendingHfpActiveDevice = device; } break; case AudioManager.MODE_RINGTONE: { HeadsetService headsetService = mFactory.getHeadsetService(); if (headsetService == null || !headsetService.isInbandRingingEnabled()) { mPendingHfpActiveDevice = device; } break; } } if (mPendingHfpActiveDevice == null) { // select the device as active if not lazy active setHfpActiveDevice(device); setLeAudioActiveDevice(null); } } break; } if (prevState == BluetoothProfile.STATE_CONNECTED) { // Device disconnected // HFP device disconnected if (DBG) { Log.d(TAG, "handleMessage(MESSAGE_HFP_ACTION_CONNECTION_STATE_CHANGED): " Loading Loading @@ -371,7 +423,7 @@ class ActiveDeviceManager { break; // The device is already connected } mHearingAidConnectedDevices.add(device); // New connected device: select it as active // New connected hearing aid device: select it as active setHearingAidActiveDevice(device); setA2dpActiveDevice(null); setHfpActiveDevice(null); Loading @@ -379,7 +431,7 @@ class ActiveDeviceManager { break; } if (prevState == BluetoothProfile.STATE_CONNECTED) { // Device disconnected // Hearing aid device disconnected if (DBG) { Log.d(TAG, "handleMessage(MESSAGE_HEARING_AID_ACTION_CONNECTION_STATE" + "_CHANGED): device " + device + " disconnected"); Loading Loading @@ -435,7 +487,7 @@ class ActiveDeviceManager { mLeAudioConnectedDevices.add(device); if (mHearingAidActiveDevice == null && mLeHearingAidActiveDevice == null && mPendingLeHearingAidActiveDevice.isEmpty()) { // New connected device: select it as active // New connected LE audio device: select it as active setLeAudioActiveDevice(device); setA2dpActiveDevice(null); setHfpActiveDevice(null); Loading @@ -448,7 +500,7 @@ class ActiveDeviceManager { break; } if (prevState == BluetoothProfile.STATE_CONNECTED) { // Device disconnected // LE audio device disconnected if (DBG) { Log.d(TAG, "handleMessage(MESSAGE_LE_AUDIO_ACTION_CONNECTION_STATE" + "_CHANGED): device " + device + " disconnected"); Loading Loading @@ -511,7 +563,7 @@ class ActiveDeviceManager { } else if (Objects.equals(mLeAudioActiveDevice, device)) { mLeHearingAidActiveDevice = device; } else { // New connected device: select it as active // New connected LE hearing aid device: select it as active setLeHearingAidActiveDevice(device); setHearingAidActiveDevice(null); setA2dpActiveDevice(null); Loading @@ -520,7 +572,7 @@ class ActiveDeviceManager { break; } if (prevState == BluetoothProfile.STATE_CONNECTED) { // Device disconnected // LE hearing aid device disconnected if (DBG) { Log.d(TAG, "handleMessage(MESSAGE_HAP_ACTION_CONNECTION_STATE" + "_CHANGED): device " + device + " disconnected"); Loading Loading @@ -558,6 +610,40 @@ class ActiveDeviceManager { } } private class AudioManagerOnModeChangedListener implements AudioManager.OnModeChangedListener { public void onModeChanged(int mode) { switch (mode) { case AudioManager.MODE_NORMAL: { if (mPendingA2dpActiveDevice != null) { setA2dpActiveDevice(mPendingA2dpActiveDevice); setLeAudioActiveDevice(null); } break; } case AudioManager.MODE_RINGTONE: { final HeadsetService headsetService = mFactory.getHeadsetService(); if (headsetService != null && headsetService.isInbandRingingEnabled() && mPendingHfpActiveDevice != null) { setHfpActiveDevice(mPendingHfpActiveDevice); setLeAudioActiveDevice(null); } break; } case AudioManager.MODE_IN_CALL: case AudioManager.MODE_IN_COMMUNICATION: case AudioManager.MODE_CALL_SCREENING: case AudioManager.MODE_CALL_REDIRECT: case AudioManager.MODE_COMMUNICATION_REDIRECT: { if (mPendingHfpActiveDevice != null) { setHfpActiveDevice(mPendingHfpActiveDevice); setLeAudioActiveDevice(null); } break; } } } } /** Notifications of audio device connection and disconnection events. */ @SuppressLint("AndroidFrameworkRequiresPermission") private class AudioManagerAudioDeviceCallback extends AudioDeviceCallback { Loading Loading @@ -604,6 +690,7 @@ class ActiveDeviceManager { mFactory = factory; mAudioManager = service.getSystemService(AudioManager.class); mAudioManagerAudioDeviceCallback = new AudioManagerAudioDeviceCallback(); mAudioManagerOnModeChangedListener = new AudioManagerOnModeChangedListener(); } void start() { Loading @@ -630,6 +717,11 @@ class ActiveDeviceManager { mAdapterService.registerReceiver(mReceiver, filter); mAudioManager.registerAudioDeviceCallback(mAudioManagerAudioDeviceCallback, mHandler); mAudioManager.addOnModeChangedListener(command -> { if (!mHandler.post(command)) { throw new RejectedExecutionException(mHandler + " is shutting down"); } }, mAudioManagerOnModeChangedListener); } void cleanup() { Loading Loading @@ -672,6 +764,10 @@ class ActiveDeviceManager { return; } mA2dpActiveDevice = device; mPendingA2dpActiveDevice = null; if (mPendingHfpActiveDevice != null) { setHfpActiveDevice(mPendingHfpActiveDevice); } } @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) Loading @@ -687,6 +783,10 @@ class ActiveDeviceManager { return; } mHfpActiveDevice = device; mPendingHfpActiveDevice = null; if (mPendingA2dpActiveDevice != null) { setA2dpActiveDevice(mPendingA2dpActiveDevice); } } private void setHearingAidActiveDevice(BluetoothDevice device) { Loading
android/app/tests/unit/src/com/android/bluetooth/btservice/ActiveDeviceManagerTest.java +11 −1 Original line number Diff line number Diff line Loading @@ -236,6 +236,8 @@ public class ActiveDeviceManagerTest { */ @Test public void onlyHeadsetConnected_setHeadsetActive() { when(mAudioManager.getMode()).thenReturn(AudioManager.MODE_IN_CALL); headsetConnected(mHeadsetDevice); verify(mHeadsetService, timeout(TIMEOUT_MS)).setActiveDevice(mHeadsetDevice); } Loading @@ -245,6 +247,8 @@ public class ActiveDeviceManagerTest { */ @Test public void secondHeadsetConnected_setSecondHeadsetActive() { when(mAudioManager.getMode()).thenReturn(AudioManager.MODE_IN_CALL); headsetConnected(mHeadsetDevice); verify(mHeadsetService, timeout(TIMEOUT_MS)).setActiveDevice(mHeadsetDevice); Loading @@ -257,6 +261,8 @@ public class ActiveDeviceManagerTest { */ @Test public void lastHeadsetDisconnected_clearHeadsetActive() { when(mAudioManager.getMode()).thenReturn(AudioManager.MODE_IN_CALL); headsetConnected(mHeadsetDevice); verify(mHeadsetService, timeout(TIMEOUT_MS)).setActiveDevice(mHeadsetDevice); Loading @@ -269,6 +275,8 @@ public class ActiveDeviceManagerTest { */ @Test public void headsetActiveDeviceSelected_setActive() { when(mAudioManager.getMode()).thenReturn(AudioManager.MODE_IN_CALL); headsetConnected(mHeadsetDevice); verify(mHeadsetService, timeout(TIMEOUT_MS)).setActiveDevice(mHeadsetDevice); Loading @@ -288,6 +296,8 @@ public class ActiveDeviceManagerTest { */ @Test public void headsetSecondDeviceDisconnected_fallbackDeviceActive() { when(mAudioManager.getMode()).thenReturn(AudioManager.MODE_IN_CALL); headsetConnected(mSecondaryAudioDevice); verify(mHeadsetService, timeout(TIMEOUT_MS)).setActiveDevice(mSecondaryAudioDevice); Loading Loading @@ -501,7 +511,7 @@ public class ActiveDeviceManagerTest { public void leAudioActive_setHeadsetActiveExplicitly() { Assume.assumeTrue("Ignore test when LeAudioService is not enabled", LeAudioService.isEnabled()); when(mAudioManager.getMode()).thenReturn(AudioManager.MODE_IN_CALL); leAudioActiveDeviceChanged(mLeAudioDevice); headsetConnected(mA2dpHeadsetDevice); headsetActiveDeviceChanged(mA2dpHeadsetDevice); Loading