Merge "Create only one media session per audio stream media service." into main
This commit is contained in:
committed by
Android (Google) Code Review
commit
77a8da1f8d
@@ -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());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@@ -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;
|
||||||
|
@@ -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());
|
||||||
|
Reference in New Issue
Block a user