blob: 898a05dd2f1a55cb5e4f9d9928580905b9de2f9b [file] [log] [blame]
/*
* Copyright 2021 HIMSA II K/S - www.himsa.com.
* Represented by EHIMA - www.ehima.com
*
* 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.hap;
import static org.mockito.Mockito.after;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.anyInt;
import static org.mockito.Mockito.anyString;
import static org.mockito.Mockito.doCallRealMethod;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothHapClient;
import android.bluetooth.BluetoothHapPresetInfo;
import android.bluetooth.BluetoothProfile;
import android.bluetooth.BluetoothStatusCodes;
import android.bluetooth.BluetoothUuid;
import android.bluetooth.IBluetoothHapClientCallback;
import android.content.AttributionSource;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Binder;
import android.os.Looper;
import android.os.ParcelUuid;
import android.os.RemoteException;
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.Utils;
import com.android.bluetooth.btservice.AdapterService;
import com.android.bluetooth.btservice.ServiceFactory;
import com.android.bluetooth.btservice.storage.DatabaseManager;
import com.android.bluetooth.csip.CsipSetCoordinatorService;
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.Mockito;
import org.mockito.MockitoAnnotations;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeoutException;
@MediumTest
@RunWith(AndroidJUnit4.class)
public class HapClientTest {
private final String mFlagDexmarker = System.getProperty("dexmaker.share_classloader", "false");
private static final int TIMEOUT_MS = 1000;
@Rule public final ServiceTestRule mServiceRule = new ServiceTestRule();
private BluetoothAdapter mAdapter;
private BluetoothDevice mDevice;
private BluetoothDevice mDevice2;
private BluetoothDevice mDevice3;
private Context mTargetContext;
private HapClientService mService;
private HapClientService.BluetoothHapClientBinder mServiceBinder;
private AttributionSource mAttributionSource;
private HasIntentReceiver mHasIntentReceiver;
private HashMap<BluetoothDevice, LinkedBlockingQueue<Intent>> mIntentQueue;
@Mock private AdapterService mAdapterService;
@Mock private DatabaseManager mDatabaseManager;
@Mock private HapClientNativeInterface mNativeInterface;
@Mock private ServiceFactory mServiceFactory;
@Mock private CsipSetCoordinatorService mCsipService;
@Mock private IBluetoothHapClientCallback mCallback;
@Mock private Binder mBinder;
@Before
public void setUp() throws Exception {
if (!mFlagDexmarker.equals("true")) {
System.setProperty("dexmaker.share_classloader", "true");
}
mTargetContext = InstrumentationRegistry.getTargetContext();
// Set up mocks and test assets
MockitoAnnotations.initMocks(this);
if (Looper.myLooper() == null) {
Looper.prepare();
}
HapClientStateMachine.sConnectTimeoutMs = TIMEOUT_MS;
TestUtils.setAdapterService(mAdapterService);
doReturn(mDatabaseManager).when(mAdapterService).getDatabase();
doReturn(true, false).when(mAdapterService).isStartedProfile(anyString());
mAdapter = BluetoothAdapter.getDefaultAdapter();
mAttributionSource = mAdapter.getAttributionSource();
HapClientNativeInterface.setInstance(mNativeInterface);
startService();
mService.mFactory = mServiceFactory;
doReturn(mCsipService).when(mServiceFactory).getCsipSetCoordinatorService();
mServiceBinder = (HapClientService.BluetoothHapClientBinder) mService.initBinder();
mServiceBinder.mIsTesting = true;
// Set up the State Changed receiver
IntentFilter filter = new IntentFilter();
filter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
filter.addAction(BluetoothHapClient.ACTION_HAP_CONNECTION_STATE_CHANGED);
filter.addAction(BluetoothHapClient.ACTION_HAP_DEVICE_AVAILABLE);
when(mCallback.asBinder()).thenReturn(mBinder);
mService.mCallbacks.register(mCallback);
mHasIntentReceiver = new HasIntentReceiver();
mTargetContext.registerReceiver(mHasIntentReceiver, filter, Context.RECEIVER_EXPORTED);
mDevice = TestUtils.getTestDevice(mAdapter, 0);
when(mNativeInterface.getDevice(getByteAddress(mDevice))).thenReturn(mDevice);
mDevice2 = TestUtils.getTestDevice(mAdapter, 1);
when(mNativeInterface.getDevice(getByteAddress(mDevice2))).thenReturn(mDevice2);
mDevice3 = TestUtils.getTestDevice(mAdapter, 2);
when(mNativeInterface.getDevice(getByteAddress(mDevice3))).thenReturn(mDevice3);
doCallRealMethod().when(mNativeInterface)
.sendMessageToService(any(HapClientStackEvent.class));
doCallRealMethod().when(mNativeInterface).onFeaturesUpdate(any(byte[].class), anyInt());
doCallRealMethod().when(mNativeInterface).onDeviceAvailable(any(byte[].class), anyInt());
doCallRealMethod().when(mNativeInterface)
.onActivePresetSelected(any(byte[].class), anyInt());
doCallRealMethod().when(mNativeInterface)
.onActivePresetSelectError(any(byte[].class), anyInt());
doCallRealMethod().when(mNativeInterface)
.onPresetNameSetError(any(byte[].class), anyInt(), anyInt());
doCallRealMethod().when(mNativeInterface)
.onPresetInfo(any(byte[].class), anyInt(), any(BluetoothHapPresetInfo[].class));
doCallRealMethod().when(mNativeInterface)
.onGroupPresetNameSetError(anyInt(), anyInt(), anyInt());
/* Prepare CAS groups */
doReturn(Arrays.asList(0x02, 0x03)).when(mCsipService).getAllGroupIds(BluetoothUuid.CAP);
int groupId2 = 0x02;
Map groups2 = new HashMap<Integer, ParcelUuid>();
groups2.put(groupId2, ParcelUuid.fromString("00001853-0000-1000-8000-00805F9B34FB"));
int groupId3 = 0x03;
Map groups3 = new HashMap<Integer, ParcelUuid>();
groups3.put(groupId3,
ParcelUuid.fromString("00001853-0000-1000-8000-00805F9B34FB"));
doReturn(Arrays.asList(mDevice, mDevice2)).when(mCsipService)
.getGroupDevicesOrdered(groupId2);
doReturn(groups2).when(mCsipService).getGroupUuidMapByDevice(mDevice);
doReturn(groups2).when(mCsipService).getGroupUuidMapByDevice(mDevice2);
doReturn(Arrays.asList(mDevice3)).when(mCsipService).getGroupDevicesOrdered(0x03);
doReturn(groups3).when(mCsipService).getGroupUuidMapByDevice(mDevice3);
doReturn(Arrays.asList(mDevice)).when(mCsipService).getGroupDevicesOrdered(0x01);
doReturn(BluetoothDevice.BOND_BONDED).when(mAdapterService)
.getBondState(any(BluetoothDevice.class));
doReturn(new ParcelUuid[]{BluetoothUuid.HAS}).when(mAdapterService)
.getRemoteUuids(any(BluetoothDevice.class));
doReturn(mDatabaseManager).when(mAdapterService).getDatabase();
mIntentQueue = new HashMap<>();
mIntentQueue.put(mDevice, new LinkedBlockingQueue<>());
mIntentQueue.put(mDevice2, new LinkedBlockingQueue<>());
mIntentQueue.put(mDevice3, new LinkedBlockingQueue<>());
}
@After
public void tearDown() throws Exception {
if (!mFlagDexmarker.equals("true")) {
System.setProperty("dexmaker.share_classloader", mFlagDexmarker);
}
if (mService == null) {
return;
}
mService.mCallbacks.unregister(mCallback);
stopService();
HapClientNativeInterface.setInstance(null);
if (mHasIntentReceiver != null) {
mTargetContext.unregisterReceiver(mHasIntentReceiver);
}
mAdapter = null;
if (mAdapterService != null) {
TestUtils.clearAdapterService(mAdapterService);
}
if (mIntentQueue != null)
mIntentQueue.clear();
}
private void startService() throws TimeoutException {
TestUtils.startService(mServiceRule, HapClientService.class);
mService = HapClientService.getHapClientService();
Assert.assertNotNull(mService);
}
private void stopService() throws TimeoutException {
TestUtils.stopService(mServiceRule, HapClientService.class);
mService = HapClientService.getHapClientService();
Assert.assertNull(mService);
}
/**
* Test getting HA Service Client
*/
@Test
public void testGetHapService() {
Assert.assertEquals(mService, HapClientService.getHapClientService());
}
/**
* Test stop HA Service Client
*/
@Test
public void testStopHapService() {
Assert.assertEquals(mService, HapClientService.getHapClientService());
InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
public void run() {
Assert.assertTrue(mService.stop());
}
});
InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
public void run() {
Assert.assertTrue(mService.start());
}
});
}
/**
* Test get/set policy for BluetoothDevice
*/
@Test
public void testGetSetPolicy() throws Exception {
when(mDatabaseManager
.getProfileConnectionPolicy(mDevice, BluetoothProfile.HAP_CLIENT))
.thenReturn(BluetoothProfile.CONNECTION_POLICY_UNKNOWN);
Assert.assertEquals("Initial device policy",
BluetoothProfile.CONNECTION_POLICY_UNKNOWN,
mService.getConnectionPolicy(mDevice));
when(mDatabaseManager
.getProfileConnectionPolicy(mDevice, BluetoothProfile.HAP_CLIENT))
.thenReturn(BluetoothProfile.CONNECTION_POLICY_FORBIDDEN);
Assert.assertEquals("Setting device policy to POLICY_FORBIDDEN",
BluetoothProfile.CONNECTION_POLICY_FORBIDDEN,
mService.getConnectionPolicy(mDevice));
when(mDatabaseManager
.getProfileConnectionPolicy(mDevice, BluetoothProfile.HAP_CLIENT))
.thenReturn(BluetoothProfile.CONNECTION_POLICY_ALLOWED);
// call getConnectionPolicy via binder
final SynchronousResultReceiver<Integer> recv = SynchronousResultReceiver.get();
int defaultRecvValue = -1000;
mServiceBinder.getConnectionPolicy(mDevice, mAttributionSource, recv);
int policy = recv.awaitResultNoInterrupt(Duration.ofMillis(TIMEOUT_MS))
.getValue(defaultRecvValue);
Assert.assertEquals("Setting device policy to POLICY_ALLOWED",
BluetoothProfile.CONNECTION_POLICY_ALLOWED, policy);
}
/**
* Test if getProfileConnectionPolicy works after the service is stopped.
*/
@Test
public void testGetPolicyAfterStopped() {
mService.stop();
when(mDatabaseManager
.getProfileConnectionPolicy(mDevice, BluetoothProfile.HAP_CLIENT))
.thenReturn(BluetoothProfile.CONNECTION_POLICY_UNKNOWN);
Assert.assertEquals("Initial device policy",
BluetoothProfile.CONNECTION_POLICY_UNKNOWN,
mService.getConnectionPolicy(mDevice));
}
/**
* Test okToConnect method using various test cases
*/
@Test
public void testOkToConnect() {
int badPolicyValue = 1024;
int badBondState = 42;
testOkToConnectCase(mDevice,
BluetoothDevice.BOND_NONE, BluetoothProfile.CONNECTION_POLICY_UNKNOWN, false);
testOkToConnectCase(mDevice,
BluetoothDevice.BOND_NONE, BluetoothProfile.CONNECTION_POLICY_FORBIDDEN, false);
testOkToConnectCase(mDevice,
BluetoothDevice.BOND_NONE, BluetoothProfile.CONNECTION_POLICY_ALLOWED, false);
testOkToConnectCase(mDevice,
BluetoothDevice.BOND_NONE, badPolicyValue, false);
testOkToConnectCase(mDevice,
BluetoothDevice.BOND_BONDING, BluetoothProfile.CONNECTION_POLICY_UNKNOWN, false);
testOkToConnectCase(mDevice,
BluetoothDevice.BOND_BONDING, BluetoothProfile.CONNECTION_POLICY_FORBIDDEN, false);
testOkToConnectCase(mDevice,
BluetoothDevice.BOND_BONDING, BluetoothProfile.CONNECTION_POLICY_ALLOWED, false);
testOkToConnectCase(mDevice,
BluetoothDevice.BOND_BONDING, badPolicyValue, false);
testOkToConnectCase(mDevice,
BluetoothDevice.BOND_BONDED, BluetoothProfile.CONNECTION_POLICY_UNKNOWN, true);
testOkToConnectCase(mDevice,
BluetoothDevice.BOND_BONDED, BluetoothProfile.CONNECTION_POLICY_FORBIDDEN, false);
testOkToConnectCase(mDevice,
BluetoothDevice.BOND_BONDED, BluetoothProfile.CONNECTION_POLICY_ALLOWED, true);
testOkToConnectCase(mDevice,
BluetoothDevice.BOND_BONDED, badPolicyValue, false);
testOkToConnectCase(mDevice,
badBondState, BluetoothProfile.CONNECTION_POLICY_UNKNOWN, false);
testOkToConnectCase(mDevice,
badBondState, BluetoothProfile.CONNECTION_POLICY_FORBIDDEN, false);
testOkToConnectCase(mDevice,
badBondState, BluetoothProfile.CONNECTION_POLICY_ALLOWED, false);
testOkToConnectCase(mDevice,
badBondState, badPolicyValue, false);
}
/**
* Test that an outgoing connection to device that does not have HAS UUID is rejected
*/
@Test
public void testOutgoingConnectMissingHasUuid() {
// Update the device policy so okToConnect() returns true
when(mDatabaseManager
.getProfileConnectionPolicy(mDevice, BluetoothProfile.HAP_CLIENT))
.thenReturn(BluetoothProfile.CONNECTION_POLICY_ALLOWED);
doReturn(true).when(mNativeInterface).connectHapClient(any(BluetoothDevice.class));
doReturn(true).when(mNativeInterface).disconnectHapClient(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(mDevice));
}
/**
* Test that an outgoing connection to device that have HAS UUID is successful
*/
@Test
public void testOutgoingConnectExistingHasUuid() {
// Update the device policy so okToConnect() returns true
when(mDatabaseManager
.getProfileConnectionPolicy(mDevice, BluetoothProfile.HAP_CLIENT))
.thenReturn(BluetoothProfile.CONNECTION_POLICY_ALLOWED);
doReturn(true).when(mNativeInterface).connectHapClient(any(BluetoothDevice.class));
doReturn(true).when(mNativeInterface).disconnectHapClient(any(BluetoothDevice.class));
doReturn(new ParcelUuid[]{BluetoothUuid.HAS}).when(mAdapterService)
.getRemoteUuids(any(BluetoothDevice.class));
// Send a connect request
Assert.assertTrue("Connect expected to succeed", mService.connect(mDevice));
}
/**
* Test that an outgoing connection to device with POLICY_FORBIDDEN is rejected
*/
@Test
public void testOutgoingConnectPolicyForbidden() {
doReturn(true).when(mNativeInterface).connectHapClient(any(BluetoothDevice.class));
doReturn(true).when(mNativeInterface).disconnectHapClient(any(BluetoothDevice.class));
// Set the device policy to POLICY_FORBIDDEN so connect() should fail
when(mDatabaseManager
.getProfileConnectionPolicy(mDevice, BluetoothProfile.HAP_CLIENT))
.thenReturn(BluetoothProfile.CONNECTION_POLICY_FORBIDDEN);
// Send a connect request
Assert.assertFalse("Connect expected to fail", mService.connect(mDevice));
}
/**
* Test that an outgoing connection times out
*/
@Test
public void testOutgoingConnectTimeout() throws Exception {
// Update the device policy so okToConnect() returns true
when(mDatabaseManager
.getProfileConnectionPolicy(mDevice, BluetoothProfile.HAP_CLIENT))
.thenReturn(BluetoothProfile.CONNECTION_POLICY_ALLOWED);
doReturn(true).when(mNativeInterface).connectHapClient(any(BluetoothDevice.class));
doReturn(true).when(mNativeInterface).disconnectHapClient(any(BluetoothDevice.class));
// Send a connect request
Assert.assertTrue("Connect failed", mService.connect(mDevice));
// Verify the connection state broadcast, and that we are in Connecting state
verifyConnectionStateIntent(TIMEOUT_MS, mDevice, BluetoothProfile.STATE_CONNECTING,
BluetoothProfile.STATE_DISCONNECTED);
Assert.assertEquals(BluetoothProfile.STATE_CONNECTING,
mService.getConnectionState(mDevice));
// Verify the connection state broadcast, and that we are in Disconnected state via binder
verifyConnectionStateIntent(HapClientStateMachine.sConnectTimeoutMs * 2,
mDevice, BluetoothProfile.STATE_DISCONNECTED, BluetoothProfile.STATE_CONNECTING);
final SynchronousResultReceiver<Integer> recv = SynchronousResultReceiver.get();
int defaultRecvValue = -1000;
mServiceBinder.getConnectionState(mDevice, mAttributionSource, recv);
int state = recv.awaitResultNoInterrupt(Duration.ofMillis(TIMEOUT_MS))
.getValue(defaultRecvValue);
Assert.assertEquals(BluetoothProfile.STATE_DISCONNECTED, state);
}
/**
* Test that an outgoing connection to two device that have HAS UUID is successful
*/
@Test
public void testConnectTwo() throws Exception {
doReturn(new ParcelUuid[]{BluetoothUuid.HAS}).when(mAdapterService)
.getRemoteUuids(any(BluetoothDevice.class));
// Send a connect request for the 1st device
testConnectingDevice(mDevice);
// Send a connect request for the 2nd device
BluetoothDevice Device2 = TestUtils.getTestDevice(mAdapter, 1);
testConnectingDevice(Device2);
// indirect call of mService.getConnectedDevices to test BluetoothHearingAidBinder
final SynchronousResultReceiver<List<BluetoothDevice>> recv =
SynchronousResultReceiver.get();
mServiceBinder.getConnectedDevices(mAttributionSource, recv);
List<BluetoothDevice> devices = recv.awaitResultNoInterrupt(Duration.ofMillis(TIMEOUT_MS))
.getValue(null);
Assert.assertTrue(devices.contains(mDevice));
Assert.assertTrue(devices.contains(Device2));
Assert.assertNotEquals(mDevice, Device2);
}
/**
* Test that for the unknown device the API calls are not forwarded down the stack to native.
*/
@Test
public void testCallsForNotConnectedDevice() {
Assert.assertEquals(BluetoothHapClient.PRESET_INDEX_UNAVAILABLE,
mService.getActivePresetIndex(mDevice));
}
/**
* Test getting HAS coordinated sets.
*/
@Test
public void testGetHapGroupCoordinatedOps() throws Exception {
doReturn(new ParcelUuid[]{BluetoothUuid.HAS}).when(mAdapterService)
.getRemoteUuids(any(BluetoothDevice.class));
testConnectingDevice(mDevice);
testConnectingDevice(mDevice2);
testConnectingDevice(mDevice3);
int flags = 0x04;
mNativeInterface.onFeaturesUpdate(getByteAddress(mDevice), flags);
int flags3 = 0x04;
mNativeInterface.onFeaturesUpdate(getByteAddress(mDevice3), flags);
/* This one has no coordinated operation support but is part of a coordinated set with
* mDevice, which supports it, thus mDevice will forward the operation to mDevice2.
* This device should also be rocognised as grouped one.
*/
int flags2 = 0;
mNativeInterface.onFeaturesUpdate(getByteAddress(mDevice2), flags2);
/* Two devices support coordinated operations thus shall report valid group ID */
Assert.assertEquals(2, mService.getHapGroup(mDevice));
Assert.assertEquals(3, mService.getHapGroup(mDevice3));
/* Third one has no coordinated operations support but is part of the group */
final SynchronousResultReceiver<Integer> recv = SynchronousResultReceiver.get();
int defaultRecvValue = -1000;
mServiceBinder.getHapGroup(mDevice2, mAttributionSource, recv);
int hapGroup = recv.awaitResultNoInterrupt(Duration.ofMillis(TIMEOUT_MS))
.getValue(defaultRecvValue);
Assert.assertEquals(2, hapGroup);
}
/**
* Test that selectPreset properly calls the native method.
*/
@Test
public void testSelectPresetNative() {
doReturn(new ParcelUuid[]{BluetoothUuid.HAS}).when(mAdapterService)
.getRemoteUuids(any(BluetoothDevice.class));
testConnectingDevice(mDevice);
// Verify Native Interface call
mService.selectPreset(mDevice, 0x00);
verify(mNativeInterface, times(0))
.selectActivePreset(eq(mDevice), eq(0x00));
try {
verify(mCallback, after(TIMEOUT_MS).times(1)).onPresetSelectionFailed(eq(mDevice),
eq(BluetoothStatusCodes.ERROR_HAP_INVALID_PRESET_INDEX));
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
mServiceBinder.selectPreset(mDevice, 0x01, mAttributionSource);
verify(mNativeInterface, times(1))
.selectActivePreset(eq(mDevice), eq(0x01));
}
/**
* Test that groupSelectActivePreset properly calls the native method.
*/
@Test
public void testGroupSelectActivePresetNative() {
doReturn(new ParcelUuid[]{BluetoothUuid.HAS}).when(mAdapterService)
.getRemoteUuids(any(BluetoothDevice.class));
testConnectingDevice(mDevice3);
int flags = 0x01;
mNativeInterface.onFeaturesUpdate(getByteAddress(mDevice3), flags);
// Verify Native Interface call
mService.selectPresetForGroup(0x03, 0x00);
try {
verify(mCallback, after(TIMEOUT_MS).times(1)).onPresetSelectionForGroupFailed(
eq(0x03), eq(BluetoothStatusCodes.ERROR_HAP_INVALID_PRESET_INDEX));
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
mServiceBinder.selectPresetForGroup(0x03, 0x01, mAttributionSource);
verify(mNativeInterface, times(1))
.groupSelectActivePreset(eq(0x03), eq(0x01));
}
/**
* Test that nextActivePreset properly calls the native method.
*/
@Test
public void testSwitchToNextPreset() {
doReturn(new ParcelUuid[]{BluetoothUuid.HAS}).when(mAdapterService)
.getRemoteUuids(any(BluetoothDevice.class));
testConnectingDevice(mDevice);
// Verify Native Interface call
mServiceBinder.switchToNextPreset(mDevice, mAttributionSource);
verify(mNativeInterface, times(1))
.nextActivePreset(eq(mDevice));
}
/**
* Test that groupNextActivePreset properly calls the native method.
*/
@Test
public void testSwitchToNextPresetForGroup() {
doReturn(new ParcelUuid[]{BluetoothUuid.HAS}).when(mAdapterService)
.getRemoteUuids(any(BluetoothDevice.class));
testConnectingDevice(mDevice3);
int flags = 0x01;
mNativeInterface.onFeaturesUpdate(getByteAddress(mDevice3), flags);
// Verify Native Interface call
mServiceBinder.switchToNextPresetForGroup(0x03, mAttributionSource);
verify(mNativeInterface, times(1)).groupNextActivePreset(eq(0x03));
}
/**
* Test that previousActivePreset properly calls the native method.
*/
@Test
public void testSwitchToPreviousPreset() {
doReturn(new ParcelUuid[]{BluetoothUuid.HAS}).when(mAdapterService)
.getRemoteUuids(any(BluetoothDevice.class));
testConnectingDevice(mDevice);
// Verify Native Interface call
mServiceBinder.switchToPreviousPreset(mDevice, mAttributionSource);
verify(mNativeInterface, times(1))
.previousActivePreset(eq(mDevice));
}
/**
* Test that groupPreviousActivePreset properly calls the native method.
*/
@Test
public void testSwitchToPreviousPresetForGroup() {
doReturn(new ParcelUuid[]{BluetoothUuid.HAS}).when(mAdapterService)
.getRemoteUuids(any(BluetoothDevice.class));
testConnectingDevice(mDevice);
testConnectingDevice(mDevice2);
int flags = 0x01;
mNativeInterface.onFeaturesUpdate(getByteAddress(mDevice), flags);
// Verify Native Interface call
mServiceBinder.switchToPreviousPresetForGroup(0x02, mAttributionSource);
verify(mNativeInterface, times(1)).groupPreviousActivePreset(eq(0x02));
}
/**
* Test that getActivePresetIndex returns cached value.
*/
@Test
public void testGetActivePresetIndex() throws Exception {
doReturn(new ParcelUuid[]{BluetoothUuid.HAS}).when(mAdapterService)
.getRemoteUuids(any(BluetoothDevice.class));
testConnectingDevice(mDevice);
testOnPresetSelected(mDevice, 0x01);
// Verify cached value via binder
final SynchronousResultReceiver<Integer> recv = SynchronousResultReceiver.get();
int defaultRecvValue = -1000;
mServiceBinder.getActivePresetIndex(mDevice, mAttributionSource, recv);
int presetIndex = recv.awaitResultNoInterrupt(Duration.ofMillis(TIMEOUT_MS))
.getValue(defaultRecvValue);
Assert.assertEquals(0x01, presetIndex);
}
/**
* Test that getActivePresetInfo returns cached value for valid parameters.
*/
@Test
public void testGetPresetInfoAndActivePresetInfo() throws Exception {
doReturn(new ParcelUuid[]{BluetoothUuid.HAS}).when(mAdapterService)
.getRemoteUuids(any(BluetoothDevice.class));
testConnectingDevice(mDevice2);
// Check when active preset is not known yet
final SynchronousResultReceiver<List<BluetoothHapPresetInfo>> presetListRecv =
SynchronousResultReceiver.get();
mServiceBinder.getAllPresetInfo(mDevice2, mAttributionSource, presetListRecv);
List<BluetoothHapPresetInfo> presetList = presetListRecv.awaitResultNoInterrupt(
Duration.ofMillis(TIMEOUT_MS)).getValue(null);
final SynchronousResultReceiver<BluetoothHapPresetInfo> presetRecv =
SynchronousResultReceiver.get();
mServiceBinder.getPresetInfo(mDevice2, 0x01, mAttributionSource, presetRecv);
BluetoothHapPresetInfo presetInfo = presetRecv.awaitResultNoInterrupt(
Duration.ofMillis(TIMEOUT_MS)).getValue(null);
Assert.assertTrue(presetList.contains(presetInfo));
Assert.assertEquals(0x01, presetInfo.getIndex());
Assert.assertEquals(BluetoothHapClient.PRESET_INDEX_UNAVAILABLE,
mService.getActivePresetIndex(mDevice2));
Assert.assertEquals(null, mService.getActivePresetInfo(mDevice2));
// Inject active preset change event
testOnPresetSelected(mDevice2, 0x01);
// Check when active preset is known
Assert.assertEquals(0x01, mService.getActivePresetIndex(mDevice2));
final SynchronousResultReceiver<BluetoothHapPresetInfo> recv =
SynchronousResultReceiver.get();
mServiceBinder.getActivePresetInfo(mDevice2, mAttributionSource, recv);
BluetoothHapPresetInfo info = recv.awaitResultNoInterrupt(Duration.ofMillis(TIMEOUT_MS))
.getValue(null);
Assert.assertNotNull(info);
Assert.assertEquals("One", info.getName());
}
/**
* Test that setPresetName properly calls the native method for the valid parameters.
*/
@Test
public void testSetPresetNameNative() {
doReturn(new ParcelUuid[]{BluetoothUuid.HAS}).when(mAdapterService)
.getRemoteUuids(any(BluetoothDevice.class));
testConnectingDevice(mDevice);
mServiceBinder.setPresetName(mDevice, 0x00, "ExamplePresetName", mAttributionSource);
verify(mNativeInterface, times(0))
.setPresetName(eq(mDevice), eq(0x00), eq("ExamplePresetName"));
try {
verify(mCallback, after(TIMEOUT_MS).times(1)).onSetPresetNameFailed(eq(mDevice),
eq(BluetoothStatusCodes.ERROR_HAP_INVALID_PRESET_INDEX));
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
// Verify Native Interface call
mService.setPresetName(mDevice, 0x01, "ExamplePresetName");
verify(mNativeInterface, times(1))
.setPresetName(eq(mDevice), eq(0x01), eq("ExamplePresetName"));
}
/**
* Test that setPresetNameForGroup properly calls the native method for the valid parameters.
*/
@Test
public void testSetPresetNameForGroup() {
doReturn(new ParcelUuid[]{BluetoothUuid.HAS}).when(mAdapterService)
.getRemoteUuids(any(BluetoothDevice.class));
int test_group = 0x02;
for (BluetoothDevice device : mCsipService.getGroupDevicesOrdered(test_group)) {
testConnectingDevice(device);
}
int flags = 0x21;
mNativeInterface.onFeaturesUpdate(getByteAddress(mDevice), flags);
mServiceBinder.setPresetNameForGroup(
test_group, 0x00, "ExamplePresetName", mAttributionSource);
try {
verify(mCallback, after(TIMEOUT_MS).times(1)).onSetPresetNameForGroupFailed(eq(test_group),
eq(BluetoothStatusCodes.ERROR_HAP_INVALID_PRESET_INDEX));
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
mService.setPresetNameForGroup(-1, 0x01, "ExamplePresetName");
try {
verify(mCallback, after(TIMEOUT_MS).times(1)).onSetPresetNameForGroupFailed(eq(-1),
eq(BluetoothStatusCodes.ERROR_CSIP_INVALID_GROUP_ID));
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
// Verify Native Interface call
mService.setPresetNameForGroup(test_group, 0x01, "ExamplePresetName");
verify(mNativeInterface, times(1))
.groupSetPresetName(eq(test_group), eq(0x01), eq("ExamplePresetName"));
}
/**
* Test that native callback generates proper intent.
*/
@Test
public void testStackEventDeviceAvailable() {
doReturn(new ParcelUuid[]{BluetoothUuid.HAS}).when(mAdapterService)
.getRemoteUuids(any(BluetoothDevice.class));
doCallRealMethod()
.when(mNativeInterface)
.onDeviceAvailable(any(byte[].class), anyInt());
mNativeInterface.onDeviceAvailable(getByteAddress(mDevice), 0x03);
Intent intent = TestUtils.waitForIntent(TIMEOUT_MS, mIntentQueue.get(mDevice));
Assert.assertNotNull(intent);
Assert.assertEquals(BluetoothHapClient.ACTION_HAP_DEVICE_AVAILABLE, intent.getAction());
Assert.assertEquals(mDevice, intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE));
Assert.assertEquals(0x03,
intent.getIntExtra(BluetoothHapClient.EXTRA_HAP_FEATURES, 0x00));
}
/**
* Test that native callback generates proper callback call.
*/
@Test
public void testStackEventOnPresetSelected() {
doReturn(new ParcelUuid[]{BluetoothUuid.HAS}).when(mAdapterService)
.getRemoteUuids(any(BluetoothDevice.class));
doCallRealMethod()
.when(mNativeInterface)
.onActivePresetSelected(any(byte[].class), anyInt());
mNativeInterface.onActivePresetSelected(getByteAddress(mDevice), 0x01);
try {
verify(mCallback, after(TIMEOUT_MS).times(1)).onPresetSelected(eq(mDevice),
eq(0x01), eq(BluetoothStatusCodes.REASON_LOCAL_STACK_REQUEST));
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
// Verify that getting current preset returns a proper value now
Assert.assertEquals(0x01, mService.getActivePresetIndex(mDevice));
}
/**
* Test that native callback generates proper callback call.
*/
@Test
public void testStackEventOnActivePresetSelectError() {
doReturn(new ParcelUuid[]{BluetoothUuid.HAS}).when(mAdapterService)
.getRemoteUuids(any(BluetoothDevice.class));
doCallRealMethod()
.when(mNativeInterface)
.onActivePresetSelectError(any(byte[].class), anyInt());
/* Send INVALID_PRESET_INDEX error */
mNativeInterface.onActivePresetSelectError(getByteAddress(mDevice), 0x05);
try {
verify(mCallback, after(TIMEOUT_MS).times(1)).onPresetSelectionFailed(eq(mDevice),
eq(BluetoothStatusCodes.ERROR_HAP_INVALID_PRESET_INDEX));
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/**
* Test that native callback generates proper callback call.
*/
@Test
public void testStackEventOnPresetInfo() {
doReturn(new ParcelUuid[]{BluetoothUuid.HAS}).when(mAdapterService)
.getRemoteUuids(any(BluetoothDevice.class));
// Connect and inject initial presets
testConnectingDevice(mDevice);
int info_reason = HapClientStackEvent.PRESET_INFO_REASON_PRESET_INFO_UPDATE;
BluetoothHapPresetInfo[] info =
{new BluetoothHapPresetInfo.Builder(0x01, "OneChangedToUnavailable")
.setWritable(true)
.setAvailable(false)
.build()};
doCallRealMethod()
.when(mNativeInterface)
.onPresetInfo(any(byte[].class), anyInt(), any());
mNativeInterface.onPresetInfo(getByteAddress(mDevice), info_reason, info);
ArgumentCaptor<List<BluetoothHapPresetInfo>> presetsCaptor =
ArgumentCaptor.forClass(List.class);
try {
verify(mCallback, after(TIMEOUT_MS).times(1)).onPresetInfoChanged(eq(mDevice),
presetsCaptor.capture(), eq(BluetoothStatusCodes.REASON_REMOTE_REQUEST));
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
List<BluetoothHapPresetInfo> presets = presetsCaptor.getValue();
Assert.assertEquals(3, presets.size());
Optional<BluetoothHapPresetInfo> preset = presetsCaptor.getValue()
.stream()
.filter(p -> 0x01 == p.getIndex())
.findFirst();
Assert.assertEquals("OneChangedToUnavailable", preset.get().getName());
Assert.assertFalse(preset.get().isAvailable());
Assert.assertTrue(preset.get().isWritable());
}
/**
* Test that native callback generates proper callback call.
*/
@Test
public void testStackEventOnPresetNameSetError() {
doReturn(new ParcelUuid[]{BluetoothUuid.HAS}).when(mAdapterService)
.getRemoteUuids(any(BluetoothDevice.class));
doCallRealMethod()
.when(mNativeInterface)
.onPresetNameSetError(any(byte[].class), anyInt(), anyInt());
/* Not a valid name length */
mNativeInterface.onPresetNameSetError(getByteAddress(mDevice), 0x01,
HapClientStackEvent.STATUS_INVALID_PRESET_NAME_LENGTH);
try {
verify(mCallback, after(TIMEOUT_MS).times(1)).onSetPresetNameFailed(eq(mDevice),
eq(BluetoothStatusCodes.ERROR_HAP_PRESET_NAME_TOO_LONG));
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
/* Invalid preset index provided */
mNativeInterface.onPresetNameSetError(getByteAddress(mDevice), 0x01,
HapClientStackEvent.STATUS_INVALID_PRESET_INDEX);
try {
verify(mCallback, after(TIMEOUT_MS).times(1)).onSetPresetNameFailed(eq(mDevice),
eq(BluetoothStatusCodes.ERROR_HAP_INVALID_PRESET_INDEX));
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
/* Not allowed on this particular preset */
mNativeInterface.onPresetNameSetError(getByteAddress(mDevice), 0x01,
HapClientStackEvent.STATUS_SET_NAME_NOT_ALLOWED);
try {
verify(mCallback, after(TIMEOUT_MS).times(1)).onSetPresetNameFailed(eq(mDevice),
eq(BluetoothStatusCodes.ERROR_REMOTE_OPERATION_REJECTED));
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
/* Not allowed on this particular preset at this time, might be possible later on */
mNativeInterface.onPresetNameSetError(getByteAddress(mDevice), 0x01,
HapClientStackEvent.STATUS_OPERATION_NOT_POSSIBLE);
try {
verify(mCallback, after(TIMEOUT_MS).times(2)).onSetPresetNameFailed(eq(mDevice),
eq(BluetoothStatusCodes.ERROR_REMOTE_OPERATION_REJECTED));
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
/* Not allowed on all presets - for example missing characteristic */
mNativeInterface.onPresetNameSetError(getByteAddress(mDevice), 0x01,
HapClientStackEvent.STATUS_OPERATION_NOT_SUPPORTED);
try {
verify(mCallback, after(TIMEOUT_MS).times(1)).onSetPresetNameFailed(eq(mDevice),
eq(BluetoothStatusCodes.ERROR_REMOTE_OPERATION_NOT_SUPPORTED));
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/**
* Test that native callback generates proper callback call.
*/
@Test
public void testStackEventOnGroupPresetNameSetError() {
doReturn(new ParcelUuid[]{BluetoothUuid.HAS}).when(mAdapterService)
.getRemoteUuids(any(BluetoothDevice.class));
doCallRealMethod()
.when(mNativeInterface)
.onGroupPresetNameSetError(anyInt(), anyInt(), anyInt());
/* Not a valid name length */
mNativeInterface.onGroupPresetNameSetError(0x01, 0x01,
HapClientStackEvent.STATUS_INVALID_PRESET_NAME_LENGTH);
try {
verify(mCallback, after(TIMEOUT_MS).times(1)).onSetPresetNameForGroupFailed(0x01,
BluetoothStatusCodes.ERROR_HAP_PRESET_NAME_TOO_LONG);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
/* Invalid preset index provided */
mNativeInterface.onGroupPresetNameSetError(0x01, 0x01,
HapClientStackEvent.STATUS_INVALID_PRESET_INDEX);
try {
verify(mCallback, after(TIMEOUT_MS).times(1)).onSetPresetNameForGroupFailed(0x01,
BluetoothStatusCodes.ERROR_HAP_INVALID_PRESET_INDEX);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
/* Not allowed on this particular preset */
mNativeInterface.onGroupPresetNameSetError(0x01, 0x01,
HapClientStackEvent.STATUS_SET_NAME_NOT_ALLOWED);
try {
verify(mCallback, after(TIMEOUT_MS).times(1)).onSetPresetNameForGroupFailed(0x01,
BluetoothStatusCodes.ERROR_REMOTE_OPERATION_REJECTED);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
/* Not allowed on this particular preset at this time, might be possible later on */
mNativeInterface.onGroupPresetNameSetError(0x01, 0x01,
HapClientStackEvent.STATUS_OPERATION_NOT_POSSIBLE);
try {
verify(mCallback, after(TIMEOUT_MS).times(2)).onSetPresetNameForGroupFailed(0x01,
BluetoothStatusCodes.ERROR_REMOTE_OPERATION_REJECTED);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
/* Not allowed on all presets - for example if peer is missing optional CP characteristic */
mNativeInterface.onGroupPresetNameSetError(0x01, 0x01,
HapClientStackEvent.STATUS_OPERATION_NOT_SUPPORTED);
try {
verify(mCallback, after(TIMEOUT_MS).times(1)).onSetPresetNameForGroupFailed(0x01,
BluetoothStatusCodes.ERROR_REMOTE_OPERATION_NOT_SUPPORTED);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
@Test
public void testServiceBinderGetDevicesMatchingConnectionStates() throws Exception {
final SynchronousResultReceiver<List<BluetoothDevice>> recv =
SynchronousResultReceiver.get();
mServiceBinder.getDevicesMatchingConnectionStates(null, mAttributionSource, recv);
List<BluetoothDevice> devices = recv.awaitResultNoInterrupt(Duration.ofMillis(TIMEOUT_MS))
.getValue(null);
Assert.assertEquals(0, devices.size());
}
@Test
public void testServiceBinderSetConnectionPolicy() throws Exception {
final SynchronousResultReceiver<Boolean> recv = SynchronousResultReceiver.get();
boolean defaultRecvValue = false;
mServiceBinder.setConnectionPolicy(
mDevice, BluetoothProfile.CONNECTION_POLICY_UNKNOWN, mAttributionSource, recv);
Assert.assertTrue(recv.awaitResultNoInterrupt(Duration.ofMillis(TIMEOUT_MS))
.getValue(defaultRecvValue));
verify(mDatabaseManager).setProfileConnectionPolicy(
mDevice, BluetoothProfile.HAP_CLIENT, BluetoothProfile.CONNECTION_POLICY_UNKNOWN);
}
@Test
public void testServiceBinderGetFeatures() throws Exception {
final SynchronousResultReceiver<Integer> recv = SynchronousResultReceiver.get();
int defaultRecvValue = -1000;
mServiceBinder.getFeatures(mDevice, mAttributionSource, recv);
int features = recv.awaitResultNoInterrupt(Duration.ofMillis(TIMEOUT_MS))
.getValue(defaultRecvValue);
Assert.assertEquals(0x00, features);
}
@Test
public void testServiceBinderRegisterUnregisterCallback() throws Exception {
IBluetoothHapClientCallback callback = Mockito.mock(IBluetoothHapClientCallback.class);
Binder binder = Mockito.mock(Binder.class);
when(callback.asBinder()).thenReturn(binder);
int size = mService.mCallbacks.getRegisteredCallbackCount();
SynchronousResultReceiver<Void> recv = SynchronousResultReceiver.get();
mServiceBinder.registerCallback(callback, mAttributionSource, recv);
recv.awaitResultNoInterrupt(Duration.ofMillis(TIMEOUT_MS)).getValue(null);
Assert.assertEquals(size + 1, mService.mCallbacks.getRegisteredCallbackCount());
recv = SynchronousResultReceiver.get();
mServiceBinder.unregisterCallback(callback, mAttributionSource, recv);
recv.awaitResultNoInterrupt(Duration.ofMillis(TIMEOUT_MS)).getValue(null);
Assert.assertEquals(size, mService.mCallbacks.getRegisteredCallbackCount());
}
@Test
public void testDumpDoesNotCrash() {
// Update the device policy so okToConnect() returns true
when(mDatabaseManager
.getProfileConnectionPolicy(mDevice, BluetoothProfile.HAP_CLIENT))
.thenReturn(BluetoothProfile.CONNECTION_POLICY_ALLOWED);
doReturn(true).when(mNativeInterface).connectHapClient(any(BluetoothDevice.class));
doReturn(true).when(mNativeInterface).disconnectHapClient(any(BluetoothDevice.class));
doReturn(new ParcelUuid[]{BluetoothUuid.HAS}).when(mAdapterService)
.getRemoteUuids(any(BluetoothDevice.class));
// Add state machine for testing dump()
mService.connect(mDevice);
mService.dump(new StringBuilder());
}
/**
* Helper function to test device connecting
*/
private void prepareConnectingDevice(BluetoothDevice device) {
// Prepare intent queue and all the mocks
mIntentQueue.put(device, new LinkedBlockingQueue<>());
when(mNativeInterface.getDevice(getByteAddress(device))).thenReturn(device);
when(mDatabaseManager
.getProfileConnectionPolicy(device, BluetoothProfile.HAP_CLIENT))
.thenReturn(BluetoothProfile.CONNECTION_POLICY_ALLOWED);
doReturn(true).when(mNativeInterface).connectHapClient(any(BluetoothDevice.class));
doReturn(true).when(mNativeInterface).disconnectHapClient(any(BluetoothDevice.class));
}
/**
* Helper function to test device connecting
*/
private void testConnectingDevice(BluetoothDevice device) {
prepareConnectingDevice(device);
// Send a connect request
Assert.assertTrue("Connect expected to succeed", mService.connect(device));
verifyConnectingDevice(device);
}
/**
* Helper function to test device connecting
*/
private void verifyConnectingDevice(BluetoothDevice 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
HapClientStackEvent evt =
new HapClientStackEvent(HapClientStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED);
evt.device = device;
evt.valueInt1 = HapClientStackEvent.CONNECTION_STATE_CONNECTED;
mService.messageFromNative(evt);
// 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));
evt = new HapClientStackEvent(HapClientStackEvent.EVENT_TYPE_DEVICE_AVAILABLE);
evt.device = device;
evt.valueInt1 = 0x01; // features
mService.messageFromNative(evt);
Intent intent = TestUtils.waitForIntent(TIMEOUT_MS, mIntentQueue.get(device));
Assert.assertNotNull(intent);
Assert.assertEquals(BluetoothHapClient.ACTION_HAP_DEVICE_AVAILABLE, intent.getAction());
Assert.assertEquals(device, intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE));
Assert.assertEquals(evt.valueInt1,
intent.getIntExtra(BluetoothHapClient.EXTRA_HAP_FEATURES, -1));
evt = new HapClientStackEvent(HapClientStackEvent.EVENT_TYPE_DEVICE_FEATURES);
evt.device = device;
evt.valueInt1 = 0x01; // features
mService.messageFromNative(evt);
// Inject some initial presets
List<BluetoothHapPresetInfo> presets =
new ArrayList<BluetoothHapPresetInfo>(Arrays.asList(
new BluetoothHapPresetInfo.Builder(0x01, "One")
.setAvailable(true)
.setWritable(false)
.build(),
new BluetoothHapPresetInfo.Builder(0x02, "Two")
.setAvailable(true)
.setWritable(true)
.build(),
new BluetoothHapPresetInfo.Builder(0x03, "Three")
.setAvailable(false)
.setWritable(false)
.build()));
mService.updateDevicePresetsCache(device,
HapClientStackEvent.PRESET_INFO_REASON_ALL_PRESET_INFO, presets);
}
private void testOnPresetSelected(BluetoothDevice device, int index) {
HapClientStackEvent evt =
new HapClientStackEvent(HapClientStackEvent.EVENT_TYPE_ON_ACTIVE_PRESET_SELECTED);
evt.device = device;
evt.valueInt1 = index;
mService.messageFromNative(evt);
try {
verify(mCallback, after(TIMEOUT_MS).times(1)).onPresetSelected(eq(device),
eq(evt.valueInt1), eq(BluetoothStatusCodes.REASON_LOCAL_STACK_REQUEST));
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/**
* Helper function to test ConnectionStateIntent() method
*/
private void verifyConnectionStateIntent(int timeoutMs, BluetoothDevice device,
int newState, int prevState) {
Intent intent = TestUtils.waitForIntent(timeoutMs, mIntentQueue.get(device));
Assert.assertNotNull(intent);
Assert.assertEquals(BluetoothHapClient.ACTION_HAP_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));
}
/**
* Helper function to test okToConnect() method
*/
private void testOkToConnectCase(BluetoothDevice device, int bondState, int policy,
boolean expected) {
doReturn(bondState).when(mAdapterService).getBondState(device);
when(mDatabaseManager.getProfileConnectionPolicy(device, BluetoothProfile.HAP_CLIENT))
.thenReturn(policy);
Assert.assertEquals(expected, mService.okToConnect(device));
}
/**
* Helper function to get byte array for a device address
*/
private byte[] getByteAddress(BluetoothDevice device) {
if (device == null) {
return Utils.getBytesFromAddress("00:00:00:00:00:00");
}
return Utils.getBytesFromAddress(device.getAddress());
}
private class HasIntentReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
try {
BluetoothDevice device = intent.getParcelableExtra(
BluetoothDevice.EXTRA_DEVICE);
Assert.assertNotNull(device);
LinkedBlockingQueue<Intent> queue = mIntentQueue.get(device);
Assert.assertNotNull(queue);
queue.put(intent);
} catch (InterruptedException e) {
Assert.fail("Cannot add Intent to the queue: " + e.getMessage());
}
}
}
}