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,