Merge "[Audiosharing] Update the media device based on assistant callback" into main
diff --git a/src/com/android/settings/connecteddevice/AvailableMediaDeviceGroupController.java b/src/com/android/settings/connecteddevice/AvailableMediaDeviceGroupController.java
index c095fee..fc3493c 100644
--- a/src/com/android/settings/connecteddevice/AvailableMediaDeviceGroupController.java
+++ b/src/com/android/settings/connecteddevice/AvailableMediaDeviceGroupController.java
@@ -17,11 +17,16 @@
 
 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.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 @@
             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 @@
             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 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 @@
         }
 
         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 06dd42b..e5964d0 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 @@
 
 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 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.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.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 @@
         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 @@
         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 @@
         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 @@
         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 @@
     @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));
+    }
 }