Merge "Create only one media session per audio stream media service." into main

This commit is contained in:
Treehugger Robot
2024-09-29 12:03:17 +00:00
committed by Android (Google) Code Review
3 changed files with 93 additions and 112 deletions

View File

@@ -106,7 +106,7 @@ public class AudioStreamMediaService extends Service {
// If the initial volume from `onDeviceVolumeChanged` is larger than zero (not muted), we will // 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. // 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 AtomicInteger mLatestPositiveVolume = new AtomicInteger(25);
private final AtomicBoolean mHasStopped = new AtomicBoolean(false); private final Object mLocalSessionLock = new Object();
private int mBroadcastId; private int mBroadcastId;
@Nullable private List<BluetoothDevice> mDevices; @Nullable private List<BluetoothDevice> mDevices;
@Nullable private LocalBluetoothManager mLocalBtManager; @Nullable private LocalBluetoothManager mLocalBtManager;
@@ -125,7 +125,7 @@ public class AudioStreamMediaService extends Service {
if (!BluetoothUtils.isAudioSharingEnabled()) { if (!BluetoothUtils.isAudioSharingEnabled()) {
return; return;
} }
Log.d(TAG, "onCreate()");
super.onCreate(); super.onCreate();
mLocalBtManager = Utils.getLocalBtManager(this); mLocalBtManager = Utils.getLocalBtManager(this);
if (mLocalBtManager == null) { if (mLocalBtManager == null) {
@@ -146,26 +146,35 @@ public class AudioStreamMediaService extends Service {
return; return;
} }
if (mNotificationManager.getNotificationChannel(CHANNEL_ID) == null) { mExecutor.execute(
NotificationChannel notificationChannel = () -> {
new NotificationChannel( if (mLocalBtManager == null
CHANNEL_ID, || mLeBroadcastAssistant == null
getString(com.android.settings.R.string.bluetooth), || mNotificationManager == null) {
NotificationManager.IMPORTANCE_HIGH); return;
mNotificationManager.createNotificationChannel(notificationChannel); }
} 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(); mBluetoothCallback = new BtCallback();
mLocalBtManager.getEventManager().registerCallback(mBluetoothCallback); mLocalBtManager.getEventManager().registerCallback(mBluetoothCallback);
mVolumeControl = mLocalBtManager.getProfileManager().getVolumeControlProfile(); mVolumeControl = mLocalBtManager.getProfileManager().getVolumeControlProfile();
if (mVolumeControl != null) { if (mVolumeControl != null) {
mVolumeControlCallback = new VolumeControlCallback(); mVolumeControlCallback = new VolumeControlCallback();
mVolumeControl.registerCallback(mExecutor, mVolumeControlCallback); mVolumeControl.registerCallback(mExecutor, mVolumeControlCallback);
} }
mBroadcastAssistantCallback = new AssistantCallback(); mBroadcastAssistantCallback = new AssistantCallback();
mLeBroadcastAssistant.registerServiceCallBack(mExecutor, mBroadcastAssistantCallback); mLeBroadcastAssistant.registerServiceCallBack(
mExecutor, mBroadcastAssistantCallback);
});
} }
@Override @Override
@@ -175,19 +184,29 @@ public class AudioStreamMediaService extends Service {
if (!BluetoothUtils.isAudioSharingEnabled()) { if (!BluetoothUtils.isAudioSharingEnabled()) {
return; return;
} }
if (mLocalBtManager != null) { if (mDevices != null) {
mLocalBtManager.getEventManager().unregisterCallback(mBluetoothCallback); mDevices.clear();
mDevices = null;
} }
if (mLeBroadcastAssistant != null && mBroadcastAssistantCallback != null) { synchronized (mLocalSessionLock) {
mLeBroadcastAssistant.unregisterServiceCallBack(mBroadcastAssistantCallback); if (mLocalSession != null) {
} mLocalSession.release();
if (mVolumeControl != null && mVolumeControlCallback != null) { mLocalSession = 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 @Override
@@ -195,43 +214,45 @@ public class AudioStreamMediaService extends Service {
Log.d(TAG, "onStartCommand()"); Log.d(TAG, "onStartCommand()");
if (intent == null) { if (intent == null) {
Log.w(TAG, "Intent is null. Service will not start."); Log.w(TAG, "Intent is null. Service will not start.");
mHasStopped.set(true);
stopSelf(); stopSelf();
return START_NOT_STICKY; return START_NOT_STICKY;
} }
mBroadcastId = intent.getIntExtra(BROADCAST_ID, -1); mBroadcastId = intent.getIntExtra(BROADCAST_ID, -1);
if (mBroadcastId == -1) { if (mBroadcastId == -1) {
Log.w(TAG, "Invalid broadcast ID. Service will not start."); Log.w(TAG, "Invalid broadcast ID. Service will not start.");
mHasStopped.set(true);
stopSelf(); stopSelf();
return START_NOT_STICKY; return START_NOT_STICKY;
} }
var extra = intent.getParcelableArrayListExtra(DEVICES, BluetoothDevice.class); var extra = intent.getParcelableArrayListExtra(DEVICES, BluetoothDevice.class);
if (extra == null || extra.isEmpty()) { if (extra == null || extra.isEmpty()) {
Log.w(TAG, "No device. Service will not start."); Log.w(TAG, "No device. Service will not start.");
mHasStopped.set(true);
stopSelf(); stopSelf();
return START_NOT_STICKY; return START_NOT_STICKY;
} }
mDevices = Collections.synchronizedList(extra); mDevices = Collections.synchronizedList(extra);
createLocalMediaSession(intent.getStringExtra(BROADCAST_TITLE)); MediaSession.Token token =
startForeground(NOTIFICATION_ID, buildNotification()); getOrCreateLocalMediaSession(intent.getStringExtra(BROADCAST_TITLE));
// Reset in case the service is previously stopped but not yet destroyed. startForeground(NOTIFICATION_ID, buildNotification(token));
mHasStopped.set(false);
return START_NOT_STICKY; return START_NOT_STICKY;
} }
private void createLocalMediaSession(String title) { private MediaSession.Token getOrCreateLocalMediaSession(String title) {
mLocalSession = new MediaSession(this, TAG); synchronized (mLocalSessionLock) {
mLocalSession.setMetadata( if (mLocalSession != null) {
new MediaMetadata.Builder() return mLocalSession.getSessionToken();
.putString(MediaMetadata.METADATA_KEY_TITLE, title) }
.putLong(MediaMetadata.METADATA_KEY_DURATION, STATIC_PLAYBACK_DURATION) mLocalSession = new MediaSession(this, TAG);
.build()); mLocalSession.setMetadata(
mLocalSession.setActive(true); new MediaMetadata.Builder()
mLocalSession.setPlaybackState(getPlaybackState()); .putString(MediaMetadata.METADATA_KEY_TITLE, title)
mMediaSessionCallback = new MediaSessionCallback(); .putLong(MediaMetadata.METADATA_KEY_DURATION, STATIC_PLAYBACK_DURATION)
mLocalSession.setCallback(mMediaSessionCallback); .build());
mLocalSession.setActive(true);
mLocalSession.setPlaybackState(getPlaybackState());
mMediaSessionCallback = new MediaSessionCallback();
mLocalSession.setCallback(mMediaSessionCallback);
return mLocalSession.getSessionToken();
}
} }
private PlaybackState getPlaybackState() { private PlaybackState getPlaybackState() {
@@ -252,12 +273,9 @@ public class AudioStreamMediaService extends Service {
return device != null ? device.getName() : DEFAULT_DEVICE_NAME; return device != null ? device.getName() : DEFAULT_DEVICE_NAME;
} }
private Notification buildNotification() { private Notification buildNotification(MediaSession.Token token) {
String deviceName = getDeviceName(); String deviceName = getDeviceName();
Notification.MediaStyle mediaStyle = Notification.MediaStyle mediaStyle = new Notification.MediaStyle().setMediaSession(token);
new Notification.MediaStyle()
.setMediaSession(
mLocalSession != null ? mLocalSession.getSessionToken() : null);
if (deviceName != null && !deviceName.isEmpty()) { if (deviceName != null && !deviceName.isEmpty()) {
mediaStyle.setRemotePlaybackInfo( mediaStyle.setRemotePlaybackInfo(
deviceName, com.android.settingslib.R.drawable.ic_bt_le_audio, null); deviceName, com.android.settingslib.R.drawable.ic_bt_le_audio, null);
@@ -291,20 +309,15 @@ public class AudioStreamMediaService extends Service {
} }
private void handleRemoveSource() { private void handleRemoveSource() {
var unused = List<BluetoothLeBroadcastReceiveState> connected =
ThreadUtils.postOnBackgroundThread( mAudioStreamsHelper == null
() -> { ? emptyList()
List<BluetoothLeBroadcastReceiveState> connected = : mAudioStreamsHelper.getAllConnectedSources();
mAudioStreamsHelper == null if (connected.stream()
? emptyList() .map(BluetoothLeBroadcastReceiveState::getBroadcastId)
: mAudioStreamsHelper.getAllConnectedSources(); .noneMatch(id -> id == mBroadcastId)) {
if (connected.stream() stopSelf();
.map(BluetoothLeBroadcastReceiveState::getBroadcastId) }
.noneMatch(id -> id == mBroadcastId)) {
mHasStopped.set(true);
stopSelf();
}
});
} }
} }
@@ -326,7 +339,11 @@ public class AudioStreamMediaService extends Service {
mIsMuted.set(false); mIsMuted.set(false);
mLatestPositiveVolume.set(volume); mLatestPositiveVolume.set(volume);
} }
updateNotification(getPlaybackState()); synchronized (mLocalSessionLock) {
if (mLocalSession != null) {
mLocalSession.setPlaybackState(getPlaybackState());
}
}
} }
} }
} }
@@ -336,7 +353,6 @@ public class AudioStreamMediaService extends Service {
public void onBluetoothStateChanged(int bluetoothState) { public void onBluetoothStateChanged(int bluetoothState) {
if (BluetoothAdapter.STATE_OFF == bluetoothState) { if (BluetoothAdapter.STATE_OFF == bluetoothState) {
Log.d(TAG, "onBluetoothStateChanged() : stopSelf"); Log.d(TAG, "onBluetoothStateChanged() : stopSelf");
mHasStopped.set(true);
stopSelf(); stopSelf();
} }
} }
@@ -362,7 +378,6 @@ public class AudioStreamMediaService extends Service {
} }
if (mDevices == null || mDevices.isEmpty()) { if (mDevices == null || mDevices.isEmpty()) {
Log.d(TAG, "onProfileConnectionStateChanged() : stopSelf"); Log.d(TAG, "onProfileConnectionStateChanged() : stopSelf");
mHasStopped.set(true);
stopSelf(); stopSelf();
} }
} }
@@ -371,7 +386,11 @@ public class AudioStreamMediaService extends Service {
private class MediaSessionCallback extends MediaSession.Callback { private class MediaSessionCallback extends MediaSession.Callback {
public void onSeekTo(long pos) { public void onSeekTo(long pos) {
Log.d(TAG, "onSeekTo: " + pos); Log.d(TAG, "onSeekTo: " + pos);
updateNotification(getPlaybackState()); synchronized (mLocalSessionLock) {
if (mLocalSession != null) {
mLocalSession.setPlaybackState(getPlaybackState());
}
}
} }
@Override @Override
@@ -425,18 +444,4 @@ public class AudioStreamMediaService extends Service {
}); });
} }
} }
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());
}
}
});
}
} }

View File

@@ -139,7 +139,6 @@ public class AudioStreamsHelper {
} }
/** Retrieves a list of all LE broadcast receive states from active sinks. */ /** Retrieves a list of all LE broadcast receive states from active sinks. */
@VisibleForTesting
public List<BluetoothLeBroadcastReceiveState> getAllConnectedSources() { public List<BluetoothLeBroadcastReceiveState> getAllConnectedSources() {
if (mLeBroadcastAssistant == null) { if (mLeBroadcastAssistant == null) {
Log.w(TAG, "getAllSources(): LeBroadcastAssistant is null!"); Log.w(TAG, "getAllSources(): LeBroadcastAssistant is null!");
@@ -165,7 +164,6 @@ public class AudioStreamsHelper {
} }
/** Retrieves LocalBluetoothLeBroadcastAssistant. */ /** Retrieves LocalBluetoothLeBroadcastAssistant. */
@VisibleForTesting
@Nullable @Nullable
public LocalBluetoothLeBroadcastAssistant getLeBroadcastAssistant() { public LocalBluetoothLeBroadcastAssistant getLeBroadcastAssistant() {
return mLeBroadcastAssistant; return mLeBroadcastAssistant;

View File

@@ -80,6 +80,7 @@ import org.mockito.Mock;
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.android.util.concurrent.InlineExecutorService;
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.util.ReflectionHelpers; import org.robolectric.util.ReflectionHelpers;
@@ -143,6 +144,8 @@ public class AudioStreamMediaServiceTest {
mAudioStreamMediaService = spy(new AudioStreamMediaService()); mAudioStreamMediaService = spy(new AudioStreamMediaService());
ReflectionHelpers.setField(mAudioStreamMediaService, "mBase", mContext); ReflectionHelpers.setField(mAudioStreamMediaService, "mBase", mContext);
ReflectionHelpers.setField(
mAudioStreamMediaService, "mExecutor", new InlineExecutorService());
when(mAudioStreamMediaService.getSystemService(anyString())) when(mAudioStreamMediaService.getSystemService(anyString()))
.thenReturn(mMediaSessionManager); .thenReturn(mMediaSessionManager);
when(mMediaSessionManager.createSession(any(), anyString(), any())).thenReturn(mISession); when(mMediaSessionManager.createSession(any(), anyString(), any())).thenReturn(mISession);
@@ -352,18 +355,6 @@ public class AudioStreamMediaServiceTest {
verify(mAudioStreamMediaService).stopSelf(); verify(mAudioStreamMediaService).stopSelf();
} }
@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 @Test
public void mediaSessionCallback_onPause_setVolume() { public void mediaSessionCallback_onPause_setVolume() {
mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING); mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
@@ -415,19 +406,6 @@ public class AudioStreamMediaServiceTest {
eq(SettingsEnums.ACTION_AUDIO_STREAM_NOTIFICATION_LEAVE_BUTTON_CLICK)); eq(SettingsEnums.ACTION_AUDIO_STREAM_NOTIFICATION_LEAVE_BUTTON_CLICK));
} }
@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 @Test
public void onBind_returnNull() { public void onBind_returnNull() {
IBinder binder = mAudioStreamMediaService.onBind(new Intent()); IBinder binder = mAudioStreamMediaService.onBind(new Intent());