Merge changes from topics "hearing-aid-white-list", "hearing-aid" into pi-dev
* changes:
DO NOT MERGE Hearing Aid: Use separate time for L/R in connect() and use whitelist
Hearing Aid: Remove device from HiSyncIdMap when unbonded
Add Feature Flag for Hearing Aid Profile
diff --git a/jni/com_android_bluetooth_hearing_aid.cpp b/jni/com_android_bluetooth_hearing_aid.cpp
index c965b1d..1602aac 100644
--- a/jni/com_android_bluetooth_hearing_aid.cpp
+++ b/jni/com_android_bluetooth_hearing_aid.cpp
@@ -194,6 +194,40 @@
return JNI_TRUE;
}
+static jboolean addToWhiteListNative(JNIEnv* env, jobject object,
+ jbyteArray address) {
+ std::shared_lock<std::shared_timed_mutex> lock(interface_mutex);
+ if (!sHearingAidInterface) return JNI_FALSE;
+
+ jbyte* addr = env->GetByteArrayElements(address, nullptr);
+ if (!addr) {
+ jniThrowIOException(env, EINVAL);
+ return JNI_FALSE;
+ }
+
+ RawAddress* tmpraw = (RawAddress*)addr;
+ sHearingAidInterface->AddToWhiteList(*tmpraw);
+ env->ReleaseByteArrayElements(address, addr, 0);
+ return JNI_TRUE;
+}
+
+static jboolean removeFromWhiteListNative(JNIEnv* env, jobject object,
+ jbyteArray address) {
+ std::shared_lock<std::shared_timed_mutex> lock(interface_mutex);
+ if (!sHearingAidInterface) return JNI_FALSE;
+
+ jbyte* addr = env->GetByteArrayElements(address, nullptr);
+ if (!addr) {
+ jniThrowIOException(env, EINVAL);
+ return JNI_FALSE;
+ }
+
+ RawAddress* tmpraw = (RawAddress*)addr;
+ sHearingAidInterface->RemoveFromWhiteList(*tmpraw);
+ env->ReleaseByteArrayElements(address, addr, 0);
+ return JNI_TRUE;
+}
+
static void setVolumeNative(JNIEnv* env, jclass clazz, jint volume) {
if (!sHearingAidInterface) {
LOG(ERROR) << __func__
@@ -209,6 +243,8 @@
{"cleanupNative", "()V", (void*)cleanupNative},
{"connectHearingAidNative", "([B)Z", (void*)connectHearingAidNative},
{"disconnectHearingAidNative", "([B)Z", (void*)disconnectHearingAidNative},
+ {"addToWhiteListNative", "([B)Z", (void*)addToWhiteListNative},
+ {"removeFromWhiteListNative", "([B)Z", (void*)removeFromWhiteListNative},
{"setVolumeNative", "(I)V", (void*)setVolumeNative},
};
diff --git a/src/com/android/bluetooth/btservice/Config.java b/src/com/android/bluetooth/btservice/Config.java
index ae65863..db79486 100644
--- a/src/com/android/bluetooth/btservice/Config.java
+++ b/src/com/android/bluetooth/btservice/Config.java
@@ -21,6 +21,7 @@
import android.content.Context;
import android.content.res.Resources;
import android.provider.Settings;
+import android.util.FeatureFlagUtils;
import android.util.Log;
import com.android.bluetooth.R;
@@ -117,6 +118,13 @@
ArrayList<Class> profiles = new ArrayList<>(PROFILE_SERVICES_AND_FLAGS.length);
for (ProfileConfig config : PROFILE_SERVICES_AND_FLAGS) {
boolean supported = resources.getBoolean(config.mSupported);
+
+ if (supported && (config.mClass == HearingAidService.class) && !FeatureFlagUtils
+ .isEnabled(ctx, FeatureFlagUtils.HEARING_AID_SETTINGS)) {
+ Log.v(TAG, "Feature Flag disables support for HearingAidService");
+ supported = false;
+ }
+
if (supported && !isProfileDisabled(ctx, config.mMask)) {
Log.v(TAG, "Adding " + config.mClass.getSimpleName());
profiles.add(config.mClass);
diff --git a/src/com/android/bluetooth/hearingaid/HearingAidNativeInterface.java b/src/com/android/bluetooth/hearingaid/HearingAidNativeInterface.java
index 2603659..e735a88 100644
--- a/src/com/android/bluetooth/hearingaid/HearingAidNativeInterface.java
+++ b/src/com/android/bluetooth/hearingaid/HearingAidNativeInterface.java
@@ -105,6 +105,28 @@
}
/**
+ * Add a hearing aid device to white list.
+ *
+ * @param device the remote device
+ * @return true on success, otherwise false.
+ */
+ @VisibleForTesting (otherwise = VisibleForTesting.PACKAGE_PRIVATE)
+ public boolean addToWhiteList(BluetoothDevice device) {
+ return addToWhiteListNative(getByteAddress(device));
+ }
+
+ /**
+ * Remove a hearing aid device from white list.
+ *
+ * @param device the remote device
+ * @return true on success, otherwise false.
+ */
+ @VisibleForTesting (otherwise = VisibleForTesting.PACKAGE_PRIVATE)
+ public boolean removeFromWhiteList(BluetoothDevice device) {
+ return removeFromWhiteListNative(getByteAddress(device));
+ }
+
+ /**
* Sets the HearingAid volume
* @param volume
*/
@@ -168,5 +190,7 @@
private native void cleanupNative();
private native boolean connectHearingAidNative(byte[] address);
private native boolean disconnectHearingAidNative(byte[] address);
+ private native boolean addToWhiteListNative(byte[] address);
+ private native boolean removeFromWhiteListNative(byte[] address);
private native void setVolumeNative(int volume);
}
diff --git a/src/com/android/bluetooth/hearingaid/HearingAidService.java b/src/com/android/bluetooth/hearingaid/HearingAidService.java
index bbc61ba..ce2ab3b 100644
--- a/src/com/android/bluetooth/hearingaid/HearingAidService.java
+++ b/src/com/android/bluetooth/hearingaid/HearingAidService.java
@@ -56,6 +56,10 @@
// Upper limit of all HearingAid devices: Bonded or Connected
private static final int MAX_HEARING_AID_STATE_MACHINES = 10;
private static HearingAidService sHearingAidService;
+ @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
+ static int sConnectTimeoutForEachSideMs = 8000;
+ @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
+ static int sCheckWhitelistTimeoutMs = 16000;
private AdapterService mAdapterService;
private HandlerThread mStateMachinesThread;
@@ -243,14 +247,6 @@
}
}
- synchronized (mStateMachines) {
- HearingAidStateMachine smConnect = getOrCreateStateMachine(device);
- if (smConnect == null) {
- Log.e(TAG, "Cannot connect to " + device + " : no state machine");
- }
- smConnect.sendMessage(HearingAidStateMachine.CONNECT);
- }
-
for (BluetoothDevice storedDevice : mDeviceHiSyncIdMap.keySet()) {
if (device.equals(storedDevice)) {
continue;
@@ -263,14 +259,27 @@
Log.e(TAG, "Ignored connect request for " + device + " : no state machine");
continue;
}
- sm.sendMessage(HearingAidStateMachine.CONNECT);
+ sm.sendMessage(HearingAidStateMachine.CONNECT,
+ sConnectTimeoutForEachSideMs);
+ sm.sendMessageDelayed(HearingAidStateMachine.CHECK_WHITELIST_CONNECTION,
+ sCheckWhitelistTimeoutMs);
}
- if (hiSyncId == BluetoothHearingAid.HI_SYNC_ID_INVALID
- && !device.equals(storedDevice)) {
- break;
- }
+ break;
}
}
+
+ synchronized (mStateMachines) {
+ HearingAidStateMachine smConnect = getOrCreateStateMachine(device);
+ if (smConnect == null) {
+ Log.e(TAG, "Cannot connect to " + device + " : no state machine");
+ } else {
+ smConnect.sendMessage(HearingAidStateMachine.CONNECT,
+ sConnectTimeoutForEachSideMs * 2);
+ smConnect.sendMessageDelayed(HearingAidStateMachine.CHECK_WHITELIST_CONNECTION,
+ sCheckWhitelistTimeoutMs);
+ }
+ }
+
return true;
}
@@ -405,6 +414,16 @@
}
}
+ /**
+ * Get the HiSyncIdMap for testing
+ *
+ * @return mDeviceHiSyncIdMap
+ */
+ @VisibleForTesting
+ Map<BluetoothDevice, Long> getHiSyncIdMap() {
+ return mDeviceHiSyncIdMap;
+ }
+
int getConnectionState(BluetoothDevice device) {
enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
synchronized (mStateMachines) {
@@ -658,6 +677,7 @@
if (bondState != BluetoothDevice.BOND_NONE) {
return;
}
+ mDeviceHiSyncIdMap.remove(device);
synchronized (mStateMachines) {
HearingAidStateMachine sm = mStateMachines.get(device);
if (sm == null) {
diff --git a/src/com/android/bluetooth/hearingaid/HearingAidStateMachine.java b/src/com/android/bluetooth/hearingaid/HearingAidStateMachine.java
index d02e102..3e0d617 100644
--- a/src/com/android/bluetooth/hearingaid/HearingAidStateMachine.java
+++ b/src/com/android/bluetooth/hearingaid/HearingAidStateMachine.java
@@ -69,13 +69,15 @@
static final int CONNECT = 1;
static final int DISCONNECT = 2;
+ static final int CHECK_WHITELIST_CONNECTION = 3;
@VisibleForTesting
static final int STACK_EVENT = 101;
private static final int CONNECT_TIMEOUT = 201;
- // NOTE: the value is not "final" - it is modified in the unit tests
- @VisibleForTesting
- static int sConnectTimeoutMs = 30000; // 30s
+ @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
+ static int sConnectTimeoutMs = 16000;
+ @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
+ static int sDisconnectTimeoutMs = 16000;
private Disconnected mDisconnected;
private Connecting mConnecting;
@@ -172,6 +174,12 @@
case DISCONNECT:
Log.w(TAG, "Disconnected: DISCONNECT ignored: " + mDevice);
break;
+ case CHECK_WHITELIST_CONNECTION:
+ if (mService.getConnectedDevices().isEmpty()) {
+ log("No device connected, remove this device from white list");
+ mNativeInterface.removeFromWhiteList(mDevice);
+ }
+ break;
case STACK_EVENT:
HearingAidStackEvent event = (HearingAidStackEvent) message.obj;
if (DBG) {
@@ -238,7 +246,9 @@
public void enter() {
Log.i(TAG, "Enter Connecting(" + mDevice + "): "
+ messageWhatToString(getCurrentMessage().what));
- sendMessageDelayed(CONNECT_TIMEOUT, sConnectTimeoutMs);
+ int timeout = getCurrentMessage().arg1 != 0
+ ? getCurrentMessage().arg1 : sConnectTimeoutMs;
+ sendMessageDelayed(CONNECT_TIMEOUT, timeout);
mConnectionState = BluetoothProfile.STATE_CONNECTING;
broadcastConnectionState(mConnectionState, mLastConnectionState);
}
@@ -261,14 +271,13 @@
deferMessage(message);
break;
case CONNECT_TIMEOUT:
- Log.w(TAG, "Connecting connection timeout: " + mDevice);
+ Log.w(TAG, "Connecting connection timeout: " + mDevice + ". Try whitelist");
mNativeInterface.disconnectHearingAid(mDevice);
- HearingAidStackEvent disconnectEvent =
- new HearingAidStackEvent(
- HearingAidStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED);
- disconnectEvent.device = mDevice;
- disconnectEvent.valueInt1 = HearingAidStackEvent.CONNECTION_STATE_DISCONNECTED;
- sendMessage(STACK_EVENT, disconnectEvent);
+ mNativeInterface.addToWhiteList(mDevice);
+ transitionTo(mDisconnected);
+ break;
+ case CHECK_WHITELIST_CONNECTION:
+ deferMessage(message);
break;
case DISCONNECT:
log("Connecting: connection canceled to " + mDevice);
@@ -325,7 +334,7 @@
public void enter() {
Log.i(TAG, "Enter Disconnecting(" + mDevice + "): "
+ messageWhatToString(getCurrentMessage().what));
- sendMessageDelayed(CONNECT_TIMEOUT, sConnectTimeoutMs);
+ sendMessageDelayed(CONNECT_TIMEOUT, sDisconnectTimeoutMs);
mConnectionState = BluetoothProfile.STATE_DISCONNECTING;
broadcastConnectionState(mConnectionState, mLastConnectionState);
}
diff --git a/tests/unit/src/com/android/bluetooth/hearingaid/HearingAidServiceTest.java b/tests/unit/src/com/android/bluetooth/hearingaid/HearingAidServiceTest.java
index b0a4755..bdc3c00 100644
--- a/tests/unit/src/com/android/bluetooth/hearingaid/HearingAidServiceTest.java
+++ b/tests/unit/src/com/android/bluetooth/hearingaid/HearingAidServiceTest.java
@@ -118,6 +118,8 @@
.getBondState(any(BluetoothDevice.class));
doReturn(new ParcelUuid[]{BluetoothUuid.HearingAid}).when(mAdapterService)
.getRemoteUuids(any(BluetoothDevice.class));
+ HearingAidService.sConnectTimeoutForEachSideMs = 1000;
+ HearingAidService.sCheckWhitelistTimeoutMs = 2000;
}
@After
@@ -343,7 +345,8 @@
mService.getConnectionState(mLeftDevice));
// Verify the connection state broadcast, and that we are in Disconnected state
- verifyConnectionStateIntent(HearingAidStateMachine.sConnectTimeoutMs * 2, mLeftDevice,
+ verifyConnectionStateIntent(HearingAidService.sConnectTimeoutForEachSideMs * 3,
+ mLeftDevice,
BluetoothProfile.STATE_DISCONNECTED,
BluetoothProfile.STATE_CONNECTING);
Assert.assertEquals(BluetoothProfile.STATE_DISCONNECTED,
@@ -916,6 +919,31 @@
mService.getConnectionState(mLeftDevice));
}
+ /**
+ * Test that the service can update HiSyncId from native message
+ */
+ @Test
+ public void getHiSyncIdFromNative_addToMap() {
+ 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));
+ }
+
+ /**
+ * 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));
+ }
+
private void connectDevice(BluetoothDevice device) {
HearingAidStackEvent connCompletedEvent;
diff --git a/tests/unit/src/com/android/bluetooth/hearingaid/HearingAidStateMachineTest.java b/tests/unit/src/com/android/bluetooth/hearingaid/HearingAidStateMachineTest.java
index e6d3ba2..474861e 100644
--- a/tests/unit/src/com/android/bluetooth/hearingaid/HearingAidStateMachineTest.java
+++ b/tests/unit/src/com/android/bluetooth/hearingaid/HearingAidStateMachineTest.java
@@ -77,7 +77,10 @@
mHearingAidStateMachine = new HearingAidStateMachine(mTestDevice, mHearingAidService,
mHearingAidNativeInterface, mHandlerThread.getLooper());
// Override the timeout value to speed up the test
- mHearingAidStateMachine.sConnectTimeoutMs = 1000; // 1s
+ mHearingAidStateMachine.sConnectTimeoutMs = 1000;
+ mHearingAidStateMachine.sDisconnectTimeoutMs = 1000;
+ HearingAidService.sConnectTimeoutForEachSideMs = 1000;
+ HearingAidService.sCheckWhitelistTimeoutMs = 2000;
mHearingAidStateMachine.start();
}
@@ -209,6 +212,7 @@
// Check that we are in Disconnected state
Assert.assertThat(mHearingAidStateMachine.getCurrentState(),
IsInstanceOf.instanceOf(HearingAidStateMachine.Disconnected.class));
+ verify(mHearingAidNativeInterface).addToWhiteList(eq(mTestDevice));
}
/**