diff options
| author | 2024-12-29 23:06:45 -0800 | |
|---|---|---|
| committer | 2024-12-29 23:06:45 -0800 | |
| commit | d9df442f0bc33b8387cb91e95ffd27cdc80d2bc6 (patch) | |
| tree | f137691878647bd96f9d541a0b88a924626ad7b6 | |
| parent | 00b503d431d583e1b7283fac57a213d5bd635e5d (diff) | |
| parent | 5add72985c718fca536a7d4113d91a8991da7e56 (diff) | |
Merge "[HA Status] Notify hearing devices connection status" into main
8 files changed, 456 insertions, 35 deletions
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java index b4afb7d8cd4c..7374f80fd9db 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java @@ -444,12 +444,23 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> } /** - * @return {@code true} if {@code cachedBluetoothDevice} is hearing aid device + * @return {@code true} if {@code cachedBluetoothDevice} is hearing aid device. + * @deprecated use {@link #isHearingDevice() } + * // TODO: b/385679160 - Target to deprecate it and replace with #isHearingDevice() */ + @Deprecated public boolean isHearingAidDevice() { return mHearingAidInfo != null; } + /** + * @return {@code true} if {@code cachedBluetoothDevice} support any of hearing device profile. + */ + public boolean isHearingDevice() { + return getProfiles().stream().anyMatch( + p -> (p instanceof HearingAidProfile || p instanceof HapClientProfile)); + } + public int getDeviceSide() { return mHearingAidInfo != null ? mHearingAidInfo.getSide() : HearingAidInfo.DeviceSide.SIDE_INVALID; @@ -910,12 +921,33 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> } } + /** + * Checks if the device is connected to the specified Bluetooth profile. + * + * @param profile The Bluetooth profile to check. + * @return {@code true} if the device is connected to the profile. + */ public boolean isConnectedProfile(LocalBluetoothProfile profile) { int status = getProfileConnectionState(profile); return status == BluetoothProfile.STATE_CONNECTED; } + /** + * Checks if the device is connected to the Bluetooth profile with the given ID. + * + * @param profileId The ID of the Bluetooth profile to check. + * @return {@code true} if the device is connected to the profile. + */ + public boolean isConnectedProfile(int profileId) { + for (LocalBluetoothProfile profile : getProfiles()) { + if (profile.getProfileId() == profileId) { + return isConnectedProfile(profile); + } + } + return false; + } + public boolean isBusy() { synchronized (mProfileLock) { for (LocalBluetoothProfile profile : mProfiles) { @@ -1891,13 +1923,6 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> } /** - * @return {@code true} if {@code cachedBluetoothDevice} is LeAudio hearing aid device - */ - public boolean isConnectedLeAudioHearingAidDevice() { - return isConnectedHapClientDevice() && isConnectedLeAudioDevice(); - } - - /** * @return {@code true} if {@code cachedBluetoothDevice} is hearing aid device * * The device may be an ASHA hearing aid that supports {@link HearingAidProfile} or a LeAudio @@ -1908,6 +1933,13 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> } /** + * @return {@code true} if {@code cachedBluetoothDevice} is LeAudio hearing aid device + */ + public boolean isConnectedLeAudioHearingAidDevice() { + return isConnectedHapClientDevice() && isConnectedLeAudioDevice(); + } + + /** * @return {@code true} if {@code cachedBluetoothDevice} is LeAudio device */ public boolean isConnectedLeAudioDevice() { diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java index b754706fb9a1..69492d5040db 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java @@ -24,7 +24,10 @@ import android.bluetooth.le.ScanFilter; import android.content.Context; import android.util.Log; +import androidx.annotation.NonNull; + import com.android.internal.annotations.VisibleForTesting; +import com.android.settingslib.flags.Flags; import java.sql.Timestamp; import java.util.ArrayList; @@ -46,7 +49,7 @@ public class CachedBluetoothDeviceManager { private final LocalBluetoothManager mBtManager; @VisibleForTesting - final List<CachedBluetoothDevice> mCachedDevices = new ArrayList<CachedBluetoothDevice>(); + final List<CachedBluetoothDevice> mCachedDevices = new ArrayList<>(); @VisibleForTesting HearingAidDeviceManager mHearingAidDeviceManager; @VisibleForTesting @@ -192,6 +195,20 @@ public class CachedBluetoothDeviceManager { } /** + * Notifies the connection status if device is hearing device. + * + * @param device The {@link CachedBluetoothDevice} need to be hearing device + */ + public synchronized void notifyHearingDevicesConnectionStatusChangedIfNeeded( + @NonNull CachedBluetoothDevice device) { + if (!device.isHearingDevice()) { + return; + } + + mHearingAidDeviceManager.notifyDevicesConnectionStatusChanged(); + } + + /** * Search for existing sub device {@link CachedBluetoothDevice}. * * @param device the address of the Bluetooth device @@ -388,8 +405,14 @@ public class CachedBluetoothDeviceManager { /** Handles when the device been set as active/inactive. */ public synchronized void onActiveDeviceChanged(CachedBluetoothDevice cachedBluetoothDevice) { - if (cachedBluetoothDevice.isHearingAidDevice()) { + if (cachedBluetoothDevice == null) { + return; + } + if (cachedBluetoothDevice.isHearingDevice()) { mHearingAidDeviceManager.onActiveDeviceChanged(cachedBluetoothDevice); + if (Flags.hearingDeviceSetConnectionStatusReport()) { + mHearingAidDeviceManager.notifyDevicesConnectionStatusChanged(); + } } } @@ -421,6 +444,14 @@ public class CachedBluetoothDeviceManager { mainDevice.unpair(); mainDevice.setSubDevice(null); } + + // TODO: b/386121967 - Should change to use isHearingDevice but mProfile get clear here. + // Need to consider where to put this logic when using isHearingDevice() + if (device.isHearingAidDevice()) { + if (Flags.hearingDeviceSetConnectionStatusReport()) { + mHearingAidDeviceManager.notifyDevicesConnectionStatusChanged(); + } + } } /** diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidDeviceManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidDeviceManager.java index ad34e837f508..2e9cd30eedc4 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidDeviceManager.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidDeviceManager.java @@ -15,7 +15,10 @@ */ package com.android.settingslib.bluetooth; +import static android.bluetooth.BluetoothDevice.BOND_BONDED; + import android.bluetooth.BluetoothCsipSetCoordinator; +import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothHapClient; import android.bluetooth.BluetoothHearingAid; import android.bluetooth.BluetoothProfile; @@ -30,15 +33,22 @@ import android.provider.Settings; import android.util.FeatureFlagUtils; import android.util.Log; +import androidx.annotation.IntDef; +import androidx.annotation.NonNull; +import androidx.collection.ArraySet; + import com.android.internal.annotations.VisibleForTesting; import com.android.settingslib.bluetooth.HearingAidAudioRoutingConstants.RoutingValue; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.HashSet; import java.util.List; import java.util.Set; +import java.util.stream.Collectors; /** - * HearingAidDeviceManager manages the set of remote HearingAid(ASHA) Bluetooth devices. + * HearingAidDeviceManager manages the set of remote bluetooth hearing devices. */ public class HearingAidDeviceManager { private static final String TAG = "HearingAidDeviceManager"; @@ -49,6 +59,10 @@ public class HearingAidDeviceManager { private final LocalBluetoothManager mBtManager; private final List<CachedBluetoothDevice> mCachedDevices; private final HearingAidAudioRoutingHelper mRoutingHelper; + @ConnectionStatus + private int mDevicesConnectionStatus = ConnectionStatus.NO_DEVICE_BONDED; + private boolean mInitialDevicesConnectionStatusUpdate = false; + HearingAidDeviceManager(Context context, LocalBluetoothManager localBtManager, List<CachedBluetoothDevice> CachedDevices) { mContext = context; @@ -68,6 +82,145 @@ public class HearingAidDeviceManager { mRoutingHelper = routingHelper; } + /** + * Defines the connection status for hearing devices. + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef({ + ConnectionStatus.NO_DEVICE_BONDED, + ConnectionStatus.DISCONNECTED, + ConnectionStatus.CONNECTED, + ConnectionStatus.CONNECTING_OR_DISCONNECTING, + ConnectionStatus.ACTIVE + }) + public @interface ConnectionStatus { + int NO_DEVICE_BONDED = -1; + int DISCONNECTED = 0; + int CONNECTED = 1; + int CONNECTING_OR_DISCONNECTING = 2; + int ACTIVE = 3; + } + + /** + * Updates the connection status of the hearing devices based on the currently bonded + * hearing aid devices. + */ + synchronized void notifyDevicesConnectionStatusChanged() { + updateDevicesConnectionStatus(); + // TODO: b/357882387 - notify connection status changes for the callers + } + + private void updateDevicesConnectionStatus() { + mInitialDevicesConnectionStatusUpdate = true; + // Add all hearing devices including sub and member into a set. + Set<CachedBluetoothDevice> allHearingDevices = mCachedDevices.stream() + .filter(d -> d.getBondState() == BluetoothDevice.BOND_BONDED + && d.isHearingDevice()) + .flatMap(d -> getAssociatedCachedDevice(d).stream()) + .collect(Collectors.toSet()); + + // Status sequence matters here. If one of the hearing devices is in previous + // ConnectionStatus, we will treat whole hearing devices is in this status. + // E.g. One of hearing device is in CONNECTED status and another is in DISCONNECTED + // status, the hearing devices connection status will notify CONNECTED status. + if (isConnectingOrDisconnectingConnectionStatus(allHearingDevices)) { + mDevicesConnectionStatus = ConnectionStatus.CONNECTING_OR_DISCONNECTING; + } else if (isActiveConnectionStatus(allHearingDevices)) { + mDevicesConnectionStatus = ConnectionStatus.ACTIVE; + } else if (isConnectedStatus(allHearingDevices)) { + mDevicesConnectionStatus = ConnectionStatus.CONNECTED; + } else if (isDisconnectedStatus(allHearingDevices)) { + mDevicesConnectionStatus = ConnectionStatus.DISCONNECTED; + } else { + mDevicesConnectionStatus = ConnectionStatus.NO_DEVICE_BONDED; + } + + if (DEBUG) { + Log.d(TAG, "updateDevicesConnectionStatus: " + mDevicesConnectionStatus); + } + } + + /** + * @return all the related CachedBluetoothDevices for this device. + */ + @NonNull + public Set<CachedBluetoothDevice> getAssociatedCachedDevice( + @NonNull CachedBluetoothDevice device) { + ArraySet<CachedBluetoothDevice> cachedDeviceSet = new ArraySet<>(); + cachedDeviceSet.add(device); + // Associated device should be added into memberDevice if it support CSIP profile. + Set<CachedBluetoothDevice> memberDevices = device.getMemberDevice(); + if (!memberDevices.isEmpty()) { + cachedDeviceSet.addAll(memberDevices); + return cachedDeviceSet; + } + // If not support CSIP profile, it should be ASHA hearing device and added into subDevice. + CachedBluetoothDevice subDevice = device.getSubDevice(); + if (subDevice != null) { + cachedDeviceSet.add(subDevice); + return cachedDeviceSet; + } + + return cachedDeviceSet; + } + + private boolean isConnectingOrDisconnectingConnectionStatus( + Set<CachedBluetoothDevice> devices) { + HearingAidProfile hearingAidProfile = mBtManager.getProfileManager().getHearingAidProfile(); + HapClientProfile hapClientProfile = mBtManager.getProfileManager().getHapClientProfile(); + + for (CachedBluetoothDevice device : devices) { + if (hearingAidProfile != null) { + int status = device.getProfileConnectionState(hearingAidProfile); + if (status == BluetoothProfile.STATE_DISCONNECTING + || status == BluetoothProfile.STATE_CONNECTING) { + return true; + } + } + if (hapClientProfile != null) { + int status = device.getProfileConnectionState(hapClientProfile); + if (status == BluetoothProfile.STATE_DISCONNECTING + || status == BluetoothProfile.STATE_CONNECTING) { + return true; + } + } + } + return false; + } + + private boolean isActiveConnectionStatus(Set<CachedBluetoothDevice> devices) { + for (CachedBluetoothDevice device : devices) { + if ((device.isActiveDevice(BluetoothProfile.HEARING_AID) + && device.isConnectedProfile(BluetoothProfile.HEARING_AID)) + || (device.isActiveDevice(BluetoothProfile.LE_AUDIO) + && device.isConnectedProfile(BluetoothProfile.LE_AUDIO))) { + return true; + } + } + return false; + } + + private boolean isConnectedStatus(Set<CachedBluetoothDevice> devices) { + return devices.stream().anyMatch(CachedBluetoothDevice::isConnected); + } + + private boolean isDisconnectedStatus(Set<CachedBluetoothDevice> devices) { + return devices.stream().anyMatch( + d -> (!d.isConnected() && d.getBondState() == BOND_BONDED)); + } + + /** + * Gets the connection status for hearing device set. Will update connection status first if + * never updated. + */ + @ConnectionStatus + public int getDevicesConnectionStatus() { + if (!mInitialDevicesConnectionStatusUpdate) { + updateDevicesConnectionStatus(); + } + return mDevicesConnectionStatus; + } + void initHearingAidDeviceIfNeeded(CachedBluetoothDevice newDevice, List<ScanFilter> leScanFilters) { HearingAidInfo info = generateHearingAidInfo(newDevice); diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java index 8dfeb559a8b5..7c24df9e9019 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java @@ -47,12 +47,14 @@ import androidx.annotation.VisibleForTesting; import com.android.internal.util.ArrayUtils; import com.android.internal.util.CollectionUtils; +import com.android.settingslib.flags.Flags; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.concurrent.CopyOnWriteArrayList; @@ -345,11 +347,17 @@ public class LocalBluetoothProfileManager { oldState == BluetoothProfile.STATE_CONNECTING) { Log.i(TAG, "Failed to connect " + mProfile + " device"); } + final boolean isAshaProfile = getHearingAidProfile() != null + && mProfile instanceof HearingAidProfile; + final boolean isHapClientProfile = getHapClientProfile() != null + && mProfile instanceof HapClientProfile; + final boolean isLeAudioProfile = getLeAudioProfile() != null + && mProfile instanceof LeAudioProfile; + final boolean isHapClientOrLeAudioProfile = isHapClientProfile || isLeAudioProfile; + final boolean isCsipProfile = getCsipSetCoordinatorProfile() != null + && mProfile instanceof CsipSetCoordinatorProfile; - if (getHearingAidProfile() != null - && mProfile instanceof HearingAidProfile - && (newState == BluetoothProfile.STATE_CONNECTED)) { - + if (isAshaProfile && (newState == BluetoothProfile.STATE_CONNECTED)) { // Check if the HiSyncID has being initialized if (cachedDevice.getHiSyncId() == BluetoothHearingAid.HI_SYNC_ID_INVALID) { long newHiSyncId = getHearingAidProfile().getHiSyncId(cachedDevice.getDevice()); @@ -366,11 +374,6 @@ public class LocalBluetoothProfileManager { HearingAidStatsLogUtils.logHearingAidInfo(cachedDevice); } - final boolean isHapClientProfile = getHapClientProfile() != null - && mProfile instanceof HapClientProfile; - final boolean isLeAudioProfile = getLeAudioProfile() != null - && mProfile instanceof LeAudioProfile; - final boolean isHapClientOrLeAudioProfile = isHapClientProfile || isLeAudioProfile; if (isHapClientOrLeAudioProfile && newState == BluetoothProfile.STATE_CONNECTED) { // Checks if both profiles are connected to the device. Hearing aid info need @@ -385,9 +388,7 @@ public class LocalBluetoothProfileManager { } } - if (getCsipSetCoordinatorProfile() != null - && mProfile instanceof CsipSetCoordinatorProfile - && newState == BluetoothProfile.STATE_CONNECTED) { + if (isCsipProfile && (newState == BluetoothProfile.STATE_CONNECTED)) { // Check if the GroupID has being initialized if (cachedDevice.getGroupId() == BluetoothCsipSetCoordinator.GROUP_ID_INVALID) { final Map<Integer, ParcelUuid> groupIdMap = getCsipSetCoordinatorProfile() @@ -403,6 +404,21 @@ public class LocalBluetoothProfileManager { } } + // LE_AUDIO, CSIP_SET_COORDINATOR profiles will also impact the connection status + // change, e.g. device need to active on LE_AUDIO to become active connection status. + final Set<Integer> hearingDeviceConnectionStatusProfileId = Set.of( + BluetoothProfile.HEARING_AID, + BluetoothProfile.HAP_CLIENT, + BluetoothProfile.LE_AUDIO, + BluetoothProfile.CSIP_SET_COORDINATOR + ); + if (Flags.hearingDeviceSetConnectionStatusReport()) { + if (hearingDeviceConnectionStatusProfileId.contains(mProfile.getProfileId())) { + mDeviceManager.notifyHearingDevicesConnectionStatusChangedIfNeeded( + cachedDevice); + } + } + cachedDevice.onProfileStateChanged(mProfile, newState); // Dispatch profile changed after device update boolean needDispatchProfileConnectionState = true; diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManagerTest.java index 05f471f62f1d..69e99c616910 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManagerTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManagerTest.java @@ -33,21 +33,37 @@ import android.bluetooth.BluetoothUuid; import android.content.Context; import android.os.Parcel; import android.os.ParcelUuid; +import android.platform.test.annotations.RequiresFlagsEnabled; +import android.platform.test.flag.junit.CheckFlagsRule; +import android.platform.test.flag.junit.DeviceFlagsValueProvider; + +import androidx.test.core.app.ApplicationProvider; + +import com.google.common.collect.ImmutableList; import org.junit.Before; import org.junit.Ignore; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; -import org.mockito.MockitoAnnotations; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; import org.robolectric.RobolectricTestRunner; -import org.robolectric.RuntimeEnvironment; import java.util.Collection; +import java.util.List; import java.util.Map; @RunWith(RobolectricTestRunner.class) public class CachedBluetoothDeviceManagerTest { + @Rule + public MockitoRule mMockitoRule = MockitoJUnit.rule(); + @Rule + public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule(); + + private final Context mContext = ApplicationProvider.getApplicationContext(); + private final static String DEVICE_NAME_1 = "TestName_1"; private final static String DEVICE_NAME_2 = "TestName_2"; private final static String DEVICE_NAME_3 = "TestName_3"; @@ -82,6 +98,8 @@ public class CachedBluetoothDeviceManagerTest { @Mock private HearingAidProfile mHearingAidProfile; @Mock + private HapClientProfile mHapClientProfile; + @Mock private CsipSetCoordinatorProfile mCsipSetCoordinatorProfile; @Mock private BluetoothDevice mDevice1; @@ -89,12 +107,11 @@ public class CachedBluetoothDeviceManagerTest { private BluetoothDevice mDevice2; @Mock private BluetoothDevice mDevice3; + private HearingAidDeviceManager mHearingAidDeviceManager; private CachedBluetoothDevice mCachedDevice1; private CachedBluetoothDevice mCachedDevice2; private CachedBluetoothDevice mCachedDevice3; private CachedBluetoothDeviceManager mCachedDeviceManager; - private HearingAidDeviceManager mHearingAidDeviceManager; - private Context mContext; private BluetoothClass createBtClass(int deviceClass) { Parcel p = Parcel.obtain(); @@ -108,8 +125,6 @@ public class CachedBluetoothDeviceManagerTest { @Before public void setUp() { - MockitoAnnotations.initMocks(this); - mContext = RuntimeEnvironment.application; when(mDevice1.getAddress()).thenReturn(DEVICE_ADDRESS_1); when(mDevice2.getAddress()).thenReturn(DEVICE_ADDRESS_2); when(mDevice3.getAddress()).thenReturn(DEVICE_ADDRESS_3); @@ -129,13 +144,15 @@ public class CachedBluetoothDeviceManagerTest { when(mA2dpProfile.isProfileReady()).thenReturn(true); when(mPanProfile.isProfileReady()).thenReturn(true); when(mHearingAidProfile.isProfileReady()).thenReturn(true); + when(mHapClientProfile.isProfileReady()).thenReturn(true); when(mCsipSetCoordinatorProfile.isProfileReady()) .thenReturn(true); doAnswer((invocation) -> mHearingAidProfile). when(mLocalProfileManager).getHearingAidProfile(); doAnswer((invocation) -> mCsipSetCoordinatorProfile) .when(mLocalProfileManager).getCsipSetCoordinatorProfile(); - mCachedDeviceManager = new CachedBluetoothDeviceManager(mContext, mLocalBluetoothManager); + mCachedDeviceManager = spy( + new CachedBluetoothDeviceManager(mContext, mLocalBluetoothManager)); mCachedDevice1 = spy(new CachedBluetoothDevice(mContext, mLocalProfileManager, mDevice1)); mCachedDevice2 = spy(new CachedBluetoothDevice(mContext, mLocalProfileManager, mDevice2)); mCachedDevice3 = spy(new CachedBluetoothDevice(mContext, mLocalProfileManager, mDevice3)); @@ -621,12 +638,55 @@ public class CachedBluetoothDeviceManagerTest { public void onActiveDeviceChanged_validHiSyncId_callExpectedFunction() { doNothing().when(mHearingAidDeviceManager).onActiveDeviceChanged(any()); when(mDevice1.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED); - CachedBluetoothDevice cachedDevice1 = mCachedDeviceManager.addDevice(mDevice1); - cachedDevice1.setHearingAidInfo( - new HearingAidInfo.Builder().setHiSyncId(HISYNCID1).build()); + when(mCachedDevice1.getProfiles()).thenReturn( + ImmutableList.of(mHapClientProfile, mHearingAidProfile)); + + mCachedDeviceManager.onActiveDeviceChanged(mCachedDevice1); + + verify(mHearingAidDeviceManager).onActiveDeviceChanged(mCachedDevice1); + } + + @Test + @RequiresFlagsEnabled( + com.android.settingslib.flags.Flags.FLAG_HEARING_DEVICE_SET_CONNECTION_STATUS_REPORT) + public void onActiveDeviceChanged_hearingDevice_callReportConnectionStatus() { + when(mDevice1.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED); + when(mCachedDevice1.getProfiles()).thenReturn( + ImmutableList.of(mHapClientProfile, mHearingAidProfile)); + + mCachedDeviceManager.onActiveDeviceChanged(mCachedDevice1); + + verify(mHearingAidDeviceManager).notifyDevicesConnectionStatusChanged(); + } + + @Test + @RequiresFlagsEnabled( + com.android.settingslib.flags.Flags.FLAG_HEARING_DEVICE_SET_CONNECTION_STATUS_REPORT) + public void onDeviceUnpaired_hearingDevice_callReportConnectionStatus() { + when(mDevice1.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED); + when(mCachedDevice1.getProfiles()).thenReturn( + ImmutableList.of(mHapClientProfile, mHearingAidProfile)); + + mCachedDeviceManager.onDeviceUnpaired(mCachedDevice1); + + verify(mHearingAidDeviceManager).notifyDevicesConnectionStatusChanged(); + } + + @Test + public void notifyHearingDevicesConnectionStatusChanged_nonHearingDevice_notCallFunction() { + when(mCachedDevice1.getProfiles()).thenReturn(List.of(mA2dpProfile)); + + mCachedDeviceManager.notifyHearingDevicesConnectionStatusChangedIfNeeded(mCachedDevice1); + + verify(mHearingAidDeviceManager, never()).notifyDevicesConnectionStatusChanged(); + } + + @Test + public void notifyHearingDevicesConnectionStatusChanged_hearingDeviceProfile_callFunction() { + when(mCachedDevice1.getProfiles()).thenReturn(List.of(mHapClientProfile)); - mCachedDeviceManager.onActiveDeviceChanged(cachedDevice1); + mCachedDeviceManager.notifyHearingDevicesConnectionStatusChangedIfNeeded(mCachedDevice1); - verify(mHearingAidDeviceManager).onActiveDeviceChanged(cachedDevice1); + verify(mHearingAidDeviceManager).notifyDevicesConnectionStatusChanged(); } } diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java index 30f8a798b674..d933a1ced8bc 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java @@ -2074,6 +2074,21 @@ public class CachedBluetoothDeviceTest { assertThat(mCachedDevice.getConnectionSummary(false)).isNull(); } + @Test + public void isHearingDevice_supportHearingRelatedProfiles_returnTrue() { + when(mCachedDevice.getProfiles()).thenReturn( + ImmutableList.of(mHapClientProfile, mHearingAidProfile)); + + assertThat(mCachedDevice.isHearingDevice()).isTrue(); + } + + @Test + public void isHearingDevice_supportOnlyLeAudioProfile_returnFalse() { + when(mCachedDevice.getProfiles()).thenReturn(ImmutableList.of(mLeAudioProfile)); + + assertThat(mCachedDevice.isHearingDevice()).isFalse(); + } + private void updateProfileStatus(LocalBluetoothProfile profile, int status) { doReturn(status).when(profile).getConnectionStatus(mDevice); mCachedDevice.onProfileStateChanged(profile, status); diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HearingAidDeviceManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HearingAidDeviceManagerTest.java index 2458c5b2eb6e..f126eaf06503 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HearingAidDeviceManagerTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HearingAidDeviceManagerTest.java @@ -54,6 +54,8 @@ import android.util.FeatureFlagUtils; import androidx.test.core.app.ApplicationProvider; +import com.android.settingslib.bluetooth.HearingAidDeviceManager.ConnectionStatus; + import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -140,6 +142,8 @@ public class HearingAidDeviceManagerTest { when(mLocalProfileManager.getHearingAidProfile()).thenReturn(mHearingAidProfile); when(mLocalProfileManager.getLeAudioProfile()).thenReturn(mLeAudioProfile); when(mLocalProfileManager.getHapClientProfile()).thenReturn(mHapClientProfile); + when(mHapClientProfile.getProfileId()).thenReturn(BluetoothProfile.HAP_CLIENT); + when(mLeAudioProfile.getProfileId()).thenReturn(BluetoothProfile.LE_AUDIO); when(mAudioStrategy.getAudioAttributesForLegacyStreamType( AudioManager.STREAM_MUSIC)) .thenReturn((new AudioAttributes.Builder()).build()); @@ -826,6 +830,90 @@ public class HearingAidDeviceManagerTest { verify(mHapClientProfile).selectPreset(mDevice2, PRESET_INDEX_1); } + @Test + public void getAssociatedCachedDevice_existSubDevice_returnSize2() { + mCachedDevice1.setSubDevice(mCachedDevice2); + + //including self device + assertThat(mHearingAidDeviceManager.getAssociatedCachedDevice( + mCachedDevice1).size()).isEqualTo(2); + } + + @Test + public void getAssociatedCachedDevice_existMemberDevice_returnSize2() { + mCachedDevice1.addMemberDevice(mCachedDevice2); + + //including self device + assertThat(mHearingAidDeviceManager.getAssociatedCachedDevice( + mCachedDevice1).size()).isEqualTo(2); + } + + @Test + public void notifyDevicesConnectionStatusChanged_connecting_connectingStatus() { + when(mCachedDevice1.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED); + when(mCachedDevice1.getProfiles()).thenReturn(List.of(mHapClientProfile)); + when(mHapClientProfile.getConnectionStatus(mDevice1)).thenReturn( + BluetoothProfile.STATE_CONNECTING); + + mCachedDeviceManager.mCachedDevices.add(mCachedDevice1); + mHearingAidDeviceManager.notifyDevicesConnectionStatusChanged(); + + assertThat(mHearingAidDeviceManager.getDevicesConnectionStatus()).isEqualTo( + ConnectionStatus.CONNECTING_OR_DISCONNECTING); + } + + @Test + public void notifyDevicesConnectionStatusChanged_activeConnectedProfile_activeStatus() { + when(mCachedDevice1.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED); + when(mCachedDevice1.getProfiles()).thenReturn(List.of(mHapClientProfile, mLeAudioProfile)); + when(mLeAudioProfile.getConnectionStatus(mDevice1)).thenReturn( + BluetoothProfile.STATE_CONNECTED); + when(mCachedDevice1.isActiveDevice(BluetoothProfile.LE_AUDIO)).thenReturn(true); + + mCachedDeviceManager.mCachedDevices.add(mCachedDevice1); + mHearingAidDeviceManager.notifyDevicesConnectionStatusChanged(); + + assertThat(mHearingAidDeviceManager.getDevicesConnectionStatus()).isEqualTo( + ConnectionStatus.ACTIVE); + } + + @Test + public void notifyDevicesConnectionStatusChanged_isConnected_connectedStatus() { + 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.notifyDevicesConnectionStatusChanged(); + + assertThat(mHearingAidDeviceManager.getDevicesConnectionStatus()).isEqualTo( + ConnectionStatus.CONNECTED); + } + + @Test + public void notifyDevicesConnectionStatusChanged_bondedNotConnected_disconnectedStatus() { + when(mCachedDevice1.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED); + when(mCachedDevice1.isConnected()).thenReturn(false); + when(mCachedDevice1.getProfiles()).thenReturn(List.of(mHapClientProfile)); + mCachedDeviceManager.mCachedDevices.add(mCachedDevice1); + + mHearingAidDeviceManager.notifyDevicesConnectionStatusChanged(); + + assertThat(mHearingAidDeviceManager.getDevicesConnectionStatus()).isEqualTo( + ConnectionStatus.DISCONNECTED); + } + + @Test + public void notifyDevicesConnectionStatusChanged_bondNone_noDeviceBondedStatus() { + when(mCachedDevice1.getBondState()).thenReturn(BluetoothDevice.BOND_NONE); + mCachedDeviceManager.mCachedDevices.add(mCachedDevice1); + + mHearingAidDeviceManager.notifyDevicesConnectionStatusChanged(); + + assertThat(mHearingAidDeviceManager.getDevicesConnectionStatus()).isEqualTo( + ConnectionStatus.NO_DEVICE_BONDED); + } + private HearingAidInfo getLeftAshaHearingAidInfo(long hiSyncId) { return new HearingAidInfo.Builder() .setAshaDeviceSide(HearingAidInfo.DeviceSide.SIDE_LEFT) diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManagerTest.java index 6ff90ba4b391..219bfe0c8c53 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManagerTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManagerTest.java @@ -37,10 +37,14 @@ import android.bluetooth.BluetoothUuid; import android.content.Context; import android.content.Intent; import android.os.ParcelUuid; +import android.platform.test.annotations.RequiresFlagsEnabled; +import android.platform.test.flag.junit.CheckFlagsRule; +import android.platform.test.flag.junit.DeviceFlagsValueProvider; import com.android.settingslib.testutils.shadow.ShadowBluetoothAdapter; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; @@ -56,6 +60,9 @@ import java.util.List; @RunWith(RobolectricTestRunner.class) @Config(shadows = {ShadowBluetoothAdapter.class}) public class LocalBluetoothProfileManagerTest { + @Rule + public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule(); + private static final long HISYNCID = 10; private static final int GROUP_ID = 1; @@ -305,6 +312,25 @@ public class LocalBluetoothProfileManagerTest { verify(mCachedBluetoothDevice).refresh(); } + @Test + @RequiresFlagsEnabled( + com.android.settingslib.flags.Flags.FLAG_HEARING_DEVICE_SET_CONNECTION_STATUS_REPORT) + public void stateChangedHandler_hapProfileStateChanged_notifyHearingDevicesConnectionStatus() { + mShadowBluetoothAdapter.setSupportedProfiles(generateList( + new int[] {BluetoothProfile.HAP_CLIENT})); + mProfileManager.updateLocalProfiles(); + + mIntent = new Intent(BluetoothHapClient.ACTION_HAP_CONNECTION_STATE_CHANGED); + mIntent.putExtra(BluetoothDevice.EXTRA_DEVICE, mDevice); + mIntent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, BluetoothProfile.STATE_CONNECTING); + mIntent.putExtra(BluetoothProfile.EXTRA_STATE, BluetoothProfile.STATE_CONNECTED); + + mContext.sendBroadcast(mIntent); + + verify(mDeviceManager).notifyHearingDevicesConnectionStatusChangedIfNeeded( + mCachedBluetoothDevice); + } + private List<Integer> generateList(int[] profiles) { if (profiles == null) { return null; |