From bf6e273ccbfed2c7506c9ac23dcfd517c80cfa28 Mon Sep 17 00:00:00 2001 From: Yiyi Shen Date: Wed, 13 Dec 2023 18:14:42 +0800 Subject: [PATCH] [Audiosharing] Update the media device based on assistant callback Bug: 305620450 Test: atest AvailableMediaDeviceGroupControllerTest Change-Id: I7a19edb4e408c59ead553ec10fd9e963674518a6 --- .../AvailableMediaDeviceGroupController.java | 120 ++++++++++++-- ...ailableMediaDeviceGroupControllerTest.java | 148 ++++++++++++------ 2 files changed, 209 insertions(+), 59 deletions(-) diff --git a/src/com/android/settings/connecteddevice/AvailableMediaDeviceGroupController.java b/src/com/android/settings/connecteddevice/AvailableMediaDeviceGroupController.java index c095fee1739..fc3493c9a95 100644 --- a/src/com/android/settings/connecteddevice/AvailableMediaDeviceGroupController.java +++ b/src/com/android/settings/connecteddevice/AvailableMediaDeviceGroupController.java @@ -17,11 +17,16 @@ package com.android.settings.connecteddevice; import static com.android.settingslib.Utils.isAudioModeOngoingCall; +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothLeBroadcastAssistant; +import android.bluetooth.BluetoothLeBroadcastMetadata; +import android.bluetooth.BluetoothLeBroadcastReceiveState; import android.bluetooth.BluetoothProfile; import android.content.Context; import android.content.pm.PackageManager; import android.util.Log; +import androidx.annotation.NonNull; import androidx.annotation.VisibleForTesting; import androidx.fragment.app.FragmentManager; import androidx.preference.Preference; @@ -33,36 +38,94 @@ import com.android.settings.accessibility.HearingAidUtils; import com.android.settings.bluetooth.AvailableMediaBluetoothDeviceUpdater; import com.android.settings.bluetooth.BluetoothDeviceUpdater; import com.android.settings.bluetooth.Utils; +import com.android.settings.connecteddevice.audiosharing.AudioSharingUtils; import com.android.settings.core.BasePreferenceController; import com.android.settings.dashboard.DashboardFragment; import com.android.settingslib.bluetooth.BluetoothCallback; +import com.android.settingslib.bluetooth.BluetoothUtils; import com.android.settingslib.bluetooth.CachedBluetoothDevice; +import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant; import com.android.settingslib.bluetooth.LocalBluetoothManager; import com.android.settingslib.core.lifecycle.LifecycleObserver; import com.android.settingslib.core.lifecycle.events.OnStart; import com.android.settingslib.core.lifecycle.events.OnStop; +import java.util.concurrent.Executor; +import java.util.concurrent.Executors; + /** - * Controller to maintain the {@link androidx.preference.PreferenceGroup} for all - * available media devices. It uses {@link DevicePreferenceCallback} - * to add/remove {@link Preference} + * Controller to maintain the {@link androidx.preference.PreferenceGroup} for all available media + * devices. It uses {@link DevicePreferenceCallback} to add/remove {@link Preference} */ public class AvailableMediaDeviceGroupController extends BasePreferenceController implements LifecycleObserver, OnStart, OnStop, DevicePreferenceCallback, BluetoothCallback { + private static final boolean DEBUG = BluetoothUtils.D; private static final String TAG = "AvailableMediaDeviceGroupController"; private static final String KEY = "available_device_list"; - @VisibleForTesting - PreferenceGroup mPreferenceGroup; - @VisibleForTesting - LocalBluetoothManager mLocalBluetoothManager; + @VisibleForTesting PreferenceGroup mPreferenceGroup; + @VisibleForTesting LocalBluetoothManager mLocalBluetoothManager; + private final Executor mExecutor; private BluetoothDeviceUpdater mBluetoothDeviceUpdater; private FragmentManager mFragmentManager; + private BluetoothLeBroadcastAssistant.Callback mAssistantCallback = + new BluetoothLeBroadcastAssistant.Callback() { + @Override + public void onSearchStarted(int reason) {} + + @Override + public void onSearchStartFailed(int reason) {} + + @Override + public void onSearchStopped(int reason) {} + + @Override + public void onSearchStopFailed(int reason) {} + + @Override + public void onSourceFound(@NonNull BluetoothLeBroadcastMetadata source) {} + + @Override + public void onSourceAdded(@NonNull BluetoothDevice sink, int sourceId, int reason) { + mBluetoothDeviceUpdater.forceUpdate(); + } + + @Override + public void onSourceAddFailed( + @NonNull BluetoothDevice sink, + @NonNull BluetoothLeBroadcastMetadata source, + int reason) {} + + @Override + public void onSourceModified( + @NonNull BluetoothDevice sink, int sourceId, int reason) {} + + @Override + public void onSourceModifyFailed( + @NonNull BluetoothDevice sink, int sourceId, int reason) {} + + @Override + public void onSourceRemoved( + @NonNull BluetoothDevice sink, int sourceId, int reason) { + mBluetoothDeviceUpdater.forceUpdate(); + } + + @Override + public void onSourceRemoveFailed( + @NonNull BluetoothDevice sink, int sourceId, int reason) {} + + @Override + public void onReceiveStateChanged( + BluetoothDevice sink, + int sourceId, + BluetoothLeBroadcastReceiveState state) {} + }; public AvailableMediaDeviceGroupController(Context context) { super(context, KEY); mLocalBluetoothManager = Utils.getLocalBtManager(mContext); + mExecutor = Executors.newSingleThreadExecutor(); } @Override @@ -71,6 +134,18 @@ public class AvailableMediaDeviceGroupController extends BasePreferenceControlle Log.e(TAG, "onStart() Bluetooth is not supported on this device"); return; } + if (AudioSharingUtils.isFeatureEnabled()) { + LocalBluetoothLeBroadcastAssistant assistant = + mLocalBluetoothManager + .getProfileManager() + .getLeAudioBroadcastAssistantProfile(); + if (assistant != null) { + if (DEBUG) { + Log.d(TAG, "onStart() Register callbacks for assistant."); + } + assistant.registerServiceCallBack(mExecutor, mAssistantCallback); + } + } mBluetoothDeviceUpdater.registerCallback(); mLocalBluetoothManager.getEventManager().registerCallback(this); mBluetoothDeviceUpdater.refreshPreference(); @@ -82,6 +157,18 @@ public class AvailableMediaDeviceGroupController extends BasePreferenceControlle Log.e(TAG, "onStop() Bluetooth is not supported on this device"); return; } + if (AudioSharingUtils.isFeatureEnabled()) { + LocalBluetoothLeBroadcastAssistant assistant = + mLocalBluetoothManager + .getProfileManager() + .getLeAudioBroadcastAssistantProfile(); + if (assistant != null) { + if (DEBUG) { + Log.d(TAG, "onStop() Register callbacks for assistant."); + } + assistant.unregisterServiceCallBack(mAssistantCallback); + } + } mBluetoothDeviceUpdater.unregisterCallback(); mLocalBluetoothManager.getEventManager().unregisterCallback(this); } @@ -130,8 +217,11 @@ public class AvailableMediaDeviceGroupController extends BasePreferenceControlle public void init(DashboardFragment fragment) { mFragmentManager = fragment.getParentFragmentManager(); - mBluetoothDeviceUpdater = new AvailableMediaBluetoothDeviceUpdater(fragment.getContext(), - AvailableMediaDeviceGroupController.this, fragment.getMetricsCategory()); + mBluetoothDeviceUpdater = + new AvailableMediaBluetoothDeviceUpdater( + fragment.getContext(), + AvailableMediaDeviceGroupController.this, + fragment.getMetricsCategory()); } @VisibleForTesting @@ -157,20 +247,20 @@ public class AvailableMediaDeviceGroupController extends BasePreferenceControlle } if (bluetoothProfile == BluetoothProfile.HEARING_AID) { - HearingAidUtils.launchHearingAidPairingDialog(mFragmentManager, activeDevice, - getMetricsCategory()); + HearingAidUtils.launchHearingAidPairingDialog( + mFragmentManager, activeDevice, getMetricsCategory()); } } private void updateTitle() { if (isAudioModeOngoingCall(mContext)) { // in phone call - mPreferenceGroup. - setTitle(mContext.getString(R.string.connected_device_call_device_title)); + mPreferenceGroup.setTitle( + mContext.getString(R.string.connected_device_call_device_title)); } else { // without phone call - mPreferenceGroup. - setTitle(mContext.getString(R.string.connected_device_media_device_title)); + mPreferenceGroup.setTitle( + mContext.getString(R.string.connected_device_media_device_title)); } } } diff --git a/tests/robotests/src/com/android/settings/connecteddevice/AvailableMediaDeviceGroupControllerTest.java b/tests/robotests/src/com/android/settings/connecteddevice/AvailableMediaDeviceGroupControllerTest.java index 06dd42bc8cf..e5964d06e5c 100644 --- a/tests/robotests/src/com/android/settings/connecteddevice/AvailableMediaDeviceGroupControllerTest.java +++ b/tests/robotests/src/com/android/settings/connecteddevice/AvailableMediaDeviceGroupControllerTest.java @@ -17,20 +17,29 @@ package com.android.settings.connecteddevice; import static com.android.settings.core.BasePreferenceController.AVAILABLE_UNSEARCHABLE; import static com.android.settings.core.BasePreferenceController.UNSUPPORTED_ON_DEVICE; + import static com.google.common.truth.Truth.assertThat; + import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static org.robolectric.shadows.ShadowLooper.shadowMainLooper; +import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothLeBroadcastAssistant; import android.bluetooth.BluetoothProfile; +import android.bluetooth.BluetoothStatusCodes; import android.content.Context; import android.content.pm.PackageManager; import android.media.AudioManager; +import android.platform.test.annotations.RequiresFlagsEnabled; +import android.platform.test.flag.junit.CheckFlagsRule; +import android.platform.test.flag.junit.DeviceFlagsValueProvider; import androidx.appcompat.app.AlertDialog; import androidx.fragment.app.FragmentActivity; @@ -42,6 +51,7 @@ import androidx.preference.PreferenceScreen; import com.android.settings.R; import com.android.settings.bluetooth.AvailableMediaBluetoothDeviceUpdater; import com.android.settings.bluetooth.Utils; +import com.android.settings.flags.Flags; import com.android.settings.testutils.shadow.ShadowAlertDialogCompat; import com.android.settings.testutils.shadow.ShadowAudioManager; import com.android.settings.testutils.shadow.ShadowBluetoothAdapter; @@ -51,9 +61,12 @@ import com.android.settingslib.bluetooth.BluetoothEventManager; import com.android.settingslib.bluetooth.CachedBluetoothDevice; import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager; import com.android.settingslib.bluetooth.HearingAidInfo; +import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant; import com.android.settingslib.bluetooth.LocalBluetoothManager; +import com.android.settingslib.bluetooth.LocalBluetoothProfileManager; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Answers; @@ -63,42 +76,46 @@ import org.robolectric.Robolectric; import org.robolectric.RobolectricTestRunner; import org.robolectric.RuntimeEnvironment; import org.robolectric.annotation.Config; +import org.robolectric.shadow.api.Shadow; + +import java.util.concurrent.Executor; /** Tests for {@link AvailableMediaDeviceGroupController}. */ @RunWith(RobolectricTestRunner.class) -@Config(shadows = { - ShadowAudioManager.class, - ShadowBluetoothAdapter.class, - ShadowBluetoothUtils.class, - ShadowAlertDialogCompat.class, -}) +@Config( + shadows = { + ShadowAudioManager.class, + ShadowBluetoothAdapter.class, + ShadowBluetoothUtils.class, + ShadowAlertDialogCompat.class, + }) public class AvailableMediaDeviceGroupControllerTest { + @Rule + public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule(); private static final String TEST_DEVICE_ADDRESS = "00:A1:A1:A1:A1:A1"; private static final String PREFERENCE_KEY_1 = "pref_key_1"; - @Mock - private AvailableMediaBluetoothDeviceUpdater mAvailableMediaBluetoothDeviceUpdater; - @Mock - private PreferenceScreen mPreferenceScreen; + @Mock private AvailableMediaBluetoothDeviceUpdater mAvailableMediaBluetoothDeviceUpdater; + @Mock private PreferenceScreen mPreferenceScreen; + @Mock(answer = Answers.RETURNS_DEEP_STUBS) private PreferenceManager mPreferenceManager; - @Mock - private PackageManager mPackageManager; - @Mock - private BluetoothEventManager mEventManager; - @Mock - private LocalBluetoothManager mLocalBluetoothManager; - @Mock - private CachedBluetoothDeviceManager mCachedDeviceManager; - @Mock - private CachedBluetoothDevice mCachedBluetoothDevice; + + @Mock private PackageManager mPackageManager; + @Mock private BluetoothEventManager mEventManager; + @Mock private LocalBluetoothManager mLocalBluetoothManager; + @Mock private LocalBluetoothProfileManager mLocalBtProfileManager; + @Mock private CachedBluetoothDeviceManager mCachedDeviceManager; + @Mock private LocalBluetoothLeBroadcastAssistant mAssistant; + @Mock private CachedBluetoothDevice mCachedBluetoothDevice; private PreferenceGroup mPreferenceGroup; private Context mContext; private Preference mPreference; private AvailableMediaDeviceGroupController mAvailableMediaDeviceGroupController; private AudioManager mAudioManager; + private ShadowBluetoothAdapter mShadowBluetoothAdapter; @Before public void setUp() { @@ -113,19 +130,26 @@ public class AvailableMediaDeviceGroupControllerTest { doReturn(mPackageManager).when(mContext).getPackageManager(); doReturn(true).when(mPackageManager).hasSystemFeature(PackageManager.FEATURE_BLUETOOTH); + mShadowBluetoothAdapter = Shadow.extract(BluetoothAdapter.getDefaultAdapter()); + mShadowBluetoothAdapter.setEnabled(true); + mShadowBluetoothAdapter.setIsLeAudioBroadcastSourceSupported( + BluetoothStatusCodes.FEATURE_NOT_SUPPORTED); + mShadowBluetoothAdapter.setIsLeAudioBroadcastAssistantSupported( + BluetoothStatusCodes.FEATURE_NOT_SUPPORTED); ShadowBluetoothUtils.sLocalBluetoothManager = mLocalBluetoothManager; mLocalBluetoothManager = Utils.getLocalBtManager(mContext); mAudioManager = mContext.getSystemService(AudioManager.class); doReturn(mEventManager).when(mLocalBluetoothManager).getEventManager(); + when(mLocalBluetoothManager.getProfileManager()).thenReturn(mLocalBtProfileManager); when(mLocalBluetoothManager.getCachedDeviceManager()).thenReturn(mCachedDeviceManager); - when(mCachedDeviceManager.findDevice(any(BluetoothDevice.class))).thenReturn( - mCachedBluetoothDevice); + when(mCachedDeviceManager.findDevice(any(BluetoothDevice.class))) + .thenReturn(mCachedBluetoothDevice); when(mCachedBluetoothDevice.getAddress()).thenReturn(TEST_DEVICE_ADDRESS); - mAvailableMediaDeviceGroupController = spy( - new AvailableMediaDeviceGroupController(mContext)); - mAvailableMediaDeviceGroupController. - setBluetoothDeviceUpdater(mAvailableMediaBluetoothDeviceUpdater); + mAvailableMediaDeviceGroupController = + spy(new AvailableMediaDeviceGroupController(mContext)); + mAvailableMediaDeviceGroupController.setBluetoothDeviceUpdater( + mAvailableMediaBluetoothDeviceUpdater); mAvailableMediaDeviceGroupController.setFragmentManager( mActivity.getSupportFragmentManager()); mAvailableMediaDeviceGroupController.mPreferenceGroup = mPreferenceGroup; @@ -176,34 +200,55 @@ public class AvailableMediaDeviceGroupControllerTest { mAvailableMediaDeviceGroupController.onStart(); verify(mAvailableMediaBluetoothDeviceUpdater).registerCallback(); - verify(mLocalBluetoothManager.getEventManager()).registerCallback( - any(BluetoothCallback.class)); + verify(mLocalBluetoothManager.getEventManager()) + .registerCallback(any(BluetoothCallback.class)); verify(mAvailableMediaBluetoothDeviceUpdater).refreshPreference(); } + @Test + @RequiresFlagsEnabled(Flags.FLAG_ENABLE_LE_AUDIO_SHARING) + public void testRegister_audioSharingOn() { + setUpBroadcast(); + // register the callback in onStart() + mAvailableMediaDeviceGroupController.onStart(); + verify(mAssistant) + .registerServiceCallBack( + any(Executor.class), any(BluetoothLeBroadcastAssistant.Callback.class)); + } + @Test public void testUnregister() { // unregister the callback in onStop() mAvailableMediaDeviceGroupController.onStop(); verify(mAvailableMediaBluetoothDeviceUpdater).unregisterCallback(); - verify(mLocalBluetoothManager.getEventManager()).unregisterCallback( - any(BluetoothCallback.class)); + verify(mLocalBluetoothManager.getEventManager()) + .unregisterCallback(any(BluetoothCallback.class)); + } + + @Test + @RequiresFlagsEnabled(Flags.FLAG_ENABLE_LE_AUDIO_SHARING) + public void testUnregister_audioSharingOn() { + setUpBroadcast(); + // unregister the callback in onStop() + mAvailableMediaDeviceGroupController.onStop(); + verify(mAssistant) + .unregisterServiceCallBack(any(BluetoothLeBroadcastAssistant.Callback.class)); } @Test public void testGetAvailabilityStatus_noBluetoothFeature_returnUnSupported() { doReturn(false).when(mPackageManager).hasSystemFeature(PackageManager.FEATURE_BLUETOOTH); - assertThat(mAvailableMediaDeviceGroupController.getAvailabilityStatus()).isEqualTo( - UNSUPPORTED_ON_DEVICE); + assertThat(mAvailableMediaDeviceGroupController.getAvailabilityStatus()) + .isEqualTo(UNSUPPORTED_ON_DEVICE); } @Test public void testGetAvailabilityStatus_BluetoothFeature_returnSupported() { doReturn(true).when(mPackageManager).hasSystemFeature(PackageManager.FEATURE_BLUETOOTH); - assertThat(mAvailableMediaDeviceGroupController.getAvailabilityStatus()).isEqualTo( - AVAILABLE_UNSEARCHABLE); + assertThat(mAvailableMediaDeviceGroupController.getAvailabilityStatus()) + .isEqualTo(AVAILABLE_UNSEARCHABLE); } @Test @@ -211,8 +256,8 @@ public class AvailableMediaDeviceGroupControllerTest { mAudioManager.setMode(AudioManager.MODE_IN_CALL); mAvailableMediaDeviceGroupController.onAudioModeChanged(); - assertThat(mPreferenceGroup.getTitle()).isEqualTo( - mContext.getText(R.string.connected_device_call_device_title)); + assertThat(mPreferenceGroup.getTitle()) + .isEqualTo(mContext.getText(R.string.connected_device_call_device_title)); } @Test @@ -220,8 +265,8 @@ public class AvailableMediaDeviceGroupControllerTest { mAudioManager.setMode(AudioManager.MODE_NORMAL); mAvailableMediaDeviceGroupController.onAudioModeChanged(); - assertThat(mPreferenceGroup.getTitle()).isEqualTo( - mContext.getText(R.string.connected_device_media_device_title)); + assertThat(mPreferenceGroup.getTitle()) + .isEqualTo(mContext.getText(R.string.connected_device_media_device_title)); } @Test @@ -243,16 +288,31 @@ public class AvailableMediaDeviceGroupControllerTest { @Test public void onActiveDeviceChanged_hearingAidProfile_launchHearingAidPairingDialog() { when(mCachedBluetoothDevice.isConnectedAshaHearingAidDevice()).thenReturn(true); - when(mCachedBluetoothDevice.getDeviceMode()).thenReturn( - HearingAidInfo.DeviceMode.MODE_BINAURAL); - when(mCachedBluetoothDevice.getDeviceSide()).thenReturn( - HearingAidInfo.DeviceSide.SIDE_LEFT); + when(mCachedBluetoothDevice.getDeviceMode()) + .thenReturn(HearingAidInfo.DeviceMode.MODE_BINAURAL); + when(mCachedBluetoothDevice.getDeviceSide()) + .thenReturn(HearingAidInfo.DeviceSide.SIDE_LEFT); - mAvailableMediaDeviceGroupController.onActiveDeviceChanged(mCachedBluetoothDevice, - BluetoothProfile.HEARING_AID); + mAvailableMediaDeviceGroupController.onActiveDeviceChanged( + mCachedBluetoothDevice, BluetoothProfile.HEARING_AID); shadowMainLooper().idle(); final AlertDialog dialog = ShadowAlertDialogCompat.getLatestAlertDialog(); assertThat(dialog.isShowing()).isTrue(); } + + private void setUpBroadcast() { + mShadowBluetoothAdapter.setIsLeAudioBroadcastSourceSupported( + BluetoothStatusCodes.FEATURE_SUPPORTED); + mShadowBluetoothAdapter.setIsLeAudioBroadcastAssistantSupported( + BluetoothStatusCodes.FEATURE_SUPPORTED); + when(mLocalBtProfileManager.getLeAudioBroadcastAssistantProfile()).thenReturn(mAssistant); + doNothing() + .when(mAssistant) + .registerServiceCallBack( + any(Executor.class), any(BluetoothLeBroadcastAssistant.Callback.class)); + doNothing() + .when(mAssistant) + .unregisterServiceCallBack(any(BluetoothLeBroadcastAssistant.Callback.class)); + } }