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

Commit 4ca00f87 authored by Jason Hsu's avatar Jason Hsu Committed by Android (Google) Code Review
Browse files

Merge "[HA Status] Implement hearing devices connection status listener" into main

parents d9df442f c8d744d0
Loading
Loading
Loading
Loading
+5 −0
Original line number Diff line number Diff line
@@ -610,6 +610,11 @@ public class CachedBluetoothDeviceManager {
        return mOngoingSetMemberPair != null && mOngoingSetMemberPair.equals(device);
    }

    @NonNull
    public HearingAidDeviceManager getHearingAidDeviceManager() {
        return mHearingAidDeviceManager;
    }

    private void log(String msg) {
        if (DEBUG) {
            Log.d(TAG, msg);
+53 −1
Original line number Diff line number Diff line
@@ -17,6 +17,7 @@ package com.android.settingslib.bluetooth;

import static android.bluetooth.BluetoothDevice.BOND_BONDED;

import android.annotation.CallbackExecutor;
import android.bluetooth.BluetoothCsipSetCoordinator;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothHapClient;
@@ -44,7 +45,10 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executor;
import java.util.stream.Collectors;

/**
@@ -59,6 +63,8 @@ public class HearingAidDeviceManager {
    private final LocalBluetoothManager mBtManager;
    private final List<CachedBluetoothDevice> mCachedDevices;
    private final HearingAidAudioRoutingHelper mRoutingHelper;
    private static final Map<ConnectionStatusListener, Executor>
            mConnectionStatusListeners = new ConcurrentHashMap<>();
    @ConnectionStatus
    private int mDevicesConnectionStatus = ConnectionStatus.NO_DEVICE_BONDED;
    private boolean mInitialDevicesConnectionStatusUpdate = false;
@@ -100,14 +106,60 @@ public class HearingAidDeviceManager {
        int CONNECTING_OR_DISCONNECTING = 2;
        int ACTIVE = 3;
    }
    /**
     * Interface for connection status listener.
     */
    public interface ConnectionStatusListener {
        /**
         * Callback when hearing devices connection status change.
         *
         * <p>devices here means singular device or binaural device.
         * E.g. One of hearing device is in CONNECTED status and another is in DISCONNECTED,
         * it will callback CONNECTED status.
         *
         * @param status Updated {@link ConnectionStatus}
         */
        void onDevicesConnectionStatusChanged(@ConnectionStatus int status);
    }

    /**
     * Registers a listener to be notified of connection status changes.
     *
     * @param listener The listener to register.
     * @param executor The executor on which the listener's callback will be run.
     */
    public void registerConnectionStatusListener(
            @NonNull ConnectionStatusListener listener,
            @NonNull @CallbackExecutor Executor executor) {
        mConnectionStatusListeners.put(listener, executor);
    }

    /**
     * Unregisters a listener previously registered with
     * {@link #registerConnectionStatusListener(ConnectionStatusListener, Executor)}.
     *
     * @param listener The listener to unregister.
     */
    public void unregisterConnectionStatusListener(
            @NonNull ConnectionStatusListener listener) {
        mConnectionStatusListeners.remove(listener);
    }

    private void notifyDevicesConnectionStatusChanged(int status) {
        mConnectionStatusListeners.forEach((listener, executor) ->
                executor.execute(() -> listener.onDevicesConnectionStatusChanged(status)));
    }

    /**
     * Updates the connection status of the hearing devices based on the currently bonded
     * hearing aid devices.
     */
    synchronized void notifyDevicesConnectionStatusChanged() {
        final int prevVal = mDevicesConnectionStatus;
        updateDevicesConnectionStatus();
        // TODO: b/357882387 - notify connection status changes for the callers
        if (mDevicesConnectionStatus != prevVal) {
            notifyDevicesConnectionStatusChanged(mDevicesConnectionStatus);
        }
    }

    private void updateDevicesConnectionStatus() {
+39 −1
Original line number Diff line number Diff line
@@ -114,7 +114,10 @@ public class HearingAidDeviceManagerTest {
    private BluetoothDevice mDevice1;
    @Mock
    private BluetoothDevice mDevice2;

    @Mock
    private HearingAidDeviceManager.ConnectionStatusListener mConnectionStatusListener;
    @Mock
    private HearingAidDeviceManager.ConnectionStatusListener mConnectionStatusListener2;

    private BluetoothClass createBtClass(int deviceClass) {
        Parcel p = Parcel.obtain();
@@ -914,6 +917,41 @@ public class HearingAidDeviceManagerTest {
                ConnectionStatus.NO_DEVICE_BONDED);
    }

    @Test
    public void notifyDevicesConnectionStatusChanged_noRegisteredListener_noCallback() {
        when(mCachedDevice1.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
        when(mCachedDevice1.getProfiles()).thenReturn(List.of(mHapClientProfile, mLeAudioProfile));
        when(mCachedDevice1.isConnected()).thenReturn(true);
        mCachedDeviceManager.mCachedDevices.add(mCachedDevice1);

        mHearingAidDeviceManager.registerConnectionStatusListener(
                mConnectionStatusListener, mContext.getMainExecutor());
        mHearingAidDeviceManager.unregisterConnectionStatusListener(
                mConnectionStatusListener);
        mHearingAidDeviceManager.notifyDevicesConnectionStatusChanged();

        verify(mConnectionStatusListener, never()).onDevicesConnectionStatusChanged(anyInt());
    }

    @Test
    public void notifyDevicesConnectionStatusChanged_twoRegisteredListener_callbackEachConnected() {
        when(mCachedDevice1.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
        when(mCachedDevice1.getProfiles()).thenReturn(List.of(mHapClientProfile, mLeAudioProfile));
        when(mCachedDevice1.isConnected()).thenReturn(true);
        mCachedDeviceManager.mCachedDevices.add(mCachedDevice1);

        mHearingAidDeviceManager.registerConnectionStatusListener(
                mConnectionStatusListener, mContext.getMainExecutor());
        mHearingAidDeviceManager.registerConnectionStatusListener(
                mConnectionStatusListener2, mContext.getMainExecutor());
        mHearingAidDeviceManager.notifyDevicesConnectionStatusChanged();

        verify(mConnectionStatusListener).onDevicesConnectionStatusChanged(
                ConnectionStatus.CONNECTED);
        verify(mConnectionStatusListener2).onDevicesConnectionStatusChanged(
                ConnectionStatus.CONNECTED);
    }

    private HearingAidInfo getLeftAshaHearingAidInfo(long hiSyncId) {
        return new HearingAidInfo.Builder()
                .setAshaDeviceSide(HearingAidInfo.DeviceSide.SIDE_LEFT)
+4 −1
Original line number Diff line number Diff line
@@ -29,6 +29,7 @@ import android.view.accessibility.AccessibilityManager;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;

import com.android.settingslib.bluetooth.HearingAidDeviceManager;
import com.android.systemui.Flags;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.accessibility.utils.TestUtils;
@@ -58,13 +59,15 @@ public class DragToInteractAnimationControllerTest extends SysuiTestCase {

    @Mock
    private AccessibilityManager mAccessibilityManager;
    @Mock
    private HearingAidDeviceManager mHearingAidDeviceManager;

    @Before
    public void setUp() throws Exception {
        final WindowManager stubWindowManager = mContext.getSystemService(WindowManager.class);
        final SecureSettings mockSecureSettings = TestUtils.mockSecureSettings();
        final MenuViewModel stubMenuViewModel = new MenuViewModel(mContext, mAccessibilityManager,
                mockSecureSettings);
                mockSecureSettings, mHearingAidDeviceManager);
        final MenuViewAppearance stubMenuViewAppearance = new MenuViewAppearance(mContext,
                stubWindowManager);
        final MenuView stubMenuView = spy(new MenuView(mContext, stubMenuViewModel,
+20 −3
Original line number Diff line number Diff line
@@ -16,6 +16,7 @@

package com.android.systemui.accessibility.floatingmenu;

import static com.android.internal.accessibility.AccessibilityShortcutController.ACCESSIBILITY_HEARING_AIDS_COMPONENT_NAME;
import static com.android.internal.accessibility.AccessibilityShortcutController.MAGNIFICATION_CONTROLLER_NAME;

import static org.mockito.ArgumentMatchers.anyInt;
@@ -25,11 +26,13 @@ import static org.mockito.Mockito.verify;

import android.content.Context;
import android.content.res.Configuration;
import android.platform.test.annotations.EnableFlags;
import android.view.accessibility.AccessibilityManager;

import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;

import com.android.settingslib.bluetooth.HearingAidDeviceManager;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.util.settings.SecureSettings;

@@ -45,6 +48,7 @@ import org.mockito.junit.MockitoRule;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.concurrent.Executor;

/** Tests for {@link MenuInfoRepository}. */
@RunWith(AndroidJUnit4.class)
@@ -55,9 +59,10 @@ public class MenuInfoRepositoryTest extends SysuiTestCase {

    @Mock
    private AccessibilityManager mAccessibilityManager;

    @Mock
    private MenuInfoRepository.OnSettingsContentsChanged mMockSettingsContentsChanged;
    private HearingAidDeviceManager mHearingAidDeviceManager;
    @Mock
    private MenuInfoRepository.OnContentsChanged mMockSettingsContentsChanged;
    @Mock
    private SecureSettings mSecureSettings;

@@ -72,7 +77,7 @@ public class MenuInfoRepositoryTest extends SysuiTestCase {
                anyInt());

        mMenuInfoRepository = new MenuInfoRepository(mContext, mAccessibilityManager,
                mMockSettingsContentsChanged, mSecureSettings);
                mMockSettingsContentsChanged, mSecureSettings, mHearingAidDeviceManager);
    }

    @After
@@ -103,4 +108,16 @@ public class MenuInfoRepositoryTest extends SysuiTestCase {

        verify(mMockSettingsContentsChanged).onTargetFeaturesChanged(any());
    }

    @Test
    @EnableFlags(
            com.android.settingslib.flags.Flags.FLAG_HEARING_DEVICE_SET_CONNECTION_STATUS_REPORT)
    public void registerObservers_addHearingDeviceTarget_verifyRegisterConnectionStatusListener() {
        mShortcutTargets.add(ACCESSIBILITY_HEARING_AIDS_COMPONENT_NAME.flattenToString());
        mMenuInfoRepository.registerObserversAndCallbacks();

        verify(mHearingAidDeviceManager).registerConnectionStatusListener(
                any(HearingAidDeviceManager.ConnectionStatusListener.class), any(
                        Executor.class));
    }
}
Loading