Loading android/app/src/com/android/bluetooth/btservice/ActiveDeviceManager.java +78 −0 Original line number Diff line number Diff line Loading @@ -26,6 +26,9 @@ import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.media.AudioDeviceCallback; import android.media.AudioDeviceInfo; import android.media.AudioManager; import android.os.Handler; import android.os.HandlerThread; import android.os.Looper; Loading Loading @@ -81,6 +84,20 @@ import java.util.Objects; * contained in the broadcast is marked as active. However, if * the contained device is null, the corresponding profile is marked * as having no active device. * 9) If a wired audio device is connected, the audio output is switched * by the Audio Framework itself to that device. We detect this here, * and the active device for each profile (A2DP/HFP/HearingAid) is set * to null to reflect the output device state change. However, if the * wired audio device is disconnected, we don't do anything explicit * and apply the default behavior instead: * 9.1) If the wired headset is still the selected output device (i.e. the * active device is set to null), the Phone itself will become the output * device (i.e., the active device will remain null). If music was * playing, it will stop. * 9.2) If one of the Bluetooth devices is the selected active device * (e.g., by the user in the UI), disconnecting the wired audio device * will have no impact. E.g., music will continue streaming over the * active Bluetooth device. */ class ActiveDeviceManager { private static final boolean DBG = true; Loading @@ -98,6 +115,8 @@ class ActiveDeviceManager { private final ServiceFactory mFactory; private HandlerThread mHandlerThread = null; private Handler mHandler = null; private final AudioManager mAudioManager; private final AudioManagerAudioDeviceCallback mAudioManagerAudioDeviceCallback; private final List<BluetoothDevice> mA2dpConnectedDevices = new LinkedList<>(); private final List<BluetoothDevice> mHfpConnectedDevices = new LinkedList<>(); Loading Loading @@ -305,9 +324,51 @@ class ActiveDeviceManager { } } /** Notifications of audio device connection and disconnection events. */ private class AudioManagerAudioDeviceCallback extends AudioDeviceCallback { private boolean isWiredAudioHeadset(AudioDeviceInfo deviceInfo) { switch (deviceInfo.getType()) { case AudioDeviceInfo.TYPE_WIRED_HEADSET: case AudioDeviceInfo.TYPE_WIRED_HEADPHONES: case AudioDeviceInfo.TYPE_USB_HEADSET: return true; default: break; } return false; } @Override public void onAudioDevicesAdded(AudioDeviceInfo[] addedDevices) { if (DBG) { Log.d(TAG, "onAudioDevicesAdded"); } boolean hasAddedWiredDevice = false; for (AudioDeviceInfo deviceInfo : addedDevices) { if (DBG) { Log.d(TAG, "Audio device added: " + deviceInfo.getProductName() + " type: " + deviceInfo.getType()); } if (isWiredAudioHeadset(deviceInfo)) { hasAddedWiredDevice = true; break; } } if (hasAddedWiredDevice) { wiredAudioDeviceConnected(); } } @Override public void onAudioDevicesRemoved(AudioDeviceInfo[] removedDevices) { } } ActiveDeviceManager(AdapterService service, ServiceFactory factory) { mAdapterService = service; mFactory = factory; mAudioManager = (AudioManager) service.getSystemService(Context.AUDIO_SERVICE); mAudioManagerAudioDeviceCallback = new AudioManagerAudioDeviceCallback(); } void start() { Loading @@ -327,6 +388,8 @@ class ActiveDeviceManager { filter.addAction(BluetoothHeadset.ACTION_ACTIVE_DEVICE_CHANGED); filter.addAction(BluetoothHearingAid.ACTION_ACTIVE_DEVICE_CHANGED); mAdapterService.registerReceiver(mReceiver, filter); mAudioManager.registerAudioDeviceCallback(mAudioManagerAudioDeviceCallback, mHandler); } void cleanup() { Loading @@ -334,6 +397,7 @@ class ActiveDeviceManager { Log.d(TAG, "cleanup()"); } mAudioManager.unregisterAudioDeviceCallback(mAudioManagerAudioDeviceCallback); mAdapterService.unregisterReceiver(mReceiver); if (mHandlerThread != null) { mHandlerThread.quit(); Loading Loading @@ -413,4 +477,18 @@ class ActiveDeviceManager { BluetoothDevice getHearingAidActiveDevice() { return mHearingAidActiveDevice; } /** * Called when a wired audio device is connected. * It might be called multiple times each time a wired audio device is connected. */ @VisibleForTesting void wiredAudioDeviceConnected() { if (DBG) { Log.d(TAG, "wiredAudioDeviceConnected"); } setA2dpActiveDevice(null); setHfpActiveDevice(null); setHearingAidActiveDevice(null); } } android/app/tests/unit/src/com/android/bluetooth/btservice/ActiveDeviceManagerTest.java +19 −0 Original line number Diff line number Diff line Loading @@ -26,6 +26,7 @@ import android.bluetooth.BluetoothHearingAid; import android.bluetooth.BluetoothProfile; import android.content.Context; import android.content.Intent; import android.media.AudioManager; import android.support.test.InstrumentationRegistry; import android.support.test.filters.MediumTest; import android.support.test.runner.AndroidJUnit4; Loading Loading @@ -62,6 +63,7 @@ public class ActiveDeviceManagerTest { @Mock private A2dpService mA2dpService; @Mock private HeadsetService mHeadsetService; @Mock private HearingAidService mHearingAidService; @Mock private AudioManager mAudioManager; @Before public void setUp() throws Exception { Loading @@ -74,6 +76,7 @@ public class ActiveDeviceManagerTest { // Set up mocks and test assets MockitoAnnotations.initMocks(this); TestUtils.setAdapterService(mAdapterService); when(mAdapterService.getSystemService(Context.AUDIO_SERVICE)).thenReturn(mAudioManager); when(mServiceFactory.getA2dpService()).thenReturn(mA2dpService); when(mServiceFactory.getHeadsetService()).thenReturn(mHeadsetService); when(mServiceFactory.getHearingAidService()).thenReturn(mHearingAidService); Loading Loading @@ -277,6 +280,22 @@ public class ActiveDeviceManagerTest { Assert.assertEquals(null, mActiveDeviceManager.getHearingAidActiveDevice()); } /** * A wired audio device is connected. Then all active devices are set to null. */ @Test public void wiredAudioDeviceConnected_setAllActiveDevicesNull() { a2dpConnected(mA2dpDevice); headsetConnected(mHeadsetDevice); verify(mA2dpService, timeout(TIMEOUT_MS)).setActiveDevice(mA2dpDevice); verify(mHeadsetService, timeout(TIMEOUT_MS)).setActiveDevice(mHeadsetDevice); mActiveDeviceManager.wiredAudioDeviceConnected(); verify(mA2dpService, timeout(TIMEOUT_MS)).setActiveDevice(isNull()); verify(mHeadsetService, timeout(TIMEOUT_MS)).setActiveDevice(isNull()); verify(mHearingAidService, timeout(TIMEOUT_MS)).setActiveDevice(isNull()); } /** * Helper to indicate A2dp connected for a device. */ Loading android/app/tests/unit/src/com/android/bluetooth/btservice/AdapterServiceTest.java +3 −0 Original line number Diff line number Diff line Loading @@ -31,6 +31,7 @@ import android.content.Context; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.res.Resources; import android.media.AudioManager; import android.os.Binder; import android.os.Looper; import android.os.PowerManager; Loading Loading @@ -67,6 +68,7 @@ public class AdapterServiceTest { private @Mock ProfileService mMockService2; private @Mock IBluetoothCallback mIBluetoothCallback; private @Mock Binder mBinder; private @Mock AudioManager mAudioManager; private static final int CONTEXT_SWITCH_MS = 100; private static final int ONE_SECOND_MS = 1000; Loading Loading @@ -104,6 +106,7 @@ public class AdapterServiceTest { when(mMockContext.getSystemService(Context.USER_SERVICE)).thenReturn(mMockUserManager); when(mMockContext.getSystemService(Context.POWER_SERVICE)).thenReturn(mPowerManager); when(mMockContext.getSystemService(Context.ALARM_SERVICE)).thenReturn(mMockAlarmManager); when(mMockContext.getSystemService(Context.AUDIO_SERVICE)).thenReturn(mAudioManager); when(mMockResources.getBoolean(R.bool.profile_supported_gatt)).thenReturn(true); when(mMockResources.getBoolean(R.bool.profile_supported_pbap)).thenReturn(true); Loading Loading
android/app/src/com/android/bluetooth/btservice/ActiveDeviceManager.java +78 −0 Original line number Diff line number Diff line Loading @@ -26,6 +26,9 @@ import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.media.AudioDeviceCallback; import android.media.AudioDeviceInfo; import android.media.AudioManager; import android.os.Handler; import android.os.HandlerThread; import android.os.Looper; Loading Loading @@ -81,6 +84,20 @@ import java.util.Objects; * contained in the broadcast is marked as active. However, if * the contained device is null, the corresponding profile is marked * as having no active device. * 9) If a wired audio device is connected, the audio output is switched * by the Audio Framework itself to that device. We detect this here, * and the active device for each profile (A2DP/HFP/HearingAid) is set * to null to reflect the output device state change. However, if the * wired audio device is disconnected, we don't do anything explicit * and apply the default behavior instead: * 9.1) If the wired headset is still the selected output device (i.e. the * active device is set to null), the Phone itself will become the output * device (i.e., the active device will remain null). If music was * playing, it will stop. * 9.2) If one of the Bluetooth devices is the selected active device * (e.g., by the user in the UI), disconnecting the wired audio device * will have no impact. E.g., music will continue streaming over the * active Bluetooth device. */ class ActiveDeviceManager { private static final boolean DBG = true; Loading @@ -98,6 +115,8 @@ class ActiveDeviceManager { private final ServiceFactory mFactory; private HandlerThread mHandlerThread = null; private Handler mHandler = null; private final AudioManager mAudioManager; private final AudioManagerAudioDeviceCallback mAudioManagerAudioDeviceCallback; private final List<BluetoothDevice> mA2dpConnectedDevices = new LinkedList<>(); private final List<BluetoothDevice> mHfpConnectedDevices = new LinkedList<>(); Loading Loading @@ -305,9 +324,51 @@ class ActiveDeviceManager { } } /** Notifications of audio device connection and disconnection events. */ private class AudioManagerAudioDeviceCallback extends AudioDeviceCallback { private boolean isWiredAudioHeadset(AudioDeviceInfo deviceInfo) { switch (deviceInfo.getType()) { case AudioDeviceInfo.TYPE_WIRED_HEADSET: case AudioDeviceInfo.TYPE_WIRED_HEADPHONES: case AudioDeviceInfo.TYPE_USB_HEADSET: return true; default: break; } return false; } @Override public void onAudioDevicesAdded(AudioDeviceInfo[] addedDevices) { if (DBG) { Log.d(TAG, "onAudioDevicesAdded"); } boolean hasAddedWiredDevice = false; for (AudioDeviceInfo deviceInfo : addedDevices) { if (DBG) { Log.d(TAG, "Audio device added: " + deviceInfo.getProductName() + " type: " + deviceInfo.getType()); } if (isWiredAudioHeadset(deviceInfo)) { hasAddedWiredDevice = true; break; } } if (hasAddedWiredDevice) { wiredAudioDeviceConnected(); } } @Override public void onAudioDevicesRemoved(AudioDeviceInfo[] removedDevices) { } } ActiveDeviceManager(AdapterService service, ServiceFactory factory) { mAdapterService = service; mFactory = factory; mAudioManager = (AudioManager) service.getSystemService(Context.AUDIO_SERVICE); mAudioManagerAudioDeviceCallback = new AudioManagerAudioDeviceCallback(); } void start() { Loading @@ -327,6 +388,8 @@ class ActiveDeviceManager { filter.addAction(BluetoothHeadset.ACTION_ACTIVE_DEVICE_CHANGED); filter.addAction(BluetoothHearingAid.ACTION_ACTIVE_DEVICE_CHANGED); mAdapterService.registerReceiver(mReceiver, filter); mAudioManager.registerAudioDeviceCallback(mAudioManagerAudioDeviceCallback, mHandler); } void cleanup() { Loading @@ -334,6 +397,7 @@ class ActiveDeviceManager { Log.d(TAG, "cleanup()"); } mAudioManager.unregisterAudioDeviceCallback(mAudioManagerAudioDeviceCallback); mAdapterService.unregisterReceiver(mReceiver); if (mHandlerThread != null) { mHandlerThread.quit(); Loading Loading @@ -413,4 +477,18 @@ class ActiveDeviceManager { BluetoothDevice getHearingAidActiveDevice() { return mHearingAidActiveDevice; } /** * Called when a wired audio device is connected. * It might be called multiple times each time a wired audio device is connected. */ @VisibleForTesting void wiredAudioDeviceConnected() { if (DBG) { Log.d(TAG, "wiredAudioDeviceConnected"); } setA2dpActiveDevice(null); setHfpActiveDevice(null); setHearingAidActiveDevice(null); } }
android/app/tests/unit/src/com/android/bluetooth/btservice/ActiveDeviceManagerTest.java +19 −0 Original line number Diff line number Diff line Loading @@ -26,6 +26,7 @@ import android.bluetooth.BluetoothHearingAid; import android.bluetooth.BluetoothProfile; import android.content.Context; import android.content.Intent; import android.media.AudioManager; import android.support.test.InstrumentationRegistry; import android.support.test.filters.MediumTest; import android.support.test.runner.AndroidJUnit4; Loading Loading @@ -62,6 +63,7 @@ public class ActiveDeviceManagerTest { @Mock private A2dpService mA2dpService; @Mock private HeadsetService mHeadsetService; @Mock private HearingAidService mHearingAidService; @Mock private AudioManager mAudioManager; @Before public void setUp() throws Exception { Loading @@ -74,6 +76,7 @@ public class ActiveDeviceManagerTest { // Set up mocks and test assets MockitoAnnotations.initMocks(this); TestUtils.setAdapterService(mAdapterService); when(mAdapterService.getSystemService(Context.AUDIO_SERVICE)).thenReturn(mAudioManager); when(mServiceFactory.getA2dpService()).thenReturn(mA2dpService); when(mServiceFactory.getHeadsetService()).thenReturn(mHeadsetService); when(mServiceFactory.getHearingAidService()).thenReturn(mHearingAidService); Loading Loading @@ -277,6 +280,22 @@ public class ActiveDeviceManagerTest { Assert.assertEquals(null, mActiveDeviceManager.getHearingAidActiveDevice()); } /** * A wired audio device is connected. Then all active devices are set to null. */ @Test public void wiredAudioDeviceConnected_setAllActiveDevicesNull() { a2dpConnected(mA2dpDevice); headsetConnected(mHeadsetDevice); verify(mA2dpService, timeout(TIMEOUT_MS)).setActiveDevice(mA2dpDevice); verify(mHeadsetService, timeout(TIMEOUT_MS)).setActiveDevice(mHeadsetDevice); mActiveDeviceManager.wiredAudioDeviceConnected(); verify(mA2dpService, timeout(TIMEOUT_MS)).setActiveDevice(isNull()); verify(mHeadsetService, timeout(TIMEOUT_MS)).setActiveDevice(isNull()); verify(mHearingAidService, timeout(TIMEOUT_MS)).setActiveDevice(isNull()); } /** * Helper to indicate A2dp connected for a device. */ Loading
android/app/tests/unit/src/com/android/bluetooth/btservice/AdapterServiceTest.java +3 −0 Original line number Diff line number Diff line Loading @@ -31,6 +31,7 @@ import android.content.Context; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.res.Resources; import android.media.AudioManager; import android.os.Binder; import android.os.Looper; import android.os.PowerManager; Loading Loading @@ -67,6 +68,7 @@ public class AdapterServiceTest { private @Mock ProfileService mMockService2; private @Mock IBluetoothCallback mIBluetoothCallback; private @Mock Binder mBinder; private @Mock AudioManager mAudioManager; private static final int CONTEXT_SWITCH_MS = 100; private static final int ONE_SECOND_MS = 1000; Loading Loading @@ -104,6 +106,7 @@ public class AdapterServiceTest { when(mMockContext.getSystemService(Context.USER_SERVICE)).thenReturn(mMockUserManager); when(mMockContext.getSystemService(Context.POWER_SERVICE)).thenReturn(mPowerManager); when(mMockContext.getSystemService(Context.ALARM_SERVICE)).thenReturn(mMockAlarmManager); when(mMockContext.getSystemService(Context.AUDIO_SERVICE)).thenReturn(mAudioManager); when(mMockResources.getBoolean(R.bool.profile_supported_gatt)).thenReturn(true); when(mMockResources.getBoolean(R.bool.profile_supported_pbap)).thenReturn(true); Loading