Merge "Create only one media session per audio stream media service." into main
diff --git a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamMediaService.java b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamMediaService.java
index d5be2bb..d1af8d9 100644
--- a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamMediaService.java
+++ b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamMediaService.java
@@ -106,7 +106,7 @@
     // If the initial volume from `onDeviceVolumeChanged` is larger than zero (not muted), we will
     // override this value. Otherwise, we raise the volume to 25 when the play button is clicked.
     private final AtomicInteger mLatestPositiveVolume = new AtomicInteger(25);
-    private final AtomicBoolean mHasStopped = new AtomicBoolean(false);
+    private final Object mLocalSessionLock = new Object();
     private int mBroadcastId;
     @Nullable private List<BluetoothDevice> mDevices;
     @Nullable private LocalBluetoothManager mLocalBtManager;
@@ -125,7 +125,7 @@
         if (!BluetoothUtils.isAudioSharingEnabled()) {
             return;
         }
-
+        Log.d(TAG, "onCreate()");
         super.onCreate();
         mLocalBtManager = Utils.getLocalBtManager(this);
         if (mLocalBtManager == null) {
@@ -146,26 +146,35 @@
             return;
         }
 
-        if (mNotificationManager.getNotificationChannel(CHANNEL_ID) == null) {
-            NotificationChannel notificationChannel =
-                    new NotificationChannel(
-                            CHANNEL_ID,
-                            getString(com.android.settings.R.string.bluetooth),
-                            NotificationManager.IMPORTANCE_HIGH);
-            mNotificationManager.createNotificationChannel(notificationChannel);
-        }
+        mExecutor.execute(
+                () -> {
+                    if (mLocalBtManager == null
+                            || mLeBroadcastAssistant == null
+                            || mNotificationManager == null) {
+                        return;
+                    }
+                    if (mNotificationManager.getNotificationChannel(CHANNEL_ID) == null) {
+                        NotificationChannel notificationChannel =
+                                new NotificationChannel(
+                                        CHANNEL_ID,
+                                        getString(com.android.settings.R.string.bluetooth),
+                                        NotificationManager.IMPORTANCE_HIGH);
+                        mNotificationManager.createNotificationChannel(notificationChannel);
+                    }
 
-        mBluetoothCallback = new BtCallback();
-        mLocalBtManager.getEventManager().registerCallback(mBluetoothCallback);
+                    mBluetoothCallback = new BtCallback();
+                    mLocalBtManager.getEventManager().registerCallback(mBluetoothCallback);
 
-        mVolumeControl = mLocalBtManager.getProfileManager().getVolumeControlProfile();
-        if (mVolumeControl != null) {
-            mVolumeControlCallback = new VolumeControlCallback();
-            mVolumeControl.registerCallback(mExecutor, mVolumeControlCallback);
-        }
+                    mVolumeControl = mLocalBtManager.getProfileManager().getVolumeControlProfile();
+                    if (mVolumeControl != null) {
+                        mVolumeControlCallback = new VolumeControlCallback();
+                        mVolumeControl.registerCallback(mExecutor, mVolumeControlCallback);
+                    }
 
-        mBroadcastAssistantCallback = new AssistantCallback();
-        mLeBroadcastAssistant.registerServiceCallBack(mExecutor, mBroadcastAssistantCallback);
+                    mBroadcastAssistantCallback = new AssistantCallback();
+                    mLeBroadcastAssistant.registerServiceCallBack(
+                            mExecutor, mBroadcastAssistantCallback);
+                });
     }
 
     @Override
@@ -175,19 +184,29 @@
         if (!BluetoothUtils.isAudioSharingEnabled()) {
             return;
         }
-        if (mLocalBtManager != null) {
-            mLocalBtManager.getEventManager().unregisterCallback(mBluetoothCallback);
+        if (mDevices != null) {
+            mDevices.clear();
+            mDevices = null;
         }
-        if (mLeBroadcastAssistant != null && mBroadcastAssistantCallback != null) {
-            mLeBroadcastAssistant.unregisterServiceCallBack(mBroadcastAssistantCallback);
+        synchronized (mLocalSessionLock) {
+            if (mLocalSession != null) {
+                mLocalSession.release();
+                mLocalSession = null;
+            }
         }
-        if (mVolumeControl != null && mVolumeControlCallback != null) {
-            mVolumeControl.unregisterCallback(mVolumeControlCallback);
-        }
-        if (mLocalSession != null) {
-            mLocalSession.release();
-            mLocalSession = null;
-        }
+        mExecutor.execute(
+                () -> {
+                    if (mLocalBtManager != null) {
+                        mLocalBtManager.getEventManager().unregisterCallback(mBluetoothCallback);
+                    }
+                    if (mLeBroadcastAssistant != null && mBroadcastAssistantCallback != null) {
+                        mLeBroadcastAssistant.unregisterServiceCallBack(
+                                mBroadcastAssistantCallback);
+                    }
+                    if (mVolumeControl != null && mVolumeControlCallback != null) {
+                        mVolumeControl.unregisterCallback(mVolumeControlCallback);
+                    }
+                });
     }
 
     @Override
@@ -195,43 +214,45 @@
         Log.d(TAG, "onStartCommand()");
         if (intent == null) {
             Log.w(TAG, "Intent is null. Service will not start.");
-            mHasStopped.set(true);
             stopSelf();
             return START_NOT_STICKY;
         }
         mBroadcastId = intent.getIntExtra(BROADCAST_ID, -1);
         if (mBroadcastId == -1) {
             Log.w(TAG, "Invalid broadcast ID. Service will not start.");
-            mHasStopped.set(true);
             stopSelf();
             return START_NOT_STICKY;
         }
         var extra = intent.getParcelableArrayListExtra(DEVICES, BluetoothDevice.class);
         if (extra == null || extra.isEmpty()) {
             Log.w(TAG, "No device. Service will not start.");
-            mHasStopped.set(true);
             stopSelf();
             return START_NOT_STICKY;
         }
         mDevices = Collections.synchronizedList(extra);
-        createLocalMediaSession(intent.getStringExtra(BROADCAST_TITLE));
-        startForeground(NOTIFICATION_ID, buildNotification());
-        // Reset in case the service is previously stopped but not yet destroyed.
-        mHasStopped.set(false);
+        MediaSession.Token token =
+                getOrCreateLocalMediaSession(intent.getStringExtra(BROADCAST_TITLE));
+        startForeground(NOTIFICATION_ID, buildNotification(token));
         return START_NOT_STICKY;
     }
 
-    private void createLocalMediaSession(String title) {
-        mLocalSession = new MediaSession(this, TAG);
-        mLocalSession.setMetadata(
-                new MediaMetadata.Builder()
-                        .putString(MediaMetadata.METADATA_KEY_TITLE, title)
-                        .putLong(MediaMetadata.METADATA_KEY_DURATION, STATIC_PLAYBACK_DURATION)
-                        .build());
-        mLocalSession.setActive(true);
-        mLocalSession.setPlaybackState(getPlaybackState());
-        mMediaSessionCallback = new MediaSessionCallback();
-        mLocalSession.setCallback(mMediaSessionCallback);
+    private MediaSession.Token getOrCreateLocalMediaSession(String title) {
+        synchronized (mLocalSessionLock) {
+            if (mLocalSession != null) {
+                return mLocalSession.getSessionToken();
+            }
+            mLocalSession = new MediaSession(this, TAG);
+            mLocalSession.setMetadata(
+                    new MediaMetadata.Builder()
+                            .putString(MediaMetadata.METADATA_KEY_TITLE, title)
+                            .putLong(MediaMetadata.METADATA_KEY_DURATION, STATIC_PLAYBACK_DURATION)
+                            .build());
+            mLocalSession.setActive(true);
+            mLocalSession.setPlaybackState(getPlaybackState());
+            mMediaSessionCallback = new MediaSessionCallback();
+            mLocalSession.setCallback(mMediaSessionCallback);
+            return mLocalSession.getSessionToken();
+        }
     }
 
     private PlaybackState getPlaybackState() {
@@ -252,12 +273,9 @@
         return device != null ? device.getName() : DEFAULT_DEVICE_NAME;
     }
 
-    private Notification buildNotification() {
+    private Notification buildNotification(MediaSession.Token token) {
         String deviceName = getDeviceName();
-        Notification.MediaStyle mediaStyle =
-                new Notification.MediaStyle()
-                        .setMediaSession(
-                                mLocalSession != null ? mLocalSession.getSessionToken() : null);
+        Notification.MediaStyle mediaStyle = new Notification.MediaStyle().setMediaSession(token);
         if (deviceName != null && !deviceName.isEmpty()) {
             mediaStyle.setRemotePlaybackInfo(
                     deviceName, com.android.settingslib.R.drawable.ic_bt_le_audio, null);
@@ -291,20 +309,15 @@
         }
 
         private void handleRemoveSource() {
-            var unused =
-                    ThreadUtils.postOnBackgroundThread(
-                            () -> {
-                                List<BluetoothLeBroadcastReceiveState> connected =
-                                        mAudioStreamsHelper == null
-                                                ? emptyList()
-                                                : mAudioStreamsHelper.getAllConnectedSources();
-                                if (connected.stream()
-                                        .map(BluetoothLeBroadcastReceiveState::getBroadcastId)
-                                        .noneMatch(id -> id == mBroadcastId)) {
-                                    mHasStopped.set(true);
-                                    stopSelf();
-                                }
-                            });
+            List<BluetoothLeBroadcastReceiveState> connected =
+                    mAudioStreamsHelper == null
+                            ? emptyList()
+                            : mAudioStreamsHelper.getAllConnectedSources();
+            if (connected.stream()
+                    .map(BluetoothLeBroadcastReceiveState::getBroadcastId)
+                    .noneMatch(id -> id == mBroadcastId)) {
+                stopSelf();
+            }
         }
     }
 
@@ -326,7 +339,11 @@
                     mIsMuted.set(false);
                     mLatestPositiveVolume.set(volume);
                 }
-                updateNotification(getPlaybackState());
+                synchronized (mLocalSessionLock) {
+                    if (mLocalSession != null) {
+                        mLocalSession.setPlaybackState(getPlaybackState());
+                    }
+                }
             }
         }
     }
@@ -336,7 +353,6 @@
         public void onBluetoothStateChanged(int bluetoothState) {
             if (BluetoothAdapter.STATE_OFF == bluetoothState) {
                 Log.d(TAG, "onBluetoothStateChanged() : stopSelf");
-                mHasStopped.set(true);
                 stopSelf();
             }
         }
@@ -362,7 +378,6 @@
             }
             if (mDevices == null || mDevices.isEmpty()) {
                 Log.d(TAG, "onProfileConnectionStateChanged() : stopSelf");
-                mHasStopped.set(true);
                 stopSelf();
             }
         }
@@ -371,7 +386,11 @@
     private class MediaSessionCallback extends MediaSession.Callback {
         public void onSeekTo(long pos) {
             Log.d(TAG, "onSeekTo: " + pos);
-            updateNotification(getPlaybackState());
+            synchronized (mLocalSessionLock) {
+                if (mLocalSession != null) {
+                    mLocalSession.setPlaybackState(getPlaybackState());
+                }
+            }
         }
 
         @Override
@@ -425,18 +444,4 @@
                             });
         }
     }
-
-    private void updateNotification(PlaybackState playbackState) {
-        var unused =
-                ThreadUtils.postOnBackgroundThread(
-                        () -> {
-                            if (mLocalSession != null) {
-                                mLocalSession.setPlaybackState(playbackState);
-                                if (mNotificationManager != null && !mHasStopped.get()) {
-                                    mNotificationManager.notify(
-                                            NOTIFICATION_ID, buildNotification());
-                                }
-                            }
-                        });
-    }
 }
diff --git a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsHelper.java b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsHelper.java
index c0d9162..7c1281f 100644
--- a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsHelper.java
+++ b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsHelper.java
@@ -139,7 +139,6 @@
     }
 
     /** Retrieves a list of all LE broadcast receive states from active sinks. */
-    @VisibleForTesting
     public List<BluetoothLeBroadcastReceiveState> getAllConnectedSources() {
         if (mLeBroadcastAssistant == null) {
             Log.w(TAG, "getAllSources(): LeBroadcastAssistant is null!");
@@ -165,7 +164,6 @@
     }
 
     /** Retrieves LocalBluetoothLeBroadcastAssistant. */
-    @VisibleForTesting
     @Nullable
     public LocalBluetoothLeBroadcastAssistant getLeBroadcastAssistant() {
         return mLeBroadcastAssistant;
diff --git a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamMediaServiceTest.java b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamMediaServiceTest.java
index abdd743..bfb474b 100644
--- a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamMediaServiceTest.java
+++ b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamMediaServiceTest.java
@@ -80,6 +80,7 @@
 import org.mockito.junit.MockitoJUnit;
 import org.mockito.junit.MockitoRule;
 import org.robolectric.RobolectricTestRunner;
+import org.robolectric.android.util.concurrent.InlineExecutorService;
 import org.robolectric.annotation.Config;
 import org.robolectric.shadow.api.Shadow;
 import org.robolectric.util.ReflectionHelpers;
@@ -143,6 +144,8 @@
 
         mAudioStreamMediaService = spy(new AudioStreamMediaService());
         ReflectionHelpers.setField(mAudioStreamMediaService, "mBase", mContext);
+        ReflectionHelpers.setField(
+                mAudioStreamMediaService, "mExecutor", new InlineExecutorService());
         when(mAudioStreamMediaService.getSystemService(anyString()))
                 .thenReturn(mMediaSessionManager);
         when(mMediaSessionManager.createSession(any(), anyString(), any())).thenReturn(mISession);
@@ -353,18 +356,6 @@
     }
 
     @Test
-    public void mediaSessionCallback_onSeekTo_updateNotification() {
-        mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
-
-        mAudioStreamMediaService.onCreate();
-        mAudioStreamMediaService.onStartCommand(setupIntent(), /* flags= */ 0, /* startId= */ 0);
-        assertThat(mAudioStreamMediaService.mMediaSessionCallback).isNotNull();
-        mAudioStreamMediaService.mMediaSessionCallback.onSeekTo(100);
-
-        verify(mNotificationManager).notify(anyInt(), any());
-    }
-
-    @Test
     public void mediaSessionCallback_onPause_setVolume() {
         mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
 
@@ -416,19 +407,6 @@
     }
 
     @Test
-    public void volumeControlCallback_onDeviceVolumeChanged_updateNotification() {
-        mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
-
-        mAudioStreamMediaService.onCreate();
-        assertThat(mAudioStreamMediaService.mVolumeControlCallback).isNotNull();
-        mAudioStreamMediaService.onStartCommand(setupIntent(), /* flags= */ 0, /* startId= */ 0);
-        mAudioStreamMediaService.mVolumeControlCallback.onDeviceVolumeChanged(
-                mDevice, /* volume= */ 0);
-
-        verify(mNotificationManager).notify(anyInt(), any());
-    }
-
-    @Test
     public void onBind_returnNull() {
         IBinder binder = mAudioStreamMediaService.onBind(new Intent());