blob: 28fef1ce7a18d7831b24447090c57995a33bda8e [file] [log] [blame]
/*
* Copyright 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.bluetooth.hearingaid;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.anyString;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothHearingAid;
import android.bluetooth.BluetoothProfile;
import android.bluetooth.BluetoothUuid;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.media.AudioManager;
import android.media.BluetoothProfileConnectionInfo;
import android.os.Looper;
import android.os.ParcelUuid;
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.MediumTest;
import androidx.test.rule.ServiceTestRule;
import androidx.test.runner.AndroidJUnit4;
import com.android.bluetooth.TestUtils;
import com.android.bluetooth.btservice.ActiveDeviceManager;
import com.android.bluetooth.btservice.AdapterService;
import com.android.bluetooth.btservice.storage.DatabaseManager;
import com.android.bluetooth.x.com.android.modules.utils.SynchronousResultReceiver;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import java.time.Duration;
import java.util.HashMap;
import java.util.List;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeoutException;
@MediumTest
@RunWith(AndroidJUnit4.class)
public class HearingAidServiceTest {
private BluetoothAdapter mAdapter;
private Context mTargetContext;
private HearingAidService mService;
private HearingAidService.BluetoothHearingAidBinder mServiceBinder;
private BluetoothDevice mLeftDevice;
private BluetoothDevice mRightDevice;
private BluetoothDevice mSingleDevice;
private HashMap<BluetoothDevice, LinkedBlockingQueue<Intent>> mDeviceQueueMap;
private static final int TIMEOUT_MS = 1000;
private HearingAidIntentReceiver mHearingAidIntentReceiver;
@Mock private AdapterService mAdapterService;
@Mock private ActiveDeviceManager mActiveDeviceManager;
@Mock private DatabaseManager mDatabaseManager;
@Mock private HearingAidNativeInterface mNativeInterface;
@Mock private AudioManager mAudioManager;
@Rule public final ServiceTestRule mServiceRule = new ServiceTestRule();
@Before
public void setUp() throws Exception {
mTargetContext = InstrumentationRegistry.getTargetContext();
// Set up mocks and test assets
MockitoAnnotations.initMocks(this);
if (Looper.myLooper() == null) {
Looper.prepare();
}
TestUtils.setAdapterService(mAdapterService);
doReturn(mActiveDeviceManager).when(mAdapterService).getActiveDeviceManager();
doReturn(mDatabaseManager).when(mAdapterService).getDatabase();
doReturn(true, false).when(mAdapterService).isStartedProfile(anyString());
mAdapter = BluetoothAdapter.getDefaultAdapter();
HearingAidNativeInterface.setInstance(mNativeInterface);
startService();
mService.mAudioManager = mAudioManager;
mServiceBinder = (HearingAidService.BluetoothHearingAidBinder) mService.initBinder();
mServiceBinder.mIsTesting = true;
// Override the timeout value to speed up the test
HearingAidStateMachine.sConnectTimeoutMs = TIMEOUT_MS; // 1s
// Get a device for testing
mLeftDevice = TestUtils.getTestDevice(mAdapter, 0);
mRightDevice = TestUtils.getTestDevice(mAdapter, 1);
mSingleDevice = TestUtils.getTestDevice(mAdapter, 2);
mDeviceQueueMap = new HashMap<>();
mDeviceQueueMap.put(mLeftDevice, new LinkedBlockingQueue<>());
mDeviceQueueMap.put(mRightDevice, new LinkedBlockingQueue<>());
mDeviceQueueMap.put(mSingleDevice, new LinkedBlockingQueue<>());
doReturn(BluetoothDevice.BOND_BONDED).when(mAdapterService)
.getBondState(any(BluetoothDevice.class));
doReturn(new ParcelUuid[]{BluetoothUuid.HEARING_AID}).when(mAdapterService)
.getRemoteUuids(any(BluetoothDevice.class));
// Set up the Connection State Changed receiver
IntentFilter filter = new IntentFilter();
filter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
filter.addAction(BluetoothHearingAid.ACTION_CONNECTION_STATE_CHANGED);
mHearingAidIntentReceiver = new HearingAidIntentReceiver(mDeviceQueueMap);
mTargetContext.registerReceiver(mHearingAidIntentReceiver, filter);
}
@After
public void tearDown() throws Exception {
stopService();
HearingAidNativeInterface.setInstance(null);
mTargetContext.unregisterReceiver(mHearingAidIntentReceiver);
mHearingAidIntentReceiver.clear();
mDeviceQueueMap.clear();
TestUtils.clearAdapterService(mAdapterService);
reset(mAudioManager);
}
private void startService() throws TimeoutException {
TestUtils.startService(mServiceRule, HearingAidService.class);
mService = HearingAidService.getHearingAidService();
Assert.assertNotNull(mService);
}
private void stopService() throws TimeoutException {
TestUtils.stopService(mServiceRule, HearingAidService.class);
mService = HearingAidService.getHearingAidService();
Assert.assertNull(mService);
}
private static class HearingAidIntentReceiver extends BroadcastReceiver {
HashMap<BluetoothDevice, LinkedBlockingQueue<Intent>> mDeviceQueueMap;
public HearingAidIntentReceiver(
HashMap<BluetoothDevice, LinkedBlockingQueue<Intent>> deviceQueueMap) {
mDeviceQueueMap = deviceQueueMap;
}
public void clear() {
mDeviceQueueMap = null;
}
@Override
public void onReceive(Context context, Intent intent) {
if (BluetoothHearingAid.ACTION_CONNECTION_STATE_CHANGED.equals(intent.getAction())
&& mDeviceQueueMap != null) {
try {
BluetoothDevice device = intent.getParcelableExtra(
BluetoothDevice.EXTRA_DEVICE);
Assert.assertNotNull(device);
LinkedBlockingQueue<Intent> queue = mDeviceQueueMap.get(device);
Assert.assertNotNull(queue);
queue.put(intent);
} catch (InterruptedException e) {
Assert.fail("Cannot add Intent to the Connection State queue: "
+ e.getMessage());
}
}
}
}
private void verifyConnectionStateIntent(int timeoutMs, BluetoothDevice device,
int newState, int prevState) {
verifyConnectionStateIntent(timeoutMs, device, newState, prevState, true);
}
private void verifyConnectionStateIntent(int timeoutMs, BluetoothDevice device,
int newState, int prevState, boolean stopAudio) {
Intent intent = TestUtils.waitForIntent(timeoutMs, mDeviceQueueMap.get(device));
Assert.assertNotNull(intent);
Assert.assertEquals(BluetoothHearingAid.ACTION_CONNECTION_STATE_CHANGED,
intent.getAction());
Assert.assertEquals(device, intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE));
Assert.assertEquals(newState, intent.getIntExtra(BluetoothProfile.EXTRA_STATE, -1));
Assert.assertEquals(prevState, intent.getIntExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE,
-1));
if (newState == BluetoothProfile.STATE_CONNECTED) {
// ActiveDeviceManager calls setActiveDevice when connected.
mService.setActiveDevice(device);
} else if (prevState == BluetoothProfile.STATE_CONNECTED) {
if (mService.getConnectedDevices().isEmpty()) {
mService.removeActiveDevice(stopAudio);
}
}
}
private void verifyNoConnectionStateIntent(int timeoutMs, BluetoothDevice device) {
Intent intent = TestUtils.waitForNoIntent(timeoutMs, mDeviceQueueMap.get(device));
Assert.assertNull(intent);
}
/**
* Test getting HearingAid Service: getHearingAidService()
*/
@Test
public void testGetHearingAidService() {
Assert.assertEquals(mService, HearingAidService.getHearingAidService());
}
/**
* Test stop HearingAid Service
*/
@Test
public void testStopHearingAidService() {
// Prepare: connect
connectDevice(mLeftDevice);
// HearingAid Service is already running: test stop(). Note: must be done on the main thread
InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
public void run() {
Assert.assertTrue(mService.stop());
}
});
// Try to restart the service. Note: must be done on the main thread
InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
public void run() {
Assert.assertTrue(mService.start());
}
});
}
/**
* Test get/set priority for BluetoothDevice
*/
@Test
public void testGetSetPriority() throws Exception {
when(mDatabaseManager.getProfileConnectionPolicy(mLeftDevice, BluetoothProfile.HEARING_AID))
.thenReturn(BluetoothProfile.CONNECTION_POLICY_UNKNOWN);
// indirect call of mService.getConnectionPolicy to test BluetoothHearingAidBinder
final SynchronousResultReceiver<Integer> recv = SynchronousResultReceiver.get();
final int defaultRecvValue = -1000;
mServiceBinder.getConnectionPolicy(mLeftDevice, null, recv);
int connectionPolicy = recv.awaitResultNoInterrupt(Duration.ofMillis(TIMEOUT_MS))
.getValue(defaultRecvValue);
Assert.assertEquals("Initial device priority",
BluetoothProfile.CONNECTION_POLICY_UNKNOWN, connectionPolicy);
when(mDatabaseManager.getProfileConnectionPolicy(mLeftDevice, BluetoothProfile.HEARING_AID))
.thenReturn(BluetoothProfile.CONNECTION_POLICY_FORBIDDEN);
Assert.assertEquals("Setting device priority to PRIORITY_OFF",
BluetoothProfile.CONNECTION_POLICY_FORBIDDEN,
mService.getConnectionPolicy(mLeftDevice));
when(mDatabaseManager.getProfileConnectionPolicy(mLeftDevice, BluetoothProfile.HEARING_AID))
.thenReturn(BluetoothProfile.CONNECTION_POLICY_ALLOWED);
Assert.assertEquals("Setting device priority to PRIORITY_ON",
BluetoothProfile.CONNECTION_POLICY_ALLOWED,
mService.getConnectionPolicy(mLeftDevice));
}
/**
* Test okToConnect method using various test cases
*/
@Test
public void testOkToConnect() {
int badPriorityValue = 1024;
int badBondState = 42;
testOkToConnectCase(mSingleDevice,
BluetoothDevice.BOND_NONE, BluetoothProfile.CONNECTION_POLICY_UNKNOWN, false);
testOkToConnectCase(mSingleDevice,
BluetoothDevice.BOND_NONE, BluetoothProfile.CONNECTION_POLICY_FORBIDDEN, false);
testOkToConnectCase(mSingleDevice,
BluetoothDevice.BOND_NONE, BluetoothProfile.CONNECTION_POLICY_ALLOWED, false);
testOkToConnectCase(mSingleDevice,
BluetoothDevice.BOND_NONE, badPriorityValue, false);
testOkToConnectCase(mSingleDevice,
BluetoothDevice.BOND_BONDING, BluetoothProfile.CONNECTION_POLICY_UNKNOWN, false);
testOkToConnectCase(mSingleDevice,
BluetoothDevice.BOND_BONDING, BluetoothProfile.CONNECTION_POLICY_FORBIDDEN, false);
testOkToConnectCase(mSingleDevice,
BluetoothDevice.BOND_BONDING, BluetoothProfile.CONNECTION_POLICY_ALLOWED, false);
testOkToConnectCase(mSingleDevice,
BluetoothDevice.BOND_BONDING, badPriorityValue, false);
testOkToConnectCase(mSingleDevice,
BluetoothDevice.BOND_BONDED, BluetoothProfile.CONNECTION_POLICY_UNKNOWN, true);
testOkToConnectCase(mSingleDevice,
BluetoothDevice.BOND_BONDED, BluetoothProfile.CONNECTION_POLICY_FORBIDDEN, false);
testOkToConnectCase(mSingleDevice,
BluetoothDevice.BOND_BONDED, BluetoothProfile.CONNECTION_POLICY_ALLOWED, true);
testOkToConnectCase(mSingleDevice,
BluetoothDevice.BOND_BONDED, badPriorityValue, false);
testOkToConnectCase(mSingleDevice,
badBondState, BluetoothProfile.CONNECTION_POLICY_UNKNOWN, false);
testOkToConnectCase(mSingleDevice,
badBondState, BluetoothProfile.CONNECTION_POLICY_FORBIDDEN, false);
testOkToConnectCase(mSingleDevice,
badBondState, BluetoothProfile.CONNECTION_POLICY_ALLOWED, false);
testOkToConnectCase(mSingleDevice,
badBondState, badPriorityValue, false);
}
/**
* Test that an outgoing connection to device that does not have Hearing Aid UUID is rejected
*/
@Test
public void testOutgoingConnectMissingHearingAidUuid() {
// Update the device priority so okToConnect() returns true
when(mDatabaseManager.getProfileConnectionPolicy(mLeftDevice, BluetoothProfile.HEARING_AID))
.thenReturn(BluetoothProfile.CONNECTION_POLICY_ALLOWED);
when(mDatabaseManager
.getProfileConnectionPolicy(mRightDevice, BluetoothProfile.HEARING_AID))
.thenReturn(BluetoothProfile.CONNECTION_POLICY_FORBIDDEN);
when(mDatabaseManager
.getProfileConnectionPolicy(mSingleDevice, BluetoothProfile.HEARING_AID))
.thenReturn(BluetoothProfile.CONNECTION_POLICY_FORBIDDEN);
doReturn(true).when(mNativeInterface).connectHearingAid(any(BluetoothDevice.class));
doReturn(true).when(mNativeInterface).disconnectHearingAid(any(BluetoothDevice.class));
// Return No UUID
doReturn(new ParcelUuid[]{}).when(mAdapterService)
.getRemoteUuids(any(BluetoothDevice.class));
// Send a connect request
Assert.assertFalse("Connect expected to fail", mService.connect(mLeftDevice));
}
/**
* Test that an outgoing connection to device with PRIORITY_OFF is rejected
*/
@Test
public void testOutgoingConnectPriorityOff() throws Exception {
doReturn(true).when(mNativeInterface).connectHearingAid(any(BluetoothDevice.class));
doReturn(true).when(mNativeInterface).disconnectHearingAid(any(BluetoothDevice.class));
// Set the device priority to PRIORITY_OFF so connect() should fail
when(mDatabaseManager
.getProfileConnectionPolicy(mLeftDevice, BluetoothProfile.HEARING_AID))
.thenReturn(BluetoothProfile.CONNECTION_POLICY_FORBIDDEN);
// Send a connect request via BluetoothHearingAidBinder
final SynchronousResultReceiver<Boolean> recv = SynchronousResultReceiver.get();
boolean defaultRecvValue = true;
mServiceBinder.connect(mLeftDevice, null, recv);
Assert.assertFalse("Connect expected to fail", recv.awaitResultNoInterrupt(
Duration.ofMillis(TIMEOUT_MS)).getValue(defaultRecvValue));
}
/**
* Test that an outgoing connection times out
*/
@Test
public void testOutgoingConnectTimeout() throws Exception {
// Update the device priority so okToConnect() returns true
when(mDatabaseManager
.getProfileConnectionPolicy(mLeftDevice, BluetoothProfile.HEARING_AID))
.thenReturn(BluetoothProfile.CONNECTION_POLICY_ALLOWED);
when(mDatabaseManager
.getProfileConnectionPolicy(mRightDevice, BluetoothProfile.HEARING_AID))
.thenReturn(BluetoothProfile.CONNECTION_POLICY_FORBIDDEN);
when(mDatabaseManager
.getProfileConnectionPolicy(mSingleDevice, BluetoothProfile.HEARING_AID))
.thenReturn(BluetoothProfile.CONNECTION_POLICY_FORBIDDEN);
doReturn(true).when(mNativeInterface).connectHearingAid(any(BluetoothDevice.class));
doReturn(true).when(mNativeInterface).disconnectHearingAid(any(BluetoothDevice.class));
// Send a connect request
Assert.assertTrue("Connect failed", mService.connect(mLeftDevice));
// Verify the connection state broadcast, and that we are in Connecting state
verifyConnectionStateIntent(TIMEOUT_MS, mLeftDevice, BluetoothProfile.STATE_CONNECTING,
BluetoothProfile.STATE_DISCONNECTED);
// indirect call of mService.getConnectionState to test BluetoothHearingAidBinder
final SynchronousResultReceiver<Integer> recv = SynchronousResultReceiver.get();
int defaultRecvValue = -1000;
mServiceBinder.getConnectionState(mLeftDevice, null, recv);
int connectionState = recv.awaitResultNoInterrupt(Duration.ofMillis(TIMEOUT_MS))
.getValue(defaultRecvValue);
Assert.assertEquals(BluetoothProfile.STATE_CONNECTING, connectionState);
// Verify the connection state broadcast, and that we are in Disconnected state
verifyConnectionStateIntent(HearingAidStateMachine.sConnectTimeoutMs * 2,
mLeftDevice, BluetoothProfile.STATE_DISCONNECTED,
BluetoothProfile.STATE_CONNECTING);
Assert.assertEquals(BluetoothProfile.STATE_DISCONNECTED,
mService.getConnectionState(mLeftDevice));
}
/**
* Test that the Hearing Aid Service connects to left and right device at the same time.
*/
@Test
public void testConnectAPair_connectBothDevices() {
// Update hiSyncId map
getHiSyncIdFromNative();
// Update the device priority so okToConnect() returns true
when(mDatabaseManager
.getProfileConnectionPolicy(mLeftDevice, BluetoothProfile.HEARING_AID))
.thenReturn(BluetoothProfile.CONNECTION_POLICY_ALLOWED);
when(mDatabaseManager
.getProfileConnectionPolicy(mRightDevice, BluetoothProfile.HEARING_AID))
.thenReturn(BluetoothProfile.CONNECTION_POLICY_ALLOWED);
when(mDatabaseManager
.getProfileConnectionPolicy(mSingleDevice, BluetoothProfile.HEARING_AID))
.thenReturn(BluetoothProfile.CONNECTION_POLICY_FORBIDDEN);
doReturn(true).when(mNativeInterface).connectHearingAid(any(BluetoothDevice.class));
doReturn(true).when(mNativeInterface).disconnectHearingAid(any(BluetoothDevice.class));
// Send a connect request
Assert.assertTrue("Connect failed", mService.connect(mLeftDevice));
// Verify the connection state broadcast, and that we are in Connecting state
verifyConnectionStateIntent(TIMEOUT_MS, mLeftDevice, BluetoothProfile.STATE_CONNECTING,
BluetoothProfile.STATE_DISCONNECTED);
Assert.assertEquals(BluetoothProfile.STATE_CONNECTING,
mService.getConnectionState(mLeftDevice));
verifyConnectionStateIntent(TIMEOUT_MS, mRightDevice, BluetoothProfile.STATE_CONNECTING,
BluetoothProfile.STATE_DISCONNECTED);
Assert.assertEquals(BluetoothProfile.STATE_CONNECTING,
mService.getConnectionState(mRightDevice));
}
/**
* Test that the service disconnects the current pair before connecting to another pair.
*/
@Test
public void testConnectAnotherPair_disconnectCurrentPair() throws Exception {
// Update hiSyncId map
getHiSyncIdFromNative();
// Update the device priority so okToConnect() returns true
when(mDatabaseManager
.getProfileConnectionPolicy(mLeftDevice, BluetoothProfile.HEARING_AID))
.thenReturn(BluetoothProfile.CONNECTION_POLICY_ALLOWED);
when(mDatabaseManager
.getProfileConnectionPolicy(mRightDevice, BluetoothProfile.HEARING_AID))
.thenReturn(BluetoothProfile.CONNECTION_POLICY_ALLOWED);
when(mDatabaseManager
.getProfileConnectionPolicy(mSingleDevice, BluetoothProfile.HEARING_AID))
.thenReturn(BluetoothProfile.CONNECTION_POLICY_ALLOWED);
doReturn(true).when(mNativeInterface).connectHearingAid(any(BluetoothDevice.class));
doReturn(true).when(mNativeInterface).disconnectHearingAid(any(BluetoothDevice.class));
// Send a connect request
Assert.assertTrue("Connect failed", mService.connect(mLeftDevice));
// Verify the connection state broadcast, and that we are in Connecting state
verifyConnectionStateIntent(TIMEOUT_MS, mLeftDevice, BluetoothProfile.STATE_CONNECTING,
BluetoothProfile.STATE_DISCONNECTED);
verifyConnectionStateIntent(TIMEOUT_MS, mRightDevice, BluetoothProfile.STATE_CONNECTING,
BluetoothProfile.STATE_DISCONNECTED);
HearingAidStackEvent connCompletedEvent;
// Send a message to trigger connection completed
connCompletedEvent = new HearingAidStackEvent(
HearingAidStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED);
connCompletedEvent.device = mLeftDevice;
connCompletedEvent.valueInt1 = HearingAidStackEvent.CONNECTION_STATE_CONNECTED;
mService.messageFromNative(connCompletedEvent);
connCompletedEvent = new HearingAidStackEvent(
HearingAidStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED);
connCompletedEvent.device = mRightDevice;
connCompletedEvent.valueInt1 = HearingAidStackEvent.CONNECTION_STATE_CONNECTED;
mService.messageFromNative(connCompletedEvent);
// Verify the connection state broadcast, and that we are in Connected state for right side
verifyConnectionStateIntent(TIMEOUT_MS, mLeftDevice, BluetoothProfile.STATE_CONNECTED,
BluetoothProfile.STATE_CONNECTING);
verifyConnectionStateIntent(TIMEOUT_MS, mRightDevice, BluetoothProfile.STATE_CONNECTED,
BluetoothProfile.STATE_CONNECTING);
// Send a connect request for another pair
Assert.assertTrue("Connect failed", mService.connect(mSingleDevice));
// Verify the connection state broadcast, and that the first pair is in Disconnecting state
verifyConnectionStateIntent(TIMEOUT_MS, mLeftDevice, BluetoothProfile.STATE_DISCONNECTING,
BluetoothProfile.STATE_CONNECTED);
verifyConnectionStateIntent(TIMEOUT_MS, mRightDevice, BluetoothProfile.STATE_DISCONNECTING,
BluetoothProfile.STATE_CONNECTED);
// indirect call of mService.getConnectedDevices to test BluetoothHearingAidBinder
final SynchronousResultReceiver<List<BluetoothDevice>> recv =
SynchronousResultReceiver.get();
List<BluetoothDevice> defaultRecvValue = null;
mServiceBinder.getConnectedDevices(null, recv);
Assert.assertFalse(recv.awaitResultNoInterrupt(Duration.ofMillis(TIMEOUT_MS))
.getValue(defaultRecvValue).contains(mLeftDevice));
Assert.assertFalse(mService.getConnectedDevices().contains(mRightDevice));
// Verify the connection state broadcast, and that the second device is in Connecting state
verifyConnectionStateIntent(TIMEOUT_MS, mSingleDevice, BluetoothProfile.STATE_CONNECTING,
BluetoothProfile.STATE_DISCONNECTED);
Assert.assertEquals(BluetoothProfile.STATE_CONNECTING,
mService.getConnectionState(mSingleDevice));
}
/**
* Test that the outgoing connect/disconnect and audio switch is successful.
*/
@Test
public void testAudioManagerConnectDisconnect() throws Exception {
// Update hiSyncId map
getHiSyncIdFromNative();
// Update the device priority so okToConnect() returns true
when(mDatabaseManager
.getProfileConnectionPolicy(mLeftDevice, BluetoothProfile.HEARING_AID))
.thenReturn(BluetoothProfile.CONNECTION_POLICY_ALLOWED);
when(mDatabaseManager
.getProfileConnectionPolicy(mRightDevice, BluetoothProfile.HEARING_AID))
.thenReturn(BluetoothProfile.CONNECTION_POLICY_ALLOWED);
when(mDatabaseManager
.getProfileConnectionPolicy(mSingleDevice, BluetoothProfile.HEARING_AID))
.thenReturn(BluetoothProfile.CONNECTION_POLICY_FORBIDDEN);
doReturn(true).when(mNativeInterface).connectHearingAid(any(BluetoothDevice.class));
doReturn(true).when(mNativeInterface).disconnectHearingAid(any(BluetoothDevice.class));
// Send a connect request
Assert.assertTrue("Connect failed", mService.connect(mLeftDevice));
Assert.assertTrue("Connect failed", mService.connect(mRightDevice));
// Verify the connection state broadcast, and that we are in Connecting state
verifyConnectionStateIntent(TIMEOUT_MS, mLeftDevice, BluetoothProfile.STATE_CONNECTING,
BluetoothProfile.STATE_DISCONNECTED);
Assert.assertEquals(BluetoothProfile.STATE_CONNECTING,
mService.getConnectionState(mLeftDevice));
verifyConnectionStateIntent(TIMEOUT_MS, mRightDevice, BluetoothProfile.STATE_CONNECTING,
BluetoothProfile.STATE_DISCONNECTED);
Assert.assertEquals(BluetoothProfile.STATE_CONNECTING,
mService.getConnectionState(mRightDevice));
HearingAidStackEvent connCompletedEvent;
// Send a message to trigger connection completed
connCompletedEvent = new HearingAidStackEvent(
HearingAidStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED);
connCompletedEvent.device = mLeftDevice;
connCompletedEvent.valueInt1 = HearingAidStackEvent.CONNECTION_STATE_CONNECTED;
mService.messageFromNative(connCompletedEvent);
// Verify the connection state broadcast, and that we are in Connected state
verifyConnectionStateIntent(TIMEOUT_MS, mLeftDevice, BluetoothProfile.STATE_CONNECTED,
BluetoothProfile.STATE_CONNECTING);
Assert.assertEquals(BluetoothProfile.STATE_CONNECTED,
mService.getConnectionState(mLeftDevice));
// Send a message to trigger connection completed for right side
connCompletedEvent = new HearingAidStackEvent(
HearingAidStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED);
connCompletedEvent.device = mRightDevice;
connCompletedEvent.valueInt1 = HearingAidStackEvent.CONNECTION_STATE_CONNECTED;
mService.messageFromNative(connCompletedEvent);
// Verify the connection state broadcast, and that we are in Connected state for right side
verifyConnectionStateIntent(TIMEOUT_MS, mRightDevice, BluetoothProfile.STATE_CONNECTED,
BluetoothProfile.STATE_CONNECTING);
Assert.assertEquals(BluetoothProfile.STATE_CONNECTED,
mService.getConnectionState(mRightDevice));
// Verify the list of connected devices
Assert.assertTrue(mService.getConnectedDevices().contains(mLeftDevice));
Assert.assertTrue(mService.getConnectedDevices().contains(mRightDevice));
// Verify the audio is routed to Hearing Aid Profile
verify(mAudioManager).handleBluetoothActiveDeviceChanged(
eq(mLeftDevice), eq(null), any(BluetoothProfileConnectionInfo.class));
// Send a disconnect request
Assert.assertTrue("Disconnect failed", mService.disconnect(mLeftDevice));
// Send a disconnect request via BluetoothHearingAidBinder
final SynchronousResultReceiver<Boolean> recv = SynchronousResultReceiver.get();
boolean revalueRecvValue = false;
mServiceBinder.disconnect(mRightDevice, null, recv);
Assert.assertTrue("Disconnect failed", recv.awaitResultNoInterrupt(
Duration.ofMillis(TIMEOUT_MS)).getValue(revalueRecvValue));
// Verify the connection state broadcast, and that we are in Disconnecting state
verifyConnectionStateIntent(TIMEOUT_MS, mLeftDevice, BluetoothProfile.STATE_DISCONNECTING,
BluetoothProfile.STATE_CONNECTED);
Assert.assertEquals(BluetoothProfile.STATE_DISCONNECTING,
mService.getConnectionState(mLeftDevice));
verifyConnectionStateIntent(TIMEOUT_MS, mRightDevice, BluetoothProfile.STATE_DISCONNECTING,
BluetoothProfile.STATE_CONNECTED);
Assert.assertEquals(BluetoothProfile.STATE_DISCONNECTING,
mService.getConnectionState(mRightDevice));
// Send a message to trigger disconnection completed
connCompletedEvent = new HearingAidStackEvent(
HearingAidStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED);
connCompletedEvent.device = mLeftDevice;
connCompletedEvent.valueInt1 = HearingAidStackEvent.CONNECTION_STATE_DISCONNECTED;
mService.messageFromNative(connCompletedEvent);
// Verify the connection state broadcast, and that we are in Disconnected state
verifyConnectionStateIntent(TIMEOUT_MS, mLeftDevice, BluetoothProfile.STATE_DISCONNECTED,
BluetoothProfile.STATE_DISCONNECTING);
Assert.assertEquals(BluetoothProfile.STATE_DISCONNECTED,
mService.getConnectionState(mLeftDevice));
// Send a message to trigger disconnection completed to the right device
connCompletedEvent = new HearingAidStackEvent(
HearingAidStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED);
connCompletedEvent.device = mRightDevice;
connCompletedEvent.valueInt1 = HearingAidStackEvent.CONNECTION_STATE_DISCONNECTED;
mService.messageFromNative(connCompletedEvent);
// Verify the connection state broadcast, and that we are in Disconnected state
verifyConnectionStateIntent(TIMEOUT_MS, mRightDevice, BluetoothProfile.STATE_DISCONNECTED,
BluetoothProfile.STATE_DISCONNECTING);
Assert.assertEquals(BluetoothProfile.STATE_DISCONNECTED,
mService.getConnectionState(mRightDevice));
// Verify the list of connected devices
Assert.assertFalse(mService.getConnectedDevices().contains(mLeftDevice));
Assert.assertFalse(mService.getConnectedDevices().contains(mRightDevice));
// Verify the audio is not routed to Hearing Aid Profile.
// Music should be paused (i.e. should not suppress noisy intent)
ArgumentCaptor<BluetoothProfileConnectionInfo> connectionInfoArgumentCaptor =
ArgumentCaptor.forClass(BluetoothProfileConnectionInfo.class);
verify(mAudioManager).handleBluetoothActiveDeviceChanged(
eq(null), eq(mLeftDevice), connectionInfoArgumentCaptor.capture());
BluetoothProfileConnectionInfo connectionInfo = connectionInfoArgumentCaptor.getValue();
Assert.assertFalse(connectionInfo.isSuppressNoisyIntent());
}
/**
* Test that the noisy intent is suppressed when we call HearingAidService#removeActiveDevice()
* with (stopAudio == false).
*/
@Test
public void testAudioManagerConnectDisconnect_suppressNoisyIntentCase() throws Exception {
// Update hiSyncId map
getHiSyncIdFromNative();
// Update the device priority so okToConnect() returns true
when(mDatabaseManager
.getProfileConnectionPolicy(mLeftDevice, BluetoothProfile.HEARING_AID))
.thenReturn(BluetoothProfile.CONNECTION_POLICY_ALLOWED);
doReturn(true).when(mNativeInterface).connectHearingAid(any(BluetoothDevice.class));
doReturn(true).when(mNativeInterface).disconnectHearingAid(any(BluetoothDevice.class));
// Send a connect request
Assert.assertTrue("Connect failed", mService.connect(mLeftDevice));
// Verify the connection state broadcast, and that we are in Connecting state
verifyConnectionStateIntent(TIMEOUT_MS, mLeftDevice, BluetoothProfile.STATE_CONNECTING,
BluetoothProfile.STATE_DISCONNECTED);
Assert.assertEquals(BluetoothProfile.STATE_CONNECTING,
mService.getConnectionState(mLeftDevice));
HearingAidStackEvent connCompletedEvent;
// Send a message to trigger connection completed
connCompletedEvent = new HearingAidStackEvent(
HearingAidStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED);
connCompletedEvent.device = mLeftDevice;
connCompletedEvent.valueInt1 = HearingAidStackEvent.CONNECTION_STATE_CONNECTED;
mService.messageFromNative(connCompletedEvent);
// Verify the connection state broadcast, and that we are in Connected state
verifyConnectionStateIntent(TIMEOUT_MS, mLeftDevice, BluetoothProfile.STATE_CONNECTED,
BluetoothProfile.STATE_CONNECTING);
Assert.assertEquals(BluetoothProfile.STATE_CONNECTED,
mService.getConnectionState(mLeftDevice));
// Verify the list of connected devices
Assert.assertTrue(mService.getConnectedDevices().contains(mLeftDevice));
// Verify the audio is routed to Hearing Aid Profile
verify(mAudioManager).handleBluetoothActiveDeviceChanged(
eq(mLeftDevice), eq(null), any(BluetoothProfileConnectionInfo.class));
// Send a disconnect request
Assert.assertTrue("Disconnect failed", mService.disconnect(mLeftDevice));
// Verify the connection state broadcast, and that we are in Disconnecting state
// Note that we call verifyConnectionStateIntent() with (stopAudio == false).
verifyConnectionStateIntent(TIMEOUT_MS, mLeftDevice, BluetoothProfile.STATE_DISCONNECTING,
BluetoothProfile.STATE_CONNECTED, false);
Assert.assertEquals(BluetoothProfile.STATE_DISCONNECTING,
mService.getConnectionState(mLeftDevice));
// Verify the audio is not routed to Hearing Aid Profile.
// Note that music should be not paused (i.e. should suppress noisy intent)
ArgumentCaptor<BluetoothProfileConnectionInfo> connectionInfoArgumentCaptor =
ArgumentCaptor.forClass(BluetoothProfileConnectionInfo.class);
verify(mAudioManager).handleBluetoothActiveDeviceChanged(
eq(null), eq(mLeftDevice), connectionInfoArgumentCaptor.capture());
BluetoothProfileConnectionInfo connectionInfo = connectionInfoArgumentCaptor.getValue();
Assert.assertTrue(connectionInfo.isSuppressNoisyIntent());
}
/**
* Test that only CONNECTION_STATE_CONNECTED or CONNECTION_STATE_CONNECTING Hearing Aid stack
* events will create a state machine.
*/
@Test
public void testCreateStateMachineStackEvents() {
// Update the device priority so okToConnect() returns true
when(mDatabaseManager
.getProfileConnectionPolicy(mLeftDevice, BluetoothProfile.HEARING_AID))
.thenReturn(BluetoothProfile.CONNECTION_POLICY_ALLOWED);
when(mDatabaseManager
.getProfileConnectionPolicy(mRightDevice, BluetoothProfile.HEARING_AID))
.thenReturn(BluetoothProfile.CONNECTION_POLICY_FORBIDDEN);
when(mDatabaseManager
.getProfileConnectionPolicy(mSingleDevice, BluetoothProfile.HEARING_AID))
.thenReturn(BluetoothProfile.CONNECTION_POLICY_FORBIDDEN);
doReturn(true).when(mNativeInterface).connectHearingAid(any(BluetoothDevice.class));
doReturn(true).when(mNativeInterface).disconnectHearingAid(any(BluetoothDevice.class));
// Hearing Aid stack event: CONNECTION_STATE_CONNECTING - state machine should be created
generateConnectionMessageFromNative(mLeftDevice, BluetoothProfile.STATE_CONNECTING,
BluetoothProfile.STATE_DISCONNECTED);
Assert.assertEquals(BluetoothProfile.STATE_CONNECTING,
mService.getConnectionState(mLeftDevice));
Assert.assertTrue(mService.getDevices().contains(mLeftDevice));
// HearingAid stack event: CONNECTION_STATE_DISCONNECTED - state machine should be removed
generateConnectionMessageFromNative(mLeftDevice, BluetoothProfile.STATE_DISCONNECTED,
BluetoothProfile.STATE_CONNECTING);
Assert.assertEquals(BluetoothProfile.STATE_DISCONNECTED,
mService.getConnectionState(mLeftDevice));
Assert.assertTrue(mService.getDevices().contains(mLeftDevice));
mService.bondStateChanged(mLeftDevice, BluetoothDevice.BOND_NONE);
Assert.assertFalse(mService.getDevices().contains(mLeftDevice));
// stack event: CONNECTION_STATE_CONNECTED - state machine should be created
generateConnectionMessageFromNative(mLeftDevice, BluetoothProfile.STATE_CONNECTED,
BluetoothProfile.STATE_DISCONNECTED);
Assert.assertEquals(BluetoothProfile.STATE_CONNECTED,
mService.getConnectionState(mLeftDevice));
Assert.assertTrue(mService.getDevices().contains(mLeftDevice));
// stack event: CONNECTION_STATE_DISCONNECTED - state machine should be removed
generateConnectionMessageFromNative(mLeftDevice, BluetoothProfile.STATE_DISCONNECTED,
BluetoothProfile.STATE_CONNECTED);
Assert.assertEquals(BluetoothProfile.STATE_DISCONNECTED,
mService.getConnectionState(mLeftDevice));
Assert.assertTrue(mService.getDevices().contains(mLeftDevice));
mService.bondStateChanged(mLeftDevice, BluetoothDevice.BOND_NONE);
Assert.assertFalse(mService.getDevices().contains(mLeftDevice));
// stack event: CONNECTION_STATE_DISCONNECTING - state machine should not be created
generateUnexpectedConnectionMessageFromNative(mLeftDevice,
BluetoothProfile.STATE_DISCONNECTING,
BluetoothProfile.STATE_DISCONNECTED);
Assert.assertEquals(BluetoothProfile.STATE_DISCONNECTED,
mService.getConnectionState(mLeftDevice));
Assert.assertFalse(mService.getDevices().contains(mLeftDevice));
// stack event: CONNECTION_STATE_DISCONNECTED - state machine should not be created
generateUnexpectedConnectionMessageFromNative(mLeftDevice,
BluetoothProfile.STATE_DISCONNECTED,
BluetoothProfile.STATE_DISCONNECTED);
Assert.assertEquals(BluetoothProfile.STATE_DISCONNECTED,
mService.getConnectionState(mLeftDevice));
Assert.assertFalse(mService.getDevices().contains(mLeftDevice));
}
/**
* Test that a state machine in DISCONNECTED state is removed only after the device is unbond.
*/
@Test
public void testDeleteStateMachineUnbondEvents() {
// Update the device priority so okToConnect() returns true
when(mDatabaseManager
.getProfileConnectionPolicy(mLeftDevice, BluetoothProfile.HEARING_AID))
.thenReturn(BluetoothProfile.CONNECTION_POLICY_ALLOWED);
when(mDatabaseManager
.getProfileConnectionPolicy(mRightDevice, BluetoothProfile.HEARING_AID))
.thenReturn(BluetoothProfile.CONNECTION_POLICY_FORBIDDEN);
when(mDatabaseManager
.getProfileConnectionPolicy(mSingleDevice, BluetoothProfile.HEARING_AID))
.thenReturn(BluetoothProfile.CONNECTION_POLICY_FORBIDDEN);
doReturn(true).when(mNativeInterface).connectHearingAid(any(BluetoothDevice.class));
doReturn(true).when(mNativeInterface).disconnectHearingAid(any(BluetoothDevice.class));
// HearingAid stack event: CONNECTION_STATE_CONNECTING - state machine should be created
generateConnectionMessageFromNative(mLeftDevice, BluetoothProfile.STATE_CONNECTING,
BluetoothProfile.STATE_DISCONNECTED);
Assert.assertEquals(BluetoothProfile.STATE_CONNECTING,
mService.getConnectionState(mLeftDevice));
Assert.assertTrue(mService.getDevices().contains(mLeftDevice));
// Device unbond - state machine is not removed
mService.bondStateChanged(mLeftDevice, BluetoothDevice.BOND_NONE);
Assert.assertTrue(mService.getDevices().contains(mLeftDevice));
// HearingAid stack event: CONNECTION_STATE_CONNECTED - state machine is not removed
mService.bondStateChanged(mLeftDevice, BluetoothDevice.BOND_BONDED);
generateConnectionMessageFromNative(mLeftDevice, BluetoothProfile.STATE_CONNECTED,
BluetoothProfile.STATE_CONNECTING);
Assert.assertEquals(BluetoothProfile.STATE_CONNECTED,
mService.getConnectionState(mLeftDevice));
Assert.assertTrue(mService.getDevices().contains(mLeftDevice));
// Device unbond - state machine is not removed
mService.bondStateChanged(mLeftDevice, BluetoothDevice.BOND_NONE);
Assert.assertTrue(mService.getDevices().contains(mLeftDevice));
// HearingAid stack event: CONNECTION_STATE_DISCONNECTING - state machine is not removed
mService.bondStateChanged(mLeftDevice, BluetoothDevice.BOND_BONDED);
generateConnectionMessageFromNative(mLeftDevice, BluetoothProfile.STATE_DISCONNECTING,
BluetoothProfile.STATE_CONNECTED);
Assert.assertEquals(BluetoothProfile.STATE_DISCONNECTING,
mService.getConnectionState(mLeftDevice));
Assert.assertTrue(mService.getDevices().contains(mLeftDevice));
// Device unbond - state machine is not removed
mService.bondStateChanged(mLeftDevice, BluetoothDevice.BOND_NONE);
Assert.assertTrue(mService.getDevices().contains(mLeftDevice));
// HearingAid stack event: CONNECTION_STATE_DISCONNECTED - state machine is not removed
mService.bondStateChanged(mLeftDevice, BluetoothDevice.BOND_BONDED);
generateConnectionMessageFromNative(mLeftDevice, BluetoothProfile.STATE_DISCONNECTED,
BluetoothProfile.STATE_DISCONNECTING);
Assert.assertEquals(BluetoothProfile.STATE_DISCONNECTED,
mService.getConnectionState(mLeftDevice));
Assert.assertTrue(mService.getDevices().contains(mLeftDevice));
// Device unbond - state machine is removed
mService.bondStateChanged(mLeftDevice, BluetoothDevice.BOND_NONE);
Assert.assertFalse(mService.getDevices().contains(mLeftDevice));
}
/**
* Test that a CONNECTION_STATE_DISCONNECTED Hearing Aid stack event will remove the state
* machine only if the device is unbond.
*/
@Test
public void testDeleteStateMachineDisconnectEvents() {
// Update the device priority so okToConnect() returns true
when(mDatabaseManager
.getProfileConnectionPolicy(mLeftDevice, BluetoothProfile.HEARING_AID))
.thenReturn(BluetoothProfile.CONNECTION_POLICY_ALLOWED);
when(mDatabaseManager
.getProfileConnectionPolicy(mRightDevice, BluetoothProfile.HEARING_AID))
.thenReturn(BluetoothProfile.CONNECTION_POLICY_FORBIDDEN);
when(mDatabaseManager
.getProfileConnectionPolicy(mSingleDevice, BluetoothProfile.HEARING_AID))
.thenReturn(BluetoothProfile.CONNECTION_POLICY_FORBIDDEN);
doReturn(true).when(mNativeInterface).connectHearingAid(any(BluetoothDevice.class));
doReturn(true).when(mNativeInterface).disconnectHearingAid(any(BluetoothDevice.class));
// HearingAid stack event: CONNECTION_STATE_CONNECTING - state machine should be created
generateConnectionMessageFromNative(mLeftDevice, BluetoothProfile.STATE_CONNECTING,
BluetoothProfile.STATE_DISCONNECTED);
Assert.assertEquals(BluetoothProfile.STATE_CONNECTING,
mService.getConnectionState(mLeftDevice));
Assert.assertTrue(mService.getDevices().contains(mLeftDevice));
// HearingAid stack event: CONNECTION_STATE_DISCONNECTED - state machine is not removed
generateConnectionMessageFromNative(mLeftDevice, BluetoothProfile.STATE_DISCONNECTED,
BluetoothProfile.STATE_CONNECTING);
Assert.assertEquals(BluetoothProfile.STATE_DISCONNECTED,
mService.getConnectionState(mLeftDevice));
Assert.assertTrue(mService.getDevices().contains(mLeftDevice));
// HearingAid stack event: CONNECTION_STATE_CONNECTING - state machine remains
generateConnectionMessageFromNative(mLeftDevice, BluetoothProfile.STATE_CONNECTING,
BluetoothProfile.STATE_DISCONNECTED);
Assert.assertEquals(BluetoothProfile.STATE_CONNECTING,
mService.getConnectionState(mLeftDevice));
Assert.assertTrue(mService.getDevices().contains(mLeftDevice));
// Device bond state marked as unbond - state machine is not removed
doReturn(BluetoothDevice.BOND_NONE).when(mAdapterService)
.getBondState(any(BluetoothDevice.class));
Assert.assertTrue(mService.getDevices().contains(mLeftDevice));
// HearingAid stack event: CONNECTION_STATE_DISCONNECTED - state machine is removed
generateConnectionMessageFromNative(mLeftDevice, BluetoothProfile.STATE_DISCONNECTED,
BluetoothProfile.STATE_CONNECTING);
Assert.assertEquals(BluetoothProfile.STATE_DISCONNECTED,
mService.getConnectionState(mLeftDevice));
Assert.assertFalse(mService.getDevices().contains(mLeftDevice));
}
@Test
public void testConnectionStateChangedActiveDevice() throws Exception {
// Update hiSyncId map
getHiSyncIdFromNative();
// Update the device priority so okToConnect() returns true
when(mDatabaseManager
.getProfileConnectionPolicy(mLeftDevice, BluetoothProfile.HEARING_AID))
.thenReturn(BluetoothProfile.CONNECTION_POLICY_ALLOWED);
when(mDatabaseManager
.getProfileConnectionPolicy(mRightDevice, BluetoothProfile.HEARING_AID))
.thenReturn(BluetoothProfile.CONNECTION_POLICY_ALLOWED);
when(mDatabaseManager
.getProfileConnectionPolicy(mSingleDevice, BluetoothProfile.HEARING_AID))
.thenReturn(BluetoothProfile.CONNECTION_POLICY_FORBIDDEN);
generateConnectionMessageFromNative(mRightDevice, BluetoothProfile.STATE_CONNECTED,
BluetoothProfile.STATE_DISCONNECTED);
Assert.assertTrue(mService.getActiveDevices().contains(mRightDevice));
// indirect call of mService.getActiveDevices to test BluetoothHearingAidBinder
final SynchronousResultReceiver<List<BluetoothDevice>> recv =
SynchronousResultReceiver.get();
List<BluetoothDevice> defaultRecvValue = null;
mServiceBinder.getActiveDevices(null, recv);
Assert.assertFalse(recv.awaitResultNoInterrupt(Duration.ofMillis(TIMEOUT_MS))
.getValue(defaultRecvValue).contains(mLeftDevice));
generateConnectionMessageFromNative(mLeftDevice, BluetoothProfile.STATE_CONNECTED,
BluetoothProfile.STATE_DISCONNECTED);
Assert.assertTrue(mService.getActiveDevices().contains(mRightDevice));
Assert.assertTrue(mService.getActiveDevices().contains(mLeftDevice));
generateConnectionMessageFromNative(mRightDevice, BluetoothProfile.STATE_DISCONNECTED,
BluetoothProfile.STATE_CONNECTED);
Assert.assertFalse(mService.getActiveDevices().contains(mRightDevice));
Assert.assertTrue(mService.getActiveDevices().contains(mLeftDevice));
generateConnectionMessageFromNative(mLeftDevice, BluetoothProfile.STATE_DISCONNECTED,
BluetoothProfile.STATE_CONNECTED);
Assert.assertFalse(mService.getActiveDevices().contains(mRightDevice));
Assert.assertFalse(mService.getActiveDevices().contains(mLeftDevice));
}
@Test
public void testConnectionStateChangedAnotherActiveDevice() throws Exception {
// Update hiSyncId map
getHiSyncIdFromNative();
// Update the device priority so okToConnect() returns true
when(mDatabaseManager
.getProfileConnectionPolicy(mLeftDevice, BluetoothProfile.HEARING_AID))
.thenReturn(BluetoothProfile.CONNECTION_POLICY_ALLOWED);
when(mDatabaseManager
.getProfileConnectionPolicy(mRightDevice, BluetoothProfile.HEARING_AID))
.thenReturn(BluetoothProfile.CONNECTION_POLICY_ALLOWED);
when(mDatabaseManager
.getProfileConnectionPolicy(mSingleDevice, BluetoothProfile.HEARING_AID))
.thenReturn(BluetoothProfile.CONNECTION_POLICY_ALLOWED);
generateConnectionMessageFromNative(mRightDevice, BluetoothProfile.STATE_CONNECTED,
BluetoothProfile.STATE_DISCONNECTED);
Assert.assertTrue(mService.getActiveDevices().contains(mRightDevice));
Assert.assertFalse(mService.getActiveDevices().contains(mLeftDevice));
generateConnectionMessageFromNative(mLeftDevice, BluetoothProfile.STATE_CONNECTED,
BluetoothProfile.STATE_DISCONNECTED);
Assert.assertTrue(mService.getActiveDevices().contains(mRightDevice));
Assert.assertTrue(mService.getActiveDevices().contains(mLeftDevice));
generateConnectionMessageFromNative(mSingleDevice, BluetoothProfile.STATE_CONNECTED,
BluetoothProfile.STATE_DISCONNECTED);
Assert.assertFalse(mService.getActiveDevices().contains(mRightDevice));
Assert.assertFalse(mService.getActiveDevices().contains(mLeftDevice));
Assert.assertTrue(mService.getActiveDevices().contains(mSingleDevice));
final SynchronousResultReceiver<Boolean> recv = SynchronousResultReceiver.get();
boolean defaultRecvValue = false;
mServiceBinder.setActiveDevice(null, null, recv);
Assert.assertTrue(recv.awaitResultNoInterrupt(Duration.ofMillis(TIMEOUT_MS))
.getValue(defaultRecvValue));
Assert.assertFalse(mService.getActiveDevices().contains(mSingleDevice));
}
/**
* Verify the correctness during first time connection.
* Connect to left device -> Get left device hiSyncId -> Connect to right device ->
* Get right device hiSyncId -> Both devices should be always connected
*/
@Test
public void firstTimeConnection_shouldConnectToBothDevices() {
// Update the device priority so okToConnect() returns true
when(mDatabaseManager
.getProfileConnectionPolicy(mLeftDevice, BluetoothProfile.HEARING_AID))
.thenReturn(BluetoothProfile.CONNECTION_POLICY_ALLOWED);
when(mDatabaseManager
.getProfileConnectionPolicy(mRightDevice, BluetoothProfile.HEARING_AID))
.thenReturn(BluetoothProfile.CONNECTION_POLICY_ALLOWED);
doReturn(true).when(mNativeInterface).connectHearingAid(any(BluetoothDevice.class));
doReturn(true).when(mNativeInterface).disconnectHearingAid(any(BluetoothDevice.class));
// Send a connect request for left device
Assert.assertTrue("Connect failed", mService.connect(mLeftDevice));
// Verify the connection state broadcast, and that we are in Connecting state
verifyConnectionStateIntent(TIMEOUT_MS, mLeftDevice, BluetoothProfile.STATE_CONNECTING,
BluetoothProfile.STATE_DISCONNECTED);
Assert.assertEquals(BluetoothProfile.STATE_CONNECTING,
mService.getConnectionState(mLeftDevice));
HearingAidStackEvent connCompletedEvent;
// Send a message to trigger connection completed
connCompletedEvent = new HearingAidStackEvent(
HearingAidStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED);
connCompletedEvent.device = mLeftDevice;
connCompletedEvent.valueInt1 = HearingAidStackEvent.CONNECTION_STATE_CONNECTED;
mService.messageFromNative(connCompletedEvent);
// Verify the connection state broadcast, and that we are in Connected state
verifyConnectionStateIntent(TIMEOUT_MS, mLeftDevice, BluetoothProfile.STATE_CONNECTED,
BluetoothProfile.STATE_CONNECTING);
Assert.assertEquals(BluetoothProfile.STATE_CONNECTED,
mService.getConnectionState(mLeftDevice));
// Get hiSyncId for left device
HearingAidStackEvent hiSyncIdEvent = new HearingAidStackEvent(
HearingAidStackEvent.EVENT_TYPE_DEVICE_AVAILABLE);
hiSyncIdEvent.device = mLeftDevice;
hiSyncIdEvent.valueInt1 = 0x02;
hiSyncIdEvent.valueLong2 = 0x0101;
mService.messageFromNative(hiSyncIdEvent);
// Send a connect request for right device
Assert.assertTrue("Connect failed", mService.connect(mRightDevice));
// Verify the connection state broadcast, and that we are in Connecting state
verifyConnectionStateIntent(TIMEOUT_MS, mRightDevice, BluetoothProfile.STATE_CONNECTING,
BluetoothProfile.STATE_DISCONNECTED);
Assert.assertEquals(BluetoothProfile.STATE_CONNECTING,
mService.getConnectionState(mRightDevice));
// Verify the left device is still connected
Assert.assertEquals(BluetoothProfile.STATE_CONNECTED,
mService.getConnectionState(mLeftDevice));
// Send a message to trigger connection completed
connCompletedEvent = new HearingAidStackEvent(
HearingAidStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED);
connCompletedEvent.device = mRightDevice;
connCompletedEvent.valueInt1 = HearingAidStackEvent.CONNECTION_STATE_CONNECTED;
mService.messageFromNative(connCompletedEvent);
// Verify the connection state broadcast, and that we are in Connected state
verifyConnectionStateIntent(TIMEOUT_MS, mRightDevice, BluetoothProfile.STATE_CONNECTED,
BluetoothProfile.STATE_CONNECTING);
Assert.assertEquals(BluetoothProfile.STATE_CONNECTED,
mService.getConnectionState(mRightDevice));
Assert.assertEquals(BluetoothProfile.STATE_CONNECTED,
mService.getConnectionState(mLeftDevice));
// Get hiSyncId for right device
hiSyncIdEvent = new HearingAidStackEvent(
HearingAidStackEvent.EVENT_TYPE_DEVICE_AVAILABLE);
hiSyncIdEvent.device = mRightDevice;
hiSyncIdEvent.valueInt1 = 0x02;
hiSyncIdEvent.valueLong2 = 0x0101;
mService.messageFromNative(hiSyncIdEvent);
Assert.assertEquals(BluetoothProfile.STATE_CONNECTED,
mService.getConnectionState(mRightDevice));
Assert.assertEquals(BluetoothProfile.STATE_CONNECTED,
mService.getConnectionState(mLeftDevice));
}
/**
* Get the HiSyncId from native stack after connecting to left device, then connect right
*/
@Test
public void getHiSyncId_afterFirstDeviceConnected() {
// Update the device priority so okToConnect() returns true
when(mDatabaseManager
.getProfileConnectionPolicy(mLeftDevice, BluetoothProfile.HEARING_AID))
.thenReturn(BluetoothProfile.CONNECTION_POLICY_ALLOWED);
when(mDatabaseManager
.getProfileConnectionPolicy(mRightDevice, BluetoothProfile.HEARING_AID))
.thenReturn(BluetoothProfile.CONNECTION_POLICY_ALLOWED);
when(mDatabaseManager
.getProfileConnectionPolicy(mSingleDevice, BluetoothProfile.HEARING_AID))
.thenReturn(BluetoothProfile.CONNECTION_POLICY_ALLOWED);
doReturn(true).when(mNativeInterface).connectHearingAid(any(BluetoothDevice.class));
doReturn(true).when(mNativeInterface).disconnectHearingAid(any(BluetoothDevice.class));
// Send a connect request
Assert.assertTrue("Connect failed", mService.connect(mLeftDevice));
// Verify the connection state broadcast, and that we are in Connecting state
verifyConnectionStateIntent(TIMEOUT_MS, mLeftDevice, BluetoothProfile.STATE_CONNECTING,
BluetoothProfile.STATE_DISCONNECTED);
Assert.assertEquals(BluetoothProfile.STATE_CONNECTING,
mService.getConnectionState(mLeftDevice));
Assert.assertEquals(BluetoothProfile.STATE_DISCONNECTED,
mService.getConnectionState(mRightDevice));
HearingAidStackEvent connCompletedEvent;
// Send a message to trigger connection completed
connCompletedEvent = new HearingAidStackEvent(
HearingAidStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED);
connCompletedEvent.device = mLeftDevice;
connCompletedEvent.valueInt1 = HearingAidStackEvent.CONNECTION_STATE_CONNECTED;
mService.messageFromNative(connCompletedEvent);
// Verify the connection state broadcast, and that we are in Connected state
verifyConnectionStateIntent(TIMEOUT_MS, mLeftDevice, BluetoothProfile.STATE_CONNECTED,
BluetoothProfile.STATE_CONNECTING);
Assert.assertEquals(BluetoothProfile.STATE_CONNECTED,
mService.getConnectionState(mLeftDevice));
// Get hiSyncId update from native stack
getHiSyncIdFromNative();
// Send a connect request for right
Assert.assertTrue("Connect failed", mService.connect(mRightDevice));
// Verify the connection state broadcast, and that we are in Connecting state
verifyConnectionStateIntent(TIMEOUT_MS, mRightDevice, BluetoothProfile.STATE_CONNECTING,
BluetoothProfile.STATE_DISCONNECTED);
Assert.assertEquals(BluetoothProfile.STATE_CONNECTING,
mService.getConnectionState(mRightDevice));
// Verify the left device is still connected
Assert.assertEquals(BluetoothProfile.STATE_CONNECTED,
mService.getConnectionState(mLeftDevice));
// Send a message to trigger connection completed
connCompletedEvent = new HearingAidStackEvent(
HearingAidStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED);
connCompletedEvent.device = mRightDevice;
connCompletedEvent.valueInt1 = HearingAidStackEvent.CONNECTION_STATE_CONNECTED;
mService.messageFromNative(connCompletedEvent);
// Verify the connection state broadcast, and that we are in Connected state
verifyConnectionStateIntent(TIMEOUT_MS, mRightDevice, BluetoothProfile.STATE_CONNECTED,
BluetoothProfile.STATE_CONNECTING);
Assert.assertEquals(BluetoothProfile.STATE_CONNECTED,
mService.getConnectionState(mRightDevice));
Assert.assertEquals(BluetoothProfile.STATE_CONNECTED,
mService.getConnectionState(mLeftDevice));
}
/**
* Test that the service can update HiSyncId from native message
*/
@Test
public void getHiSyncIdFromNative_addToMap() throws Exception {
getHiSyncIdFromNative();
Assert.assertTrue("hiSyncIdMap should contain mLeftDevice",
mService.getHiSyncIdMap().containsKey(mLeftDevice));
Assert.assertTrue("hiSyncIdMap should contain mRightDevice",
mService.getHiSyncIdMap().containsKey(mRightDevice));
Assert.assertTrue("hiSyncIdMap should contain mSingleDevice",
mService.getHiSyncIdMap().containsKey(mSingleDevice));
SynchronousResultReceiver<Long> recv = SynchronousResultReceiver.get();
long defaultRecvValue = -1000;
mServiceBinder.getHiSyncId(mLeftDevice, null, recv);
long id = recv.awaitResultNoInterrupt(Duration.ofMillis(TIMEOUT_MS))
.getValue(defaultRecvValue);
Assert.assertNotEquals(BluetoothHearingAid.HI_SYNC_ID_INVALID, id);
recv = SynchronousResultReceiver.get();
mServiceBinder.getHiSyncId(mRightDevice, null, recv);
id = recv.awaitResultNoInterrupt(Duration.ofMillis(TIMEOUT_MS))
.getValue(defaultRecvValue);
Assert.assertNotEquals(BluetoothHearingAid.HI_SYNC_ID_INVALID, id);
recv = SynchronousResultReceiver.get();
mServiceBinder.getHiSyncId(mSingleDevice, null, recv);
id = recv.awaitResultNoInterrupt(Duration.ofMillis(TIMEOUT_MS))
.getValue(defaultRecvValue);
Assert.assertNotEquals(BluetoothHearingAid.HI_SYNC_ID_INVALID, id);
}
/**
* Test that the service removes the device from HiSyncIdMap when it's unbonded
*/
@Test
public void deviceUnbonded_removeHiSyncId() {
getHiSyncIdFromNative();
mService.bondStateChanged(mLeftDevice, BluetoothDevice.BOND_NONE);
Assert.assertFalse("hiSyncIdMap shouldn't contain mLeftDevice",
mService.getHiSyncIdMap().containsKey(mLeftDevice));
}
@Test
public void serviceBinder_callGetDeviceMode() throws Exception {
final SynchronousResultReceiver<Integer> recv = SynchronousResultReceiver.get();
mServiceBinder.getDeviceMode(mSingleDevice, null, recv);
int mode = recv.awaitResultNoInterrupt(Duration.ofMillis(TIMEOUT_MS))
.getValue(BluetoothHearingAid.MODE_UNKNOWN);
// return unknown value if no device connected
Assert.assertEquals(BluetoothHearingAid.MODE_UNKNOWN, mode);
}
@Test
public void serviceBinder_callGetDeviceSide() throws Exception {
final SynchronousResultReceiver<Integer> recv = SynchronousResultReceiver.get();
mServiceBinder.getDeviceSide(mSingleDevice, null, recv);
int side = recv.awaitResultNoInterrupt(Duration.ofMillis(TIMEOUT_MS))
.getValue(BluetoothHearingAid.SIDE_UNKNOWN);
// return unknown value if no device connected
Assert.assertEquals(BluetoothHearingAid.SIDE_UNKNOWN, side);
}
@Test
public void serviceBinder_setConnectionPolicy() throws Exception {
when(mDatabaseManager.setProfileConnectionPolicy(mSingleDevice,
BluetoothProfile.HEARING_AID, BluetoothProfile.CONNECTION_POLICY_UNKNOWN))
.thenReturn(true);
final SynchronousResultReceiver<Boolean> recv = SynchronousResultReceiver.get();
boolean defaultRecvValue = false;
mServiceBinder.setConnectionPolicy(mSingleDevice,
BluetoothProfile.CONNECTION_POLICY_UNKNOWN, null, recv);
Assert.assertTrue(recv.awaitResultNoInterrupt(Duration.ofMillis(TIMEOUT_MS))
.getValue(defaultRecvValue));
verify(mDatabaseManager).setProfileConnectionPolicy(mSingleDevice,
BluetoothProfile.HEARING_AID, BluetoothProfile.CONNECTION_POLICY_UNKNOWN);
}
@Test
public void serviceBinder_setVolume() throws Exception {
final SynchronousResultReceiver<Void> recv = SynchronousResultReceiver.get();
mServiceBinder.setVolume(0, null, recv);
recv.awaitResultNoInterrupt(Duration.ofMillis(TIMEOUT_MS));
verify(mNativeInterface).setVolume(0);
}
@Test
public void dump_doesNotCrash() {
// Update the device priority so okToConnect() returns true
when(mDatabaseManager
.getProfileConnectionPolicy(mSingleDevice, BluetoothProfile.HEARING_AID))
.thenReturn(BluetoothProfile.CONNECTION_POLICY_ALLOWED);
doReturn(true).when(mNativeInterface).connectHearingAid(any(BluetoothDevice.class));
// Send a connect request
mService.connect(mSingleDevice);
mService.dump(new StringBuilder());
}
private void connectDevice(BluetoothDevice device) {
HearingAidStackEvent connCompletedEvent;
List<BluetoothDevice> prevConnectedDevices = mService.getConnectedDevices();
// Update the device priority so okToConnect() returns true
when(mDatabaseManager.getProfileConnectionPolicy(device, BluetoothProfile.HEARING_AID))
.thenReturn(BluetoothProfile.CONNECTION_POLICY_ALLOWED);
doReturn(true).when(mNativeInterface).connectHearingAid(device);
doReturn(true).when(mNativeInterface).disconnectHearingAid(device);
// Send a connect request
Assert.assertTrue("Connect failed", mService.connect(device));
// Verify the connection state broadcast, and that we are in Connecting state
verifyConnectionStateIntent(TIMEOUT_MS, device, BluetoothProfile.STATE_CONNECTING,
BluetoothProfile.STATE_DISCONNECTED);
Assert.assertEquals(BluetoothProfile.STATE_CONNECTING,
mService.getConnectionState(device));
// Send a message to trigger connection completed
connCompletedEvent = new HearingAidStackEvent(
HearingAidStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED);
connCompletedEvent.device = device;
connCompletedEvent.valueInt1 = HearingAidStackEvent.CONNECTION_STATE_CONNECTED;
mService.messageFromNative(connCompletedEvent);
// Verify the connection state broadcast, and that we are in Connected state
verifyConnectionStateIntent(TIMEOUT_MS, device, BluetoothProfile.STATE_CONNECTED,
BluetoothProfile.STATE_CONNECTING);
Assert.assertEquals(BluetoothProfile.STATE_CONNECTED,
mService.getConnectionState(device));
// Verify that the device is in the list of connected devices
Assert.assertTrue(mService.getConnectedDevices().contains(device));
// Verify the list of previously connected devices
for (BluetoothDevice prevDevice : prevConnectedDevices) {
Assert.assertTrue(mService.getConnectedDevices().contains(prevDevice));
}
}
private void generateConnectionMessageFromNative(BluetoothDevice device, int newConnectionState,
int oldConnectionState) {
HearingAidStackEvent stackEvent =
new HearingAidStackEvent(HearingAidStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED);
stackEvent.device = device;
stackEvent.valueInt1 = newConnectionState;
mService.messageFromNative(stackEvent);
// Verify the connection state broadcast
verifyConnectionStateIntent(TIMEOUT_MS, device, newConnectionState, oldConnectionState);
}
private void generateUnexpectedConnectionMessageFromNative(BluetoothDevice device,
int newConnectionState, int oldConnectionState) {
HearingAidStackEvent stackEvent =
new HearingAidStackEvent(HearingAidStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED);
stackEvent.device = device;
stackEvent.valueInt1 = newConnectionState;
mService.messageFromNative(stackEvent);
// Verify the connection state broadcast
verifyNoConnectionStateIntent(TIMEOUT_MS, device);
}
/**
* Helper function to test okToConnect() method
*
* @param device test device
* @param bondState bond state value, could be invalid
* @param priority value, could be invalid, coudl be invalid
* @param expected expected result from okToConnect()
*/
private void testOkToConnectCase(BluetoothDevice device, int bondState, int priority,
boolean expected) {
doReturn(bondState).when(mAdapterService).getBondState(device);
when(mDatabaseManager.getProfileConnectionPolicy(device, BluetoothProfile.HEARING_AID))
.thenReturn(priority);
Assert.assertEquals(expected, mService.okToConnect(device));
}
private void getHiSyncIdFromNative() {
HearingAidStackEvent event = new HearingAidStackEvent(
HearingAidStackEvent.EVENT_TYPE_DEVICE_AVAILABLE);
event.device = mLeftDevice;
event.valueInt1 = 0x02;
event.valueLong2 = 0x0101;
mService.messageFromNative(event);
event.device = mRightDevice;
event.valueInt1 = 0x03;
mService.messageFromNative(event);
event.device = mSingleDevice;
event.valueInt1 = 0x00;
event.valueLong2 = 0x0102;
mService.messageFromNative(event);
}
}