diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index e10b17ddbf8..ec4d8efd183 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -2290,6 +2290,7 @@
android:exported="true">
+
diff --git a/res/layout/bluetooth_pin_confirm.xml b/res/layout/bluetooth_pin_confirm.xml
index 48912755890..28ad1f62920 100644
--- a/res/layout/bluetooth_pin_confirm.xml
+++ b/res/layout/bluetooth_pin_confirm.xml
@@ -65,6 +65,18 @@
android:textAppearance="@*android:style/TextAppearance.DeviceDefault.Subhead"
android:visibility="gone" />
+
+
To pair with:<br><b>%1$s</b><br><br>Make sure it is showing this passkey:<br><b>%2$s</b>
+
+ Confirm to pair with the coordinated set
+
From:<br><b>%1$s</b><br><br>Pair with this device?
@@ -1909,6 +1912,8 @@
Device details
Device\'s Bluetooth address: %1$s
+
+ Device\'s Bluetooth address:\n%1$s
Forget device?
diff --git a/src/com/android/settings/bluetooth/AvailableMediaBluetoothDeviceUpdater.java b/src/com/android/settings/bluetooth/AvailableMediaBluetoothDeviceUpdater.java
index 27d63bfdb74..14c20f196ab 100644
--- a/src/com/android/settings/bluetooth/AvailableMediaBluetoothDeviceUpdater.java
+++ b/src/com/android/settings/bluetooth/AvailableMediaBluetoothDeviceUpdater.java
@@ -70,9 +70,10 @@ public class AvailableMediaBluetoothDeviceUpdater extends BluetoothDeviceUpdater
if (DBG) {
Log.d(TAG, "isFilterMatched() current audio profile : " + currentAudioProfile);
}
- // If device is Hearing Aid, it is compatible with HFP and A2DP.
+ // If device is Hearing Aid or LE Audio, it is compatible with HFP and A2DP.
// It would show in Available Devices group.
- if (cachedDevice.isConnectedHearingAidDevice()) {
+ if (cachedDevice.isConnectedHearingAidDevice()
+ || cachedDevice.isConnectedLeAudioDevice()) {
return true;
}
// According to the current audio profile type,
diff --git a/src/com/android/settings/bluetooth/BluetoothDetailsMacAddressController.java b/src/com/android/settings/bluetooth/BluetoothDetailsMacAddressController.java
index dda247eac52..c5f845371b4 100644
--- a/src/com/android/settings/bluetooth/BluetoothDetailsMacAddressController.java
+++ b/src/com/android/settings/bluetooth/BluetoothDetailsMacAddressController.java
@@ -16,6 +16,7 @@
package com.android.settings.bluetooth;
+import android.bluetooth.BluetoothCsipSetCoordinator;
import android.content.Context;
import androidx.preference.PreferenceFragmentCompat;
@@ -50,8 +51,17 @@ public class BluetoothDetailsMacAddressController extends BluetoothDetailsContro
@Override
protected void refresh() {
- mFooterPreference.setTitle(mContext.getString(
+ if (mCachedDevice.getGroupId() != BluetoothCsipSetCoordinator.GROUP_ID_INVALID) {
+ StringBuilder mTitle = new StringBuilder(mContext.getString(
+ R.string.bluetooth_multuple_devices_mac_address, mCachedDevice.getAddress()));
+ for (CachedBluetoothDevice member: mCachedDevice.getMemberDevice()) {
+ mTitle.append("\n").append(member.getAddress());
+ }
+ mFooterPreference.setTitle(mTitle);
+ } else {
+ mFooterPreference.setTitle(mContext.getString(
R.string.bluetooth_device_mac_address, mCachedDevice.getAddress()));
+ }
}
@Override
diff --git a/src/com/android/settings/bluetooth/BluetoothPairingController.java b/src/com/android/settings/bluetooth/BluetoothPairingController.java
index ca3dda67384..ec5c8ddf912 100644
--- a/src/com/android/settings/bluetooth/BluetoothPairingController.java
+++ b/src/com/android/settings/bluetooth/BluetoothPairingController.java
@@ -28,6 +28,7 @@ import androidx.annotation.VisibleForTesting;
import com.android.settings.R;
import com.android.settings.bluetooth.BluetoothPairingDialogFragment.BluetoothPairingDialogListener;
+import com.android.settingslib.bluetooth.CachedBluetoothDevice;
import com.android.settingslib.bluetooth.LocalBluetoothManager;
import com.android.settingslib.bluetooth.LocalBluetoothProfile;
@@ -64,6 +65,7 @@ public class BluetoothPairingController implements OnCheckedChangeListener,
private String mDeviceName;
private LocalBluetoothProfile mPbapClientProfile;
private boolean mPbapAllowed;
+ private boolean mIsCoordinatedSetMember;
/**
* Creates an instance of a BluetoothPairingController.
@@ -90,6 +92,10 @@ public class BluetoothPairingController implements OnCheckedChangeListener,
mDeviceName = mBluetoothManager.getCachedDeviceManager().getName(mDevice);
mPbapClientProfile = mBluetoothManager.getProfileManager().getPbapClientProfile();
mPasskeyFormatted = formatKey(mPasskey);
+ final CachedBluetoothDevice cachedDevice =
+ mBluetoothManager.getCachedDeviceManager().findDevice(mDevice);
+ mIsCoordinatedSetMember = (cachedDevice != null)
+ ? cachedDevice.isCoordinatedSetMemberDevice() : false;
}
@Override
@@ -155,6 +161,15 @@ public class BluetoothPairingController implements OnCheckedChangeListener,
return mDeviceName;
}
+ /**
+ * A method for querying if the bluetooth device is a LE coordinated set member device.
+ *
+ * @return - A boolean indicating if the device is a CSIP supported device.
+ */
+ public boolean isCoordinatedSetMemberDevice() {
+ return mIsCoordinatedSetMember;
+ }
+
/**
* A method for querying if the bluetooth device has a profile already set up on this device.
*
diff --git a/src/com/android/settings/bluetooth/BluetoothPairingDialogFragment.java b/src/com/android/settings/bluetooth/BluetoothPairingDialogFragment.java
index d38302d8830..9e3624732d0 100644
--- a/src/com/android/settings/bluetooth/BluetoothPairingDialogFragment.java
+++ b/src/com/android/settings/bluetooth/BluetoothPairingDialogFragment.java
@@ -344,6 +344,9 @@ public class BluetoothPairingDialogFragment extends InstrumentedDialogFragment i
pairingViewContent.setVisibility(View.VISIBLE);
pairingViewContent.setText(mPairingController.getPairingContent());
}
+ final TextView messagePairingSet = (TextView) view.findViewById(R.id.pairing_group_message);
+ messagePairingSet.setVisibility(mPairingController.isCoordinatedSetMemberDevice()
+ ? View.VISIBLE : View.GONE);
return view;
}
diff --git a/src/com/android/settings/bluetooth/BluetoothPairingRequest.java b/src/com/android/settings/bluetooth/BluetoothPairingRequest.java
index 993f584dd36..6b80256285a 100644
--- a/src/com/android/settings/bluetooth/BluetoothPairingRequest.java
+++ b/src/com/android/settings/bluetooth/BluetoothPairingRequest.java
@@ -16,12 +16,16 @@
package com.android.settings.bluetooth;
+import android.bluetooth.BluetoothCsipSetCoordinator;
import android.bluetooth.BluetoothDevice;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.os.PowerManager;
import android.os.UserHandle;
+import android.text.TextUtils;
+
+import com.android.settingslib.bluetooth.LocalBluetoothManager;
/**
* BluetoothPairingRequest is a receiver for any Bluetooth pairing request. It
@@ -34,36 +38,55 @@ public final class BluetoothPairingRequest extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
- if (action == null || !action.equals(BluetoothDevice.ACTION_PAIRING_REQUEST)) {
+ if (action == null) {
return;
}
- PowerManager powerManager = context.getSystemService(PowerManager.class);
BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
- int pairingVariant = intent.getIntExtra(BluetoothDevice.EXTRA_PAIRING_VARIANT,
- BluetoothDevice.ERROR);
- String deviceAddress = device != null ? device.getAddress() : null;
- String deviceName = device != null ? device.getName() : null;
- boolean shouldShowDialog = LocalBluetoothPreferences.shouldShowDialogInForeground(
- context, deviceAddress, deviceName);
+ final LocalBluetoothManager mBluetoothManager = Utils.getLocalBtManager(context);
+ if (TextUtils.equals(action, BluetoothDevice.ACTION_PAIRING_REQUEST)) {
+ PowerManager powerManager = context.getSystemService(PowerManager.class);
+ int pairingVariant = intent.getIntExtra(BluetoothDevice.EXTRA_PAIRING_VARIANT,
+ BluetoothDevice.ERROR);
+ String deviceAddress = device != null ? device.getAddress() : null;
+ String deviceName = device != null ? device.getName() : null;
+ boolean shouldShowDialog = LocalBluetoothPreferences.shouldShowDialogInForeground(
+ context, deviceAddress, deviceName);
- // Skips consent pairing dialog if the device was recently associated with CDM
- if (pairingVariant == BluetoothDevice.PAIRING_VARIANT_CONSENT
- && device.canBondWithoutDialog()) {
- device.setPairingConfirmation(true);
- } else if (powerManager.isInteractive() && shouldShowDialog) {
- // Since the screen is on and the BT-related activity is in the foreground,
- // just open the dialog
- // convert broadcast intent into activity intent (same action string)
- Intent pairingIntent = BluetoothPairingService.getPairingDialogIntent(context, intent,
- BluetoothDevice.EXTRA_PAIRING_INITIATOR_FOREGROUND);
+ // Skips consent pairing dialog if the device was recently associated with CDM
+ if (pairingVariant == BluetoothDevice.PAIRING_VARIANT_CONSENT
+ && (device.canBondWithoutDialog()
+ || mBluetoothManager.getCachedDeviceManager().isOngoingPairByCsip(device))) {
+ device.setPairingConfirmation(true);
+ } else if (powerManager.isInteractive() && shouldShowDialog) {
+ // Since the screen is on and the BT-related activity is in the foreground,
+ // just open the dialog
+ // convert broadcast intent into activity intent (same action string)
+ Intent pairingIntent = BluetoothPairingService.getPairingDialogIntent(context,
+ intent, BluetoothDevice.EXTRA_PAIRING_INITIATOR_FOREGROUND);
- context.startActivityAsUser(pairingIntent, UserHandle.CURRENT);
- } else {
- // Put up a notification that leads to the dialog
- intent.setClass(context, BluetoothPairingService.class);
- intent.setAction(BluetoothDevice.ACTION_PAIRING_REQUEST);
- context.startServiceAsUser(intent, UserHandle.CURRENT);
+ context.startActivityAsUser(pairingIntent, UserHandle.CURRENT);
+ } else {
+ // Put up a notification that leads to the dialog
+ intent.setClass(context, BluetoothPairingService.class);
+ intent.setAction(BluetoothDevice.ACTION_PAIRING_REQUEST);
+ context.startServiceAsUser(intent, UserHandle.CURRENT);
+ }
+ } else if (TextUtils.equals(action,
+ BluetoothCsipSetCoordinator.ACTION_CSIS_SET_MEMBER_AVAILABLE)) {
+ if (device == null) {
+ return;
+ }
+
+ final int groupId = intent.getIntExtra(BluetoothCsipSetCoordinator.EXTRA_CSIS_GROUP_ID,
+ BluetoothCsipSetCoordinator.GROUP_ID_INVALID);
+ if (groupId == BluetoothCsipSetCoordinator.GROUP_ID_INVALID) {
+ return;
+ }
+
+ if (mBluetoothManager.getCachedDeviceManager().shouldPairByCsip(device, groupId)) {
+ device.createBond(BluetoothDevice.TRANSPORT_LE);
+ }
}
}
}
diff --git a/src/com/android/settings/bluetooth/ConnectedBluetoothDeviceUpdater.java b/src/com/android/settings/bluetooth/ConnectedBluetoothDeviceUpdater.java
index fc1b9b734b6..d1c45b61f45 100644
--- a/src/com/android/settings/bluetooth/ConnectedBluetoothDeviceUpdater.java
+++ b/src/com/android/settings/bluetooth/ConnectedBluetoothDeviceUpdater.java
@@ -70,9 +70,10 @@ public class ConnectedBluetoothDeviceUpdater extends BluetoothDeviceUpdater {
if (DBG) {
Log.d(TAG, "isFilterMatched() current audio profile : " + currentAudioProfile);
}
- // If device is Hearing Aid, it is compatible with HFP and A2DP.
+ // If device is Hearing Aid or LE Audio, it is compatible with HFP and A2DP.
// It would not show in Connected Devices group.
- if (cachedDevice.isConnectedHearingAidDevice()) {
+ if (cachedDevice.isConnectedHearingAidDevice()
+ || cachedDevice.isConnectedLeAudioDevice()) {
return false;
}
// According to the current audio profile type,
diff --git a/src/com/android/settings/bluetooth/SavedBluetoothDeviceUpdater.java b/src/com/android/settings/bluetooth/SavedBluetoothDeviceUpdater.java
index dab4f231e35..f5bc279b8a3 100644
--- a/src/com/android/settings/bluetooth/SavedBluetoothDeviceUpdater.java
+++ b/src/com/android/settings/bluetooth/SavedBluetoothDeviceUpdater.java
@@ -65,7 +65,7 @@ public class SavedBluetoothDeviceUpdater extends BluetoothDeviceUpdater
removePreferenceIfNecessary(bluetoothDevices, cachedManager);
for (BluetoothDevice device : bluetoothDevices) {
final CachedBluetoothDevice cachedDevice = cachedManager.findDevice(device);
- if (cachedDevice != null) {
+ if (cachedDevice != null && !cachedManager.isSubDevice(device)) {
update(cachedDevice);
}
}
diff --git a/tests/robotests/src/com/android/settings/bluetooth/AvailableMediaBluetoothDeviceUpdaterTest.java b/tests/robotests/src/com/android/settings/bluetooth/AvailableMediaBluetoothDeviceUpdaterTest.java
index 924e2468de6..013ef5221f6 100644
--- a/tests/robotests/src/com/android/settings/bluetooth/AvailableMediaBluetoothDeviceUpdaterTest.java
+++ b/tests/robotests/src/com/android/settings/bluetooth/AvailableMediaBluetoothDeviceUpdaterTest.java
@@ -234,6 +234,32 @@ public class AvailableMediaBluetoothDeviceUpdaterTest {
verify(mBluetoothDeviceUpdater).addPreference(mCachedBluetoothDevice);
}
+ @Test
+ public void onProfileConnectionStateChanged_leAudioDeviceConnected_notInCall_addPreference() {
+ mAudioManager.setMode(AudioManager.MODE_NORMAL);
+ when(mBluetoothDeviceUpdater
+ .isDeviceConnected(any(CachedBluetoothDevice.class))).thenReturn(true);
+ when(mCachedBluetoothDevice.isConnectedLeAudioDevice()).thenReturn(true);
+
+ mBluetoothDeviceUpdater.onProfileConnectionStateChanged(mCachedBluetoothDevice,
+ BluetoothProfile.STATE_CONNECTED, BluetoothProfile.LE_AUDIO);
+
+ verify(mBluetoothDeviceUpdater).addPreference(mCachedBluetoothDevice);
+ }
+
+ @Test
+ public void onProfileConnectionStateChanged_leAudioDeviceConnected_inCall_addPreference() {
+ mAudioManager.setMode(AudioManager.MODE_IN_CALL);
+ when(mBluetoothDeviceUpdater
+ .isDeviceConnected(any(CachedBluetoothDevice.class))).thenReturn(true);
+ when(mCachedBluetoothDevice.isConnectedLeAudioDevice()).thenReturn(true);
+
+ mBluetoothDeviceUpdater.onProfileConnectionStateChanged(mCachedBluetoothDevice,
+ BluetoothProfile.STATE_CONNECTED, BluetoothProfile.LE_AUDIO);
+
+ verify(mBluetoothDeviceUpdater).addPreference(mCachedBluetoothDevice);
+ }
+
@Test
public void onProfileConnectionStateChanged_deviceDisconnected_removePreference() {
mBluetoothDeviceUpdater.onProfileConnectionStateChanged(mCachedBluetoothDevice,
diff --git a/tests/robotests/src/com/android/settings/bluetooth/BluetoothPairingDialogTest.java b/tests/robotests/src/com/android/settings/bluetooth/BluetoothPairingDialogTest.java
index be733ec97d4..a53e693976e 100644
--- a/tests/robotests/src/com/android/settings/bluetooth/BluetoothPairingDialogTest.java
+++ b/tests/robotests/src/com/android/settings/bluetooth/BluetoothPairingDialogTest.java
@@ -427,6 +427,34 @@ public class BluetoothPairingDialogTest {
userEntryDialogExistingTextTest("test");
}
+ @Test
+ public void groupPairing_setMemberDevice_showsMessageHint() {
+ // set the correct dialog type
+ when(controller.getDialogType()).thenReturn(BluetoothPairingController.CONFIRMATION_DIALOG);
+ when(controller.isCoordinatedSetMemberDevice()).thenReturn(true);
+
+ // build the fragment
+ BluetoothPairingDialogFragment frag = makeFragment();
+
+ // verify message is what we expect it to be and is visible
+ TextView message = frag.getmDialog().findViewById(R.id.pairing_group_message);
+ assertThat(message.getVisibility()).isEqualTo(View.VISIBLE);
+ }
+
+ @Test
+ public void groupPairing_nonSetMemberDevice_hidesMessageHint() {
+ // set the correct dialog type
+ when(controller.getDialogType()).thenReturn(BluetoothPairingController.CONFIRMATION_DIALOG);
+ when(controller.isCoordinatedSetMemberDevice()).thenReturn(false);
+
+ // build the fragment
+ BluetoothPairingDialogFragment frag = makeFragment();
+
+ // verify message is what we expect it to be and is visible
+ TextView message = frag.getmDialog().findViewById(R.id.pairing_group_message);
+ assertThat(message.getVisibility()).isEqualTo(View.GONE);
+ }
+
// Runs a test simulating the user entry dialog type in a situation like device rotation, where
// the dialog fragment gets created and we already have some existing text entered into the
// pin field.
diff --git a/tests/robotests/src/com/android/settings/bluetooth/ConnectedBluetoothDeviceUpdaterTest.java b/tests/robotests/src/com/android/settings/bluetooth/ConnectedBluetoothDeviceUpdaterTest.java
index ea91fed19fc..40b20dcaea3 100644
--- a/tests/robotests/src/com/android/settings/bluetooth/ConnectedBluetoothDeviceUpdaterTest.java
+++ b/tests/robotests/src/com/android/settings/bluetooth/ConnectedBluetoothDeviceUpdaterTest.java
@@ -234,6 +234,33 @@ public class ConnectedBluetoothDeviceUpdaterTest {
verify(mBluetoothDeviceUpdater).removePreference(mCachedBluetoothDevice);
}
+ @Test
+ public void onProfileConnectionStateChanged_leAudioDeviceConnected_inCall_removePreference() {
+ mAudioManager.setMode(AudioManager.MODE_IN_CALL);
+ when(mBluetoothDeviceUpdater
+ .isDeviceConnected(any(CachedBluetoothDevice.class))).thenReturn(true);
+ when(mCachedBluetoothDevice.isConnectedLeAudioDevice()).thenReturn(true);
+
+ mBluetoothDeviceUpdater.onProfileConnectionStateChanged(mCachedBluetoothDevice,
+ BluetoothProfile.STATE_CONNECTED, BluetoothProfile.LE_AUDIO);
+
+ verify(mBluetoothDeviceUpdater).removePreference(mCachedBluetoothDevice);
+ }
+
+ @Test
+ public void onProfileConnectionStateChanged_leAudioDeviceConnected_notInCall_removePreference()
+ {
+ mAudioManager.setMode(AudioManager.MODE_NORMAL);
+ when(mBluetoothDeviceUpdater
+ .isDeviceConnected(any(CachedBluetoothDevice.class))).thenReturn(true);
+ when(mCachedBluetoothDevice.isConnectedLeAudioDevice()).thenReturn(true);
+
+ mBluetoothDeviceUpdater.onProfileConnectionStateChanged(mCachedBluetoothDevice,
+ BluetoothProfile.STATE_CONNECTED, BluetoothProfile.LE_AUDIO);
+
+ verify(mBluetoothDeviceUpdater).removePreference(mCachedBluetoothDevice);
+ }
+
@Test
public void onProfileConnectionStateChanged_deviceDisconnected_removePreference() {
mBluetoothDeviceUpdater.onProfileConnectionStateChanged(mCachedBluetoothDevice,