Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 7734956e authored by Hansong Zhang's avatar Hansong Zhang
Browse files

DO NOT MERGE Hearing Aid: Use separate time for L/R in connect() and use whitelist

* connect() sends connect command to L/R devices in parallel, but in
  native layer connecting is sequential. We need to have separate timeout
  for devices (8 seconds for the first, 16 seconds for the second) so that
  each we attempt to connect each device with same amount of time.
* When one device is connected but another is not, use whitelist
* Connect to the other device in the group first, then the selected
  device

Bug: 116317072
Bug: 115365334
Test: connect to a pair with one device off
Test: also run unit test
Change-Id: I332b2bd23f518a6024ba5166c86db2d5ecbde6fc
parent d09a777d
Loading
Loading
Loading
Loading
+36 −0
Original line number Diff line number Diff line
@@ -194,6 +194,40 @@ static jboolean disconnectHearingAidNative(JNIEnv* env, jobject object,
  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 @@ static JNINativeMethod sMethods[] = {
    {"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},
};

+24 −0
Original line number Diff line number Diff line
@@ -104,6 +104,28 @@ public class HearingAidNativeInterface {
        return disconnectHearingAidNative(getByteAddress(device));
    }

    /**
     * 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 @@ public class HearingAidNativeInterface {
    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);
}
+22 −13
Original line number Diff line number Diff line
@@ -56,6 +56,10 @@ public class HearingAidService extends ProfileService {
    // 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 @@ public class HearingAidService extends ProfileService {
            }
        }

        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 @@ public class HearingAidService extends ProfileService {
                        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;
            }
        }

        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;
    }

+21 −12
Original line number Diff line number Diff line
@@ -69,13 +69,15 @@ final class HearingAidStateMachine extends StateMachine {

    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 @@ final class HearingAidStateMachine extends StateMachine {
                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 @@ final class HearingAidStateMachine extends StateMachine {
        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 @@ final class HearingAidStateMachine extends StateMachine {
                    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 @@ final class HearingAidStateMachine extends StateMachine {
        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);
        }
+4 −1
Original line number Diff line number Diff line
@@ -118,6 +118,8 @@ public class HearingAidServiceTest {
                .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 @@ public class HearingAidServiceTest {
                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,
Loading