diff options
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; |