Merge "[Audiosharing] Use setBroadcastToUnicastFallbackGroup to set primary" into main

This commit is contained in:
Yiyi Shen
2025-01-15 02:34:03 -08:00
committed by Android (Google) Code Review
7 changed files with 88 additions and 29 deletions

View File

@@ -16,6 +16,8 @@
package com.android.settings.connecteddevice.audiosharing; package com.android.settings.connecteddevice.audiosharing;
import static com.android.settingslib.Utils.isAudioModeOngoingCall;
import android.app.settings.SettingsEnums; import android.app.settings.SettingsEnums;
import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothCsipSetCoordinator; import android.bluetooth.BluetoothCsipSetCoordinator;
@@ -48,6 +50,7 @@ import com.android.settingslib.bluetooth.BluetoothEventManager;
import com.android.settingslib.bluetooth.BluetoothUtils; import com.android.settingslib.bluetooth.BluetoothUtils;
import com.android.settingslib.bluetooth.CachedBluetoothDevice; import com.android.settingslib.bluetooth.CachedBluetoothDevice;
import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager; import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager;
import com.android.settingslib.bluetooth.LeAudioProfile;
import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant; import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant;
import com.android.settingslib.bluetooth.LocalBluetoothManager; import com.android.settingslib.bluetooth.LocalBluetoothManager;
import com.android.settingslib.bluetooth.LocalBluetoothProfileManager; import com.android.settingslib.bluetooth.LocalBluetoothProfileManager;
@@ -91,6 +94,7 @@ public class AudioSharingCallAudioPreferenceController extends AudioSharingBaseP
Map<Integer, List<BluetoothDevice>> mGroupedConnectedDevices = new HashMap<>(); Map<Integer, List<BluetoothDevice>> mGroupedConnectedDevices = new HashMap<>();
private List<AudioSharingDeviceItem> mDeviceItemsInSharingSession = new ArrayList<>(); private List<AudioSharingDeviceItem> mDeviceItemsInSharingSession = new ArrayList<>();
private final AtomicBoolean mCallbacksRegistered = new AtomicBoolean(false); private final AtomicBoolean mCallbacksRegistered = new AtomicBoolean(false);
private AtomicBoolean mIsAudioModeOngoingCall = new AtomicBoolean(false);
@VisibleForTesting @VisibleForTesting
final BluetoothLeBroadcastAssistant.Callback mBroadcastAssistantCallback = final BluetoothLeBroadcastAssistant.Callback mBroadcastAssistantCallback =
@@ -202,28 +206,15 @@ public class AudioSharingCallAudioPreferenceController extends AudioSharingBaseP
mDeviceItemsInSharingSession, mDeviceItemsInSharingSession,
pair == null ? -1 : pair.first, pair == null ? -1 : pair.first,
(AudioSharingDeviceItem item) -> { (AudioSharingDeviceItem item) -> {
int currentGroupId = int currentCallAudioGroupId =
BluetoothUtils.getPrimaryGroupIdForBroadcast( BluetoothUtils.getPrimaryGroupIdForBroadcast(
mContext.getContentResolver()); mContext.getContentResolver());
int clickedGroupId = item.getGroupId(); int clickedGroupId = item.getGroupId();
if (clickedGroupId == currentGroupId) { if (clickedGroupId == currentCallAudioGroupId) {
Log.d(TAG, "Skip set call audio device: unchanged"); Log.d(TAG, "Skip set call audio device: unchanged");
return; return;
} }
List<BluetoothDevice> devices = setCallAudioGroup(clickedGroupId);
mGroupedConnectedDevices.getOrDefault(
clickedGroupId, ImmutableList.of());
CachedBluetoothDevice lead =
AudioSharingUtils.getLeadDevice(
mCacheManager, devices);
if (lead != null) {
String addr = lead.getDevice().getAnonymizedAddress();
Log.d(TAG, "Set call audio device: " + addr);
AudioSharingUtils.setPrimary(mContext, lead);
logCallAudioDeviceChange(currentGroupId, lead);
} else {
Log.d(TAG, "Skip set call audio device: no lead");
}
}); });
} }
return true; return true;
@@ -269,6 +260,11 @@ public class AudioSharingCallAudioPreferenceController extends AudioSharingBaseP
} }
} }
@Override
public void onAudioModeChanged() {
mIsAudioModeOngoingCall.set(isAudioModeOngoingCall(mContext));
}
/** /**
* Initialize the controller. * Initialize the controller.
* *
@@ -311,6 +307,7 @@ public class AudioSharingCallAudioPreferenceController extends AudioSharingBaseP
false, false,
mSettingsObserver); mSettingsObserver);
mAssistant.registerServiceCallBack(mExecutor, mBroadcastAssistantCallback); mAssistant.registerServiceCallBack(mExecutor, mBroadcastAssistantCallback);
mIsAudioModeOngoingCall.set(isAudioModeOngoingCall(mContext));
mCallbacksRegistered.set(true); mCallbacksRegistered.set(true);
} }
} }
@@ -333,6 +330,32 @@ public class AudioSharingCallAudioPreferenceController extends AudioSharingBaseP
} }
} }
private void setCallAudioGroup(int groupId) {
List<BluetoothDevice> devices =
mGroupedConnectedDevices.getOrDefault(
groupId, ImmutableList.of());
CachedBluetoothDevice lead =
AudioSharingUtils.getLeadDevice(
mCacheManager, devices);
if (lead != null) {
String addr = lead.getDevice().getAnonymizedAddress();
Log.d(TAG, "Set call audio device: " + addr);
if (Flags.adoptPrimaryGroupManagementApi() && !mIsAudioModeOngoingCall.get()) {
LeAudioProfile leaProfile = mBtManager == null ? null
: mBtManager.getProfileManager().getLeAudioProfile();
if (leaProfile != null) {
leaProfile.setBroadcastToUnicastFallbackGroup(groupId);
}
} else {
lead.setActive();
}
AudioSharingUtils.setUserPreferredPrimary(mContext, lead);
logCallAudioDeviceChange(groupId, lead);
} else {
Log.d(TAG, "Skip set call audio device: no lead");
}
}
/** /**
* Update the preference summary: current headset for call audio. * Update the preference summary: current headset for call audio.
* *

View File

@@ -389,7 +389,8 @@ public class AudioSharingDevicePreferenceController extends BasePreferenceContro
Log.d(TAG, "onDeviceClick, set active in call mode"); Log.d(TAG, "onDeviceClick, set active in call mode");
CachedBluetoothDevice cachedDevice = CachedBluetoothDevice cachedDevice =
((BluetoothDevicePreference) preference).getBluetoothDevice(); ((BluetoothDevicePreference) preference).getBluetoothDevice();
AudioSharingUtils.setPrimary(mContext, cachedDevice); cachedDevice.setActive();
AudioSharingUtils.setUserPreferredPrimary(mContext, cachedDevice);
} }
mMetricsFeatureProvider.action(mContext, SettingsEnums.ACTION_AUDIO_SHARING_DEVICE_CLICK, mMetricsFeatureProvider.action(mContext, SettingsEnums.ACTION_AUDIO_SHARING_DEVICE_CLICK,
isCallMode); isCallMode);

View File

@@ -192,7 +192,8 @@ public class AudioSharingDialogHandler {
// If this method is called with user triggered, e.g. manual click on the // If this method is called with user triggered, e.g. manual click on the
// "Connected devices" page, we need call setActive for the device, since user // "Connected devices" page, we need call setActive for the device, since user
// intend to switch active device for the call. // intend to switch active device for the call.
AudioSharingUtils.setPrimary(mContext, cachedDevice); cachedDevice.setActive();
AudioSharingUtils.setUserPreferredPrimary(mContext, cachedDevice);
} }
return; return;
} }

View File

@@ -346,11 +346,10 @@ public class AudioSharingUtils {
return vc != null && vc.isProfileReady(); return vc != null && vc.isProfileReady();
} }
/** Set {@link CachedBluetoothDevice} as primary device for call audio */ /** Set {@link CachedBluetoothDevice} as user preferred primary device for call audio */
public static void setPrimary( public static void setUserPreferredPrimary(
@NonNull Context context, @Nullable CachedBluetoothDevice cachedDevice) { @NonNull Context context, @Nullable CachedBluetoothDevice cachedDevice) {
if (cachedDevice == null) return; if (cachedDevice == null) return;
cachedDevice.setActive();
if (BluetoothUtils.isAudioSharingHysteresisModeFixAvailable(context)) { if (BluetoothUtils.isAudioSharingHysteresisModeFixAvailable(context)) {
int groupId = BluetoothUtils.getGroupId(cachedDevice); int groupId = BluetoothUtils.getGroupId(cachedDevice);
// TODO: use real key name in SettingsProvider // TODO: use real key name in SettingsProvider

View File

@@ -24,6 +24,7 @@ import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never; import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
@@ -67,6 +68,7 @@ import com.android.settingslib.bluetooth.BluetoothEventManager;
import com.android.settingslib.bluetooth.BluetoothUtils; import com.android.settingslib.bluetooth.BluetoothUtils;
import com.android.settingslib.bluetooth.CachedBluetoothDevice; import com.android.settingslib.bluetooth.CachedBluetoothDevice;
import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager; import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager;
import com.android.settingslib.bluetooth.LeAudioProfile;
import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcast; import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcast;
import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant; import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant;
import com.android.settingslib.bluetooth.LocalBluetoothManager; import com.android.settingslib.bluetooth.LocalBluetoothManager;
@@ -77,7 +79,6 @@ import com.android.settingslib.flags.Flags;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import org.junit.After; import org.junit.After;
import org.junit.Before; import org.junit.Before;
@@ -89,8 +90,10 @@ import org.mockito.Spy;
import org.mockito.junit.MockitoJUnit; import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule; import org.mockito.junit.MockitoRule;
import org.robolectric.RobolectricTestRunner; import org.robolectric.RobolectricTestRunner;
import org.robolectric.Shadows;
import org.robolectric.annotation.Config; import org.robolectric.annotation.Config;
import org.robolectric.shadow.api.Shadow; import org.robolectric.shadow.api.Shadow;
import org.robolectric.shadows.ShadowListView;
import org.robolectric.shadows.androidx.fragment.FragmentController; import org.robolectric.shadows.androidx.fragment.FragmentController;
import java.util.ArrayList; import java.util.ArrayList;
@@ -483,19 +486,46 @@ public class AudioSharingCallAudioPreferenceControllerTest {
AlertDialog dialog = ShadowAlertDialogCompat.getLatestAlertDialog(); AlertDialog dialog = ShadowAlertDialogCompat.getLatestAlertDialog();
assertThat(dialog.isShowing()).isTrue(); assertThat(dialog.isShowing()).isTrue();
assertThat(dialog.getListView().getCount()).isEqualTo(2); assertThat(dialog.getListView().getCount()).isEqualTo(2);
ArrayList<View> outViews = new ArrayList<>(); ShadowListView listView = Shadows.shadowOf(dialog.getListView());
dialog.getListView() View view1 = listView.findItemContainingText(TEST_DEVICE_NAME1);
.findViewsWithText(outViews, TEST_DEVICE_NAME1, View.FIND_VIEWS_WITH_TEXT); assertThat(view1).isNotNull();
assertThat(outViews.size()).isEqualTo(1); assertThat(view1 instanceof CheckedTextView).isTrue();
View view = Iterables.getOnlyElement(outViews); assertThat(((CheckedTextView) view1).isChecked()).isTrue();
assertThat(view instanceof CheckedTextView).isTrue(); View view2 = listView.findItemContainingText(TEST_DEVICE_NAME2);
assertThat(((CheckedTextView) view).isChecked()).isTrue(); assertThat(view2).isNotNull();
assertThat(view2 instanceof CheckedTextView).isTrue();
assertThat(((CheckedTextView) view2).isChecked()).isFalse();
verify(mFeatureFactory.metricsFeatureProvider) verify(mFeatureFactory.metricsFeatureProvider)
.visible( .visible(
/* context= */ eq(null), /* context= */ eq(null),
/* source= */ anyInt(), /* source= */ anyInt(),
eq(SettingsEnums.DIALOG_AUDIO_SHARING_CALL_AUDIO), eq(SettingsEnums.DIALOG_AUDIO_SHARING_CALL_AUDIO),
/* latency= */ anyInt()); /* latency= */ anyInt());
LeAudioProfile leAudioProfile = mock(LeAudioProfile.class);
when(mBtProfileManager.getLeAudioProfile()).thenReturn(leAudioProfile);
// Perform click to switch call audio device by set active
mSetFlagsRule.disableFlags(Flags.FLAG_ADOPT_PRIMARY_GROUP_MANAGEMENT_API);
int index = listView.findIndexOfItemContainingText(TEST_DEVICE_NAME2);
listView.performItemClick(index);
shadowOf(Looper.getMainLooper()).idle();
assertThat(((CheckedTextView) view1).isChecked()).isFalse();
assertThat(((CheckedTextView) view2).isChecked()).isTrue();
verify(mCachedDevice3).setActive();
verify(leAudioProfile, never()).setBroadcastToUnicastFallbackGroup(TEST_DEVICE_GROUP_ID2);
// Perform click to switch call audio device with API
mSetFlagsRule.enableFlags(Flags.FLAG_ADOPT_PRIMARY_GROUP_MANAGEMENT_API);
Settings.Secure.putInt(mContentResolver, TEST_SETTINGS_KEY, TEST_DEVICE_GROUP_ID2);
index = listView.findIndexOfItemContainingText(TEST_DEVICE_NAME1);
listView.performItemClick(index);
shadowOf(Looper.getMainLooper()).idle();
assertThat(((CheckedTextView) view1).isChecked()).isTrue();
assertThat(((CheckedTextView) view2).isChecked()).isFalse();
verify(mCachedDevice1, never()).setActive();
verify(leAudioProfile).setBroadcastToUnicastFallbackGroup(TEST_DEVICE_GROUP_ID1);
} }
@Test @Test

View File

@@ -584,6 +584,7 @@ public class AudioSharingDevicePreferenceControllerTest {
public void testInCallState_showCallStateTitleAndSetActiveOnDeviceClick() { public void testInCallState_showCallStateTitleAndSetActiveOnDeviceClick() {
mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING); mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
mSetFlagsRule.disableFlags(Flags.FLAG_AUDIO_SHARING_HYSTERESIS_MODE_FIX); mSetFlagsRule.disableFlags(Flags.FLAG_AUDIO_SHARING_HYSTERESIS_MODE_FIX);
mSetFlagsRule.enableFlags(Flags.FLAG_ADOPT_PRIMARY_GROUP_MANAGEMENT_API);
Settings.Secure.putInt(mContext.getContentResolver(), Settings.Secure.putInt(mContext.getContentResolver(),
BLUETOOTH_LE_BROADCAST_PRIMARY_DEVICE_GROUP_ID, BLUETOOTH_LE_BROADCAST_PRIMARY_DEVICE_GROUP_ID,
BluetoothCsipSetCoordinator.GROUP_ID_INVALID); BluetoothCsipSetCoordinator.GROUP_ID_INVALID);
@@ -609,6 +610,7 @@ public class AudioSharingDevicePreferenceControllerTest {
public void testInCallState_enableHysteresisFix_setAndSaveActiveOnDeviceClick() { public void testInCallState_enableHysteresisFix_setAndSaveActiveOnDeviceClick() {
mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING); mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
mSetFlagsRule.enableFlags(Flags.FLAG_AUDIO_SHARING_HYSTERESIS_MODE_FIX); mSetFlagsRule.enableFlags(Flags.FLAG_AUDIO_SHARING_HYSTERESIS_MODE_FIX);
mSetFlagsRule.enableFlags(Flags.FLAG_ADOPT_PRIMARY_GROUP_MANAGEMENT_API);
Settings.Secure.putInt(mContext.getContentResolver(), Settings.Secure.putInt(mContext.getContentResolver(),
BLUETOOTH_LE_BROADCAST_PRIMARY_DEVICE_GROUP_ID, BLUETOOTH_LE_BROADCAST_PRIMARY_DEVICE_GROUP_ID,
BluetoothCsipSetCoordinator.GROUP_ID_INVALID); BluetoothCsipSetCoordinator.GROUP_ID_INVALID);

View File

@@ -198,6 +198,7 @@ public class AudioSharingDialogHandlerTest {
@Test @Test
public void handleUserTriggeredDeviceConnected_inCall_setActive() { public void handleUserTriggeredDeviceConnected_inCall_setActive() {
mSetFlagsRule.disableFlags(Flags.FLAG_AUDIO_SHARING_HYSTERESIS_MODE_FIX); mSetFlagsRule.disableFlags(Flags.FLAG_AUDIO_SHARING_HYSTERESIS_MODE_FIX);
mSetFlagsRule.enableFlags(Flags.FLAG_ADOPT_PRIMARY_GROUP_MANAGEMENT_API);
Settings.Secure.putInt(mContext.getContentResolver(), Settings.Secure.putInt(mContext.getContentResolver(),
BLUETOOTH_LE_BROADCAST_PRIMARY_DEVICE_GROUP_ID, BLUETOOTH_LE_BROADCAST_PRIMARY_DEVICE_GROUP_ID,
BluetoothCsipSetCoordinator.GROUP_ID_INVALID); BluetoothCsipSetCoordinator.GROUP_ID_INVALID);
@@ -218,6 +219,7 @@ public class AudioSharingDialogHandlerTest {
@Test @Test
public void handleUserTriggeredDeviceConnected_inCall_enableHysteresisFix_setAndSaveActive() { public void handleUserTriggeredDeviceConnected_inCall_enableHysteresisFix_setAndSaveActive() {
mSetFlagsRule.enableFlags(Flags.FLAG_AUDIO_SHARING_HYSTERESIS_MODE_FIX); mSetFlagsRule.enableFlags(Flags.FLAG_AUDIO_SHARING_HYSTERESIS_MODE_FIX);
mSetFlagsRule.enableFlags(Flags.FLAG_ADOPT_PRIMARY_GROUP_MANAGEMENT_API);
Settings.Secure.putInt(mContext.getContentResolver(), Settings.Secure.putInt(mContext.getContentResolver(),
BLUETOOTH_LE_BROADCAST_PRIMARY_DEVICE_GROUP_ID, BLUETOOTH_LE_BROADCAST_PRIMARY_DEVICE_GROUP_ID,
BluetoothCsipSetCoordinator.GROUP_ID_INVALID); BluetoothCsipSetCoordinator.GROUP_ID_INVALID);
@@ -452,6 +454,7 @@ public class AudioSharingDialogHandlerTest {
@Test @Test
public void handleDeviceConnected_inCall_doNothing() { public void handleDeviceConnected_inCall_doNothing() {
mSetFlagsRule.enableFlags(Flags.FLAG_ADOPT_PRIMARY_GROUP_MANAGEMENT_API);
when(mAudioManager.getMode()).thenReturn(AudioManager.MODE_IN_CALL); when(mAudioManager.getMode()).thenReturn(AudioManager.MODE_IN_CALL);
setUpBroadcast(true); setUpBroadcast(true);
when(mAssistant.getAllConnectedDevices()).thenReturn(ImmutableList.of()); when(mAssistant.getAllConnectedDevices()).thenReturn(ImmutableList.of());