From 3d54a876f5018c35bbcf855f168142135c046b72 Mon Sep 17 00:00:00 2001 From: chelseahao Date: Thu, 27 Feb 2025 11:57:02 +0800 Subject: [PATCH] Handle receive state in audio stream media service. Test: atest Bug: 398700619 Flag: com.android.settingslib.flags.audio_stream_media_service_by_receive_state Change-Id: If8934dd6da5420c0e345ae0f412cc9e512e43027 --- .../audiostreams/AudioStreamMediaService.java | 182 ++++++++++-- .../AudioStreamMediaServiceTest.java | 258 ++++++++++++++++++ 2 files changed, 413 insertions(+), 27 deletions(-) diff --git a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamMediaService.java b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamMediaService.java index ec8d7bcc206..15592bfca72 100644 --- a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamMediaService.java +++ b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamMediaService.java @@ -16,9 +16,13 @@ package com.android.settings.connecteddevice.audiosharing.audiostreams; +import static com.android.settingslib.bluetooth.LocalBluetoothLeBroadcast.EXTRA_PRIVATE_BROADCAST_RECEIVE_DATA; +import static com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant.LocalBluetoothLeBroadcastSourceState.DECRYPTION_FAILED; import static com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant.LocalBluetoothLeBroadcastSourceState.PAUSED; import static com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant.LocalBluetoothLeBroadcastSourceState.STREAMING; +import static java.util.Collections.emptyList; + import android.app.Notification; import android.app.NotificationChannel; import android.app.NotificationManager; @@ -56,11 +60,14 @@ import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager; import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant; import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant.LocalBluetoothLeBroadcastSourceState; import com.android.settingslib.bluetooth.LocalBluetoothManager; +import com.android.settingslib.bluetooth.PrivateBroadcastReceiveData; import com.android.settingslib.bluetooth.VolumeControlProfile; import com.android.settingslib.core.instrumentation.MetricsFeatureProvider; +import com.android.settingslib.flags.Flags; import com.android.settingslib.utils.ThreadUtils; import java.util.HashMap; +import java.util.List; import java.util.Map; public class AudioStreamMediaService extends Service { @@ -122,7 +129,9 @@ public class AudioStreamMediaService extends Service { private int mLatestPositiveVolume = 25; private boolean mHysteresisModeFixAvailable; private int mBroadcastId; - @Nullable private Map mStateByDevice; + @VisibleForTesting + @Nullable + Map mStateByDevice; @Nullable private LocalBluetoothManager mLocalBtManager; @Nullable private AudioStreamsHelper mAudioStreamsHelper; @Nullable private LocalBluetoothLeBroadcastAssistant mLeBroadcastAssistant; @@ -236,6 +245,19 @@ public class AudioStreamMediaService extends Service { stopSelf(); return START_NOT_STICKY; } + // TODO(b/398700619): Remove hasExtra check when feasible. + if (Flags.audioStreamMediaServiceByReceiveState() && intent.hasExtra( + EXTRA_PRIVATE_BROADCAST_RECEIVE_DATA)) { + PrivateBroadcastReceiveData data = intent.getParcelableExtra( + EXTRA_PRIVATE_BROADCAST_RECEIVE_DATA, PrivateBroadcastReceiveData.class); + if (data == null || !PrivateBroadcastReceiveData.Companion.isValid(data)) { + Log.w(TAG, "Data is null or invalid. Service will not start."); + stopSelf(); + return START_NOT_STICKY; + } + getHandler().post(() -> handleIntentData(data)); + return START_NOT_STICKY; + } getHandler().post(() -> { mBroadcastId = intent.getIntExtra(BROADCAST_ID, -1); if (mBroadcastId == -1) { @@ -258,6 +280,78 @@ public class AudioStreamMediaService extends Service { return START_NOT_STICKY; } + private void handleIntentData(PrivateBroadcastReceiveData data) { + int broadcastId = data.getBroadcastId(); + BluetoothDevice device = data.getSink(); + int sourceId = data.getSourceId(); + var state = data.getState(); + String programInfo = data.getProgramInfo(); + + // Service not running yet. + if (mBroadcastId == 0) { + Log.d(TAG, "handleIntentData(): sending " + data + " to handleInitialSetup()"); + handleInitialSetup(broadcastId, device, state, sourceId, programInfo); + return; + } + + // Service running with a different broadcast id, most likely staled. We have a new + // broadcast Id to handle. + if (mBroadcastId != broadcastId) { + Log.d(TAG, "handleIntentData(): sending " + data + " to handleNewBroadcastId()"); + handleNewBroadcastId(broadcastId, device, state, sourceId, programInfo); + return; + } + + // Service running with the same broadcast Id, we have new device joining or a state update. + if (mStateByDevice != null && (!mStateByDevice.containsKey(device) || mStateByDevice.get( + device) != state)) { + Log.d(TAG, "handleIntentData(): sending " + data + " to handleNewDeviceOrState()"); + handleNewDeviceOrState(device, state, sourceId, programInfo); + } + + Log.d(TAG, "handleIntentData(): nothing to update."); + } + + private void handleInitialSetup(int broadcastId, BluetoothDevice device, + LocalBluetoothLeBroadcastSourceState state, int sourceId, String programInfo) { + if (state == DECRYPTION_FAILED) { + Log.d(TAG, "handleInitialSetup() : decryption failed. Service will not start."); + stopSelf(); + return; + } + mBroadcastId = broadcastId; + mStateByDevice = new HashMap<>(); + mStateByDevice.put(device, state); + MediaSession.Token token = getOrCreateLocalMediaSession( + getBroadcastName(device, sourceId, programInfo)); + startForeground(NOTIFICATION_ID, buildNotification(token)); + } + + private void handleNewBroadcastId(int broadcastId, BluetoothDevice device, + LocalBluetoothLeBroadcastSourceState state, int sourceId, String programInfo) { + if (state == DECRYPTION_FAILED) { + Log.d(TAG, "handleNewBroadcastId() : decryption failed. Ignore."); + return; + } + mBroadcastId = broadcastId; + mStateByDevice = new HashMap<>(); + mStateByDevice.put(device, state); + updateMediaSessionAndNotify(device, sourceId, programInfo); + } + + private void handleNewDeviceOrState(BluetoothDevice device, + LocalBluetoothLeBroadcastSourceState state, int sourceId, String programInfo) { + if (mStateByDevice != null) { + mStateByDevice.put(device, state); + } + if (getDeviceInValidState().isEmpty()) { + Log.d(TAG, "handleNewDeviceOrState() : no device is in valid state. Stop service."); + stopSelf(); + return; + } + updateMediaSessionAndNotify(device, sourceId, programInfo); + } + private MediaSession.Token getOrCreateLocalMediaSession(String title) { if (mLocalSession != null) { return mLocalSession.getSessionToken(); @@ -288,7 +382,8 @@ public class AudioStreamMediaService extends Service { } private String getDeviceName() { - if (mStateByDevice == null || mStateByDevice.isEmpty() || mLocalBtManager == null) { + List validDevices = getDeviceInValidState(); + if (validDevices.isEmpty() || mLocalBtManager == null) { return DEFAULT_DEVICE_NAME; } @@ -297,8 +392,7 @@ public class AudioStreamMediaService extends Service { return DEFAULT_DEVICE_NAME; } - CachedBluetoothDevice device = manager.findDevice( - mStateByDevice.keySet().iterator().next()); + CachedBluetoothDevice device = manager.findDevice(validDevices.getFirst()); return device != null ? device.getName() : DEFAULT_DEVICE_NAME; } @@ -320,6 +414,47 @@ public class AudioStreamMediaService extends Service { return notificationBuilder.build(); } + private void updateMediaSessionAndNotify(BluetoothDevice device, int sourceId, + String programInfo) { + if (mNotificationManager == null || mLocalSession == null) { + Log.w(TAG, "mNotificationManager or mLocalSession is null, ignore update."); + return; + } + mLocalSession.setMetadata(new MediaMetadata.Builder().putString( + MediaMetadata.METADATA_KEY_TITLE, + getBroadcastName(device, sourceId, programInfo)).putLong( + MediaMetadata.METADATA_KEY_DURATION, STATIC_PLAYBACK_DURATION).build()); + mLocalSession.setPlaybackState(getPlaybackState()); + mNotificationManager.notify(NOTIFICATION_ID, + buildNotification(mLocalSession.getSessionToken())); + } + + private String getBroadcastName(BluetoothDevice sink, int sourceId, String programInfo) { + if (mLeBroadcastAssistant == null || sink == null) { + return programInfo; + } + var metadata = mLeBroadcastAssistant.getSourceMetadata(sink, sourceId); + if (metadata == null || metadata.getBroadcastId() != mBroadcastId + || metadata.getBroadcastName() == null || metadata.getBroadcastName().isEmpty()) { + Log.d(TAG, "getBroadcastName(): source metadata not found, using programInfo: " + + programInfo); + return programInfo; + } + return metadata.getBroadcastName(); + } + + private List getDeviceInValidState() { + if (mStateByDevice == null || mStateByDevice.isEmpty()) { + Log.w(TAG, "getDeviceInValidState() : mStateByDevice is null or empty!"); + return emptyList(); + } + if (Flags.audioStreamMediaServiceByReceiveState()) { + return mStateByDevice.entrySet().stream().filter( + entry -> entry.getValue() != DECRYPTION_FAILED).map(Map.Entry::getKey).toList(); + } + return mStateByDevice.keySet().stream().toList(); + } + @Nullable @Override public IBinder onBind(Intent intent) { @@ -342,6 +477,9 @@ public class AudioStreamMediaService extends Service { @Override public void onReceiveStateChanged( BluetoothDevice sink, int sourceId, BluetoothLeBroadcastReceiveState state) { + if (Flags.audioStreamMediaServiceByReceiveState()) { + return; + } super.onReceiveStateChanged(sink, sourceId, state); if (!mHysteresisModeFixAvailable || mStateByDevice == null || !mStateByDevice.containsKey(sink)) { @@ -383,23 +521,21 @@ public class AudioStreamMediaService extends Service { @Override public void onDeviceVolumeChanged( @NonNull BluetoothDevice device, @IntRange(from = -255, to = 255) int volume) { - if (mStateByDevice == null || mStateByDevice.isEmpty()) { - Log.w(TAG, "active device or device has source is null!"); + if (!getDeviceInValidState().contains(device)) { + Log.w(TAG, "onDeviceVolumeChanged() : device not in valid state list"); return; } Log.d( TAG, "onDeviceVolumeChanged() bluetoothDevice : " + device + " volume: " + volume); - if (mStateByDevice.containsKey(device)) { - if (volume == 0) { - mIsMuted = true; - } else { - mIsMuted = false; - mLatestPositiveVolume = volume; - } - if (mLocalSession != null) { - mLocalSession.setPlaybackState(getPlaybackState()); - } + if (volume == 0) { + mIsMuted = true; + } else { + mIsMuted = false; + mLatestPositiveVolume = volume; + } + if (mLocalSession != null) { + mLocalSession.setPlaybackState(getPlaybackState()); } } } @@ -426,7 +562,7 @@ public class AudioStreamMediaService extends Service { && mStateByDevice != null) { mStateByDevice.remove(cachedDevice.getDevice()); } - if (mStateByDevice == null || mStateByDevice.isEmpty()) { + if (getDeviceInValidState().isEmpty()) { Log.d(TAG, "onProfileConnectionStateChanged() : stopSelf"); stopSelf(); } @@ -484,11 +620,7 @@ public class AudioStreamMediaService extends Service { } private void handleOnPlay() { - if (mStateByDevice == null || mStateByDevice.isEmpty()) { - Log.w(TAG, "active device or device has source is null!"); - return; - } - mStateByDevice.keySet().forEach(device -> { + getDeviceInValidState().forEach(device -> { Log.d(TAG, "onPlay() setting volume for device : " + device + " volume: " + mLatestPositiveVolume); setDeviceVolume(device, mLatestPositiveVolume); @@ -496,11 +628,7 @@ public class AudioStreamMediaService extends Service { } private void handleOnPause() { - if (mStateByDevice == null || mStateByDevice.isEmpty()) { - Log.w(TAG, "active device or device has source is null!"); - return; - } - mStateByDevice.keySet().forEach(device -> { + getDeviceInValidState().forEach(device -> { Log.d(TAG, "onPause() setting volume for device : " + device + " volume: " + 0); setDeviceVolume(device, /* volume= */ 0); }); 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 c82c9787ccd..c00e2a5e379 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 @@ -19,6 +19,10 @@ package com.android.settings.connecteddevice.audiosharing.audiostreams; import static com.android.settings.connecteddevice.audiosharing.audiostreams.AudioStreamMediaService.BROADCAST_ID; import static com.android.settings.connecteddevice.audiosharing.audiostreams.AudioStreamMediaService.DEVICES; import static com.android.settings.connecteddevice.audiosharing.audiostreams.AudioStreamMediaService.LEAVE_BROADCAST_ACTION; +import static com.android.settingslib.bluetooth.LocalBluetoothLeBroadcast.EXTRA_PRIVATE_BROADCAST_RECEIVE_DATA; +import static com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant.LocalBluetoothLeBroadcastSourceState.DECRYPTION_FAILED; +import static com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant.LocalBluetoothLeBroadcastSourceState.PAUSED; +import static com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant.LocalBluetoothLeBroadcastSourceState.STREAMING; import static com.google.common.truth.Truth.assertThat; @@ -57,6 +61,7 @@ import android.os.Looper; import android.os.RemoteException; import android.platform.test.flag.junit.SetFlagsRule; import android.util.DisplayMetrics; +import android.view.KeyEvent; import com.android.settings.connecteddevice.audiosharing.audiostreams.testshadows.ShadowAudioStreamsHelper; import com.android.settings.testutils.FakeFeatureFactory; @@ -69,6 +74,7 @@ import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager; import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant; import com.android.settingslib.bluetooth.LocalBluetoothManager; import com.android.settingslib.bluetooth.LocalBluetoothProfileManager; +import com.android.settingslib.bluetooth.PrivateBroadcastReceiveData; import com.android.settingslib.bluetooth.VolumeControlProfile; import com.android.settingslib.flags.Flags; @@ -116,6 +122,8 @@ public class AudioStreamMediaServiceTest { @Mock private VolumeControlProfile mVolumeControlProfile; @Mock private CachedBluetoothDevice mCachedBluetoothDevice; @Mock private BluetoothDevice mDevice; + @Mock + private BluetoothDevice mDevice2; @Mock private ISession mISession; @Mock private ISessionController mISessionController; @Mock private PackageManager mPackageManager; @@ -240,6 +248,7 @@ public class AudioStreamMediaServiceTest { @Test public void onDestroy_flagOn_cleanup() { mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING); + mSetFlagsRule.disableFlags(Flags.FLAG_AUDIO_STREAM_MEDIA_SERVICE_BY_RECEIVE_STATE); var devices = new ArrayList(); devices.add(mDevice); @@ -256,8 +265,33 @@ public class AudioStreamMediaServiceTest { verify(mVolumeControlProfile).unregisterCallback(any()); } + @Test + public void byReceiveStateFlagOn_onDestroy_flagOn_cleanup() { + mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING); + mSetFlagsRule.enableFlags(Flags.FLAG_AUDIO_STREAM_MEDIA_SERVICE_BY_RECEIVE_STATE); + + Intent intent = setupReceiveDataIntent(1, mDevice, STREAMING); + mAudioStreamMediaService.onCreate(); + mAudioStreamMediaService.onStartCommand(intent, /* flags= */ 0, /* startId= */ 0); + mAudioStreamMediaService.onDestroy(); + + verify(mBluetoothEventManager).unregisterCallback(any()); + verify(mLeBroadcastAssistant).unregisterServiceCallBack(any()); + verify(mVolumeControlProfile).unregisterCallback(any()); + } + + @Test + public void byReceiveStateFlagOn_onStartCommand_invalidData_stopSelf() { + mSetFlagsRule.enableFlags(Flags.FLAG_AUDIO_STREAM_MEDIA_SERVICE_BY_RECEIVE_STATE); + Intent intent = setupReceiveDataIntent(-1, mDevice, STREAMING); + mAudioStreamMediaService.onStartCommand(intent, /* flags= */ 0, /* startId= */ 0); + + verify(mAudioStreamMediaService).stopSelf(); + } + @Test public void onStartCommand_noBroadcastId_stopSelf() { + mSetFlagsRule.disableFlags(Flags.FLAG_AUDIO_STREAM_MEDIA_SERVICE_BY_RECEIVE_STATE); mAudioStreamMediaService.onStartCommand(new Intent(), /* flags= */ 0, /* startId= */ 0); verify(mAudioStreamMediaService).stopSelf(); @@ -265,6 +299,7 @@ public class AudioStreamMediaServiceTest { @Test public void onStartCommand_noDevice_stopSelf() { + mSetFlagsRule.disableFlags(Flags.FLAG_AUDIO_STREAM_MEDIA_SERVICE_BY_RECEIVE_STATE); Intent intent = new Intent(); intent.putExtra(BROADCAST_ID, 1); @@ -273,8 +308,110 @@ public class AudioStreamMediaServiceTest { verify(mAudioStreamMediaService).stopSelf(); } + @Test + public void byReceiveStateFlagOn_onStartCommand_createSessionAndStartForeground() { + mSetFlagsRule.enableFlags(Flags.FLAG_AUDIO_STREAM_MEDIA_SERVICE_BY_RECEIVE_STATE); + Intent intent = setupReceiveDataIntent(1, mDevice, STREAMING); + mAudioStreamMediaService.onStartCommand(intent, /* flags= */ 0, /* startId= */ 0); + + ArgumentCaptor notificationCapture = ArgumentCaptor.forClass( + Notification.class); + verify(mAudioStreamMediaService).startForeground(anyInt(), notificationCapture.capture()); + var notification = notificationCapture.getValue(); + assertThat(notification.getSmallIcon()).isNotNull(); + assertThat(notification.isStyle(Notification.MediaStyle.class)).isTrue(); + + verify(mAudioStreamMediaService, never()).stopSelf(); + } + + @Test + public void byReceiveStateFlagOn_onStartCommand_decryptionFailed_stopSelf() { + mSetFlagsRule.enableFlags(Flags.FLAG_AUDIO_STREAM_MEDIA_SERVICE_BY_RECEIVE_STATE); + Intent intent = setupReceiveDataIntent(1, mDevice, DECRYPTION_FAILED); + mAudioStreamMediaService.onStartCommand(intent, /* flags= */ 0, /* startId= */ 0); + + verify(mAudioStreamMediaService).stopSelf(); + } + + @Test + public void byReceiveStateFlagOn_onStartCommand_addDevice() { + mSetFlagsRule.enableFlags(Flags.FLAG_AUDIO_STREAM_MEDIA_SERVICE_BY_RECEIVE_STATE); + mAudioStreamMediaService.onCreate(); + Intent intent1 = setupReceiveDataIntent(1, mDevice, STREAMING); + mAudioStreamMediaService.onStartCommand(intent1, /* flags= */ 0, /* startId= */ 0); + Intent intent2 = setupReceiveDataIntent(1, mDevice2, PAUSED); + mAudioStreamMediaService.onStartCommand(intent2, /* flags= */ 0, /* startId= */ 0); + + ArgumentCaptor notificationCapture = ArgumentCaptor.forClass( + Notification.class); + verify(mAudioStreamMediaService).startForeground(anyInt(), notificationCapture.capture()); + var notification = notificationCapture.getValue(); + assertThat(notification.getSmallIcon()).isNotNull(); + assertThat(notification.isStyle(Notification.MediaStyle.class)).isTrue(); + + assertThat(mAudioStreamMediaService.mStateByDevice).isNotNull(); + var deviceState = mAudioStreamMediaService.mStateByDevice.get(mDevice); + assertThat(deviceState).isNotNull(); + assertThat(deviceState).isEqualTo(STREAMING); + var device2State = mAudioStreamMediaService.mStateByDevice.get(mDevice2); + assertThat(device2State).isNotNull(); + assertThat(device2State).isEqualTo(PAUSED); + verify(mAudioStreamMediaService, never()).stopSelf(); + verify(mNotificationManager).notify(anyInt(), any()); + } + + @Test + public void byReceiveStateFlagOn_onStartCommand_updateState() { + mSetFlagsRule.enableFlags(Flags.FLAG_AUDIO_STREAM_MEDIA_SERVICE_BY_RECEIVE_STATE); + mAudioStreamMediaService.onCreate(); + Intent intent1 = setupReceiveDataIntent(1, mDevice, STREAMING); + mAudioStreamMediaService.onStartCommand(intent1, /* flags= */ 0, /* startId= */ 0); + Intent intent2 = setupReceiveDataIntent(1, mDevice, PAUSED); + mAudioStreamMediaService.onStartCommand(intent2, /* flags= */ 0, /* startId= */ 0); + + ArgumentCaptor notificationCapture = ArgumentCaptor.forClass( + Notification.class); + verify(mAudioStreamMediaService).startForeground(anyInt(), notificationCapture.capture()); + var notification = notificationCapture.getValue(); + assertThat(notification.getSmallIcon()).isNotNull(); + assertThat(notification.isStyle(Notification.MediaStyle.class)).isTrue(); + + assertThat(mAudioStreamMediaService.mStateByDevice).isNotNull(); + var deviceState = mAudioStreamMediaService.mStateByDevice.get(mDevice); + assertThat(deviceState).isNotNull(); + assertThat(deviceState).isEqualTo(PAUSED); + verify(mAudioStreamMediaService, never()).stopSelf(); + verify(mNotificationManager).notify(anyInt(), any()); + } + + @Test + public void byReceiveStateFlagOn_onStartCommand_newBroadcastId() { + mSetFlagsRule.enableFlags(Flags.FLAG_AUDIO_STREAM_MEDIA_SERVICE_BY_RECEIVE_STATE); + mAudioStreamMediaService.onCreate(); + Intent intent1 = setupReceiveDataIntent(1, mDevice, STREAMING); + mAudioStreamMediaService.onStartCommand(intent1, /* flags= */ 0, /* startId= */ 0); + Intent intent2 = setupReceiveDataIntent(2, mDevice2, PAUSED); + mAudioStreamMediaService.onStartCommand(intent2, /* flags= */ 0, /* startId= */ 0); + + ArgumentCaptor notificationCapture = ArgumentCaptor.forClass( + Notification.class); + verify(mAudioStreamMediaService).startForeground(anyInt(), notificationCapture.capture()); + var notification = notificationCapture.getValue(); + assertThat(notification.getSmallIcon()).isNotNull(); + assertThat(notification.isStyle(Notification.MediaStyle.class)).isTrue(); + + assertThat(mAudioStreamMediaService.mStateByDevice).isNotNull(); + var oldDeviceState = mAudioStreamMediaService.mStateByDevice.get(mDevice); + assertThat(oldDeviceState).isNull(); + var newDeviceState = mAudioStreamMediaService.mStateByDevice.get(mDevice2); + assertThat(newDeviceState).isEqualTo(PAUSED); + verify(mAudioStreamMediaService, never()).stopSelf(); + verify(mNotificationManager).notify(anyInt(), any()); + } + @Test public void onStartCommand_createSessionAndStartForeground() { + mSetFlagsRule.disableFlags(Flags.FLAG_AUDIO_STREAM_MEDIA_SERVICE_BY_RECEIVE_STATE); var devices = new ArrayList(); devices.add(mDevice); @@ -317,6 +454,7 @@ public class AudioStreamMediaServiceTest { @Test public void assistantCallback_onReceiveStateChanged_connected_doNothing() { + mSetFlagsRule.disableFlags(Flags.FLAG_AUDIO_STREAM_MEDIA_SERVICE_BY_RECEIVE_STATE); mSetFlagsRule.enableFlags(Flags.FLAG_AUDIO_SHARING_HYSTERESIS_MODE_FIX); mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING); @@ -338,6 +476,7 @@ public class AudioStreamMediaServiceTest { @Test public void assistantCallback_onReceiveStateChanged_hysteresis_updateNotification() { + mSetFlagsRule.disableFlags(Flags.FLAG_AUDIO_STREAM_MEDIA_SERVICE_BY_RECEIVE_STATE); mSetFlagsRule.enableFlags(Flags.FLAG_AUDIO_SHARING_HYSTERESIS_MODE_FIX); mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING); @@ -357,6 +496,7 @@ public class AudioStreamMediaServiceTest { @Test public void assistantCallback_onReceiveStateChanged_hysteresis_flagOff_doNothing() { + mSetFlagsRule.disableFlags(Flags.FLAG_AUDIO_STREAM_MEDIA_SERVICE_BY_RECEIVE_STATE); mSetFlagsRule.disableFlags(Flags.FLAG_AUDIO_SHARING_HYSTERESIS_MODE_FIX); mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING); @@ -386,6 +526,7 @@ public class AudioStreamMediaServiceTest { @Test public void bluetoothCallback_onDeviceDisconnect_stopSelf() { mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING); + mSetFlagsRule.disableFlags(Flags.FLAG_AUDIO_STREAM_MEDIA_SERVICE_BY_RECEIVE_STATE); mAudioStreamMediaService.onCreate(); assertThat(mAudioStreamMediaService.mBluetoothCallback).isNotNull(); mAudioStreamMediaService.onStartCommand(setupIntent(), /* flags= */ 0, /* startId= */ 0); @@ -398,9 +539,26 @@ public class AudioStreamMediaServiceTest { verify(mAudioStreamMediaService).stopSelf(); } + @Test + public void byReceiveStateFlagOn_bluetoothCallback_onDeviceDisconnect_stopSelf() { + mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING); + mSetFlagsRule.enableFlags(Flags.FLAG_AUDIO_STREAM_MEDIA_SERVICE_BY_RECEIVE_STATE); + mAudioStreamMediaService.onCreate(); + assertThat(mAudioStreamMediaService.mBluetoothCallback).isNotNull(); + Intent intent = setupReceiveDataIntent(1, mDevice, STREAMING); + mAudioStreamMediaService.onStartCommand(intent, /* flags= */ 0, /* startId= */ 0); + + mAudioStreamMediaService.mBluetoothCallback.onProfileConnectionStateChanged( + mCachedBluetoothDevice, BluetoothAdapter.STATE_DISCONNECTED, + BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT); + + verify(mAudioStreamMediaService).stopSelf(); + } + @Test public void mediaSessionCallback_onPause_setVolume() { mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING); + mSetFlagsRule.disableFlags(Flags.FLAG_AUDIO_STREAM_MEDIA_SERVICE_BY_RECEIVE_STATE); mAudioStreamMediaService.onCreate(); mAudioStreamMediaService.onStartCommand(setupIntent(), /* flags= */ 0, /* startId= */ 0); @@ -415,9 +573,26 @@ public class AudioStreamMediaServiceTest { eq(1)); } + @Test + public void byReceiveStateFlagOn_mediaSessionCallback_onPause_setVolume() { + mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING); + mSetFlagsRule.enableFlags(Flags.FLAG_AUDIO_STREAM_MEDIA_SERVICE_BY_RECEIVE_STATE); + + mAudioStreamMediaService.onCreate(); + Intent intent = setupReceiveDataIntent(1, mDevice, STREAMING); + mAudioStreamMediaService.onStartCommand(intent, /* flags= */ 0, /* startId= */ 0); + assertThat(mAudioStreamMediaService.mMediaSessionCallback).isNotNull(); + mAudioStreamMediaService.mMediaSessionCallback.onPause(); + + verify(mVolumeControlProfile).setDeviceVolume(any(), anyInt(), anyBoolean()); + verify(mFeatureFactory.metricsFeatureProvider).action(any(), + eq(SettingsEnums.ACTION_AUDIO_STREAM_NOTIFICATION_MUTE_BUTTON_CLICK), eq(1)); + } + @Test public void mediaSessionCallback_onPlay_setVolume() { mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING); + mSetFlagsRule.disableFlags(Flags.FLAG_AUDIO_STREAM_MEDIA_SERVICE_BY_RECEIVE_STATE); mAudioStreamMediaService.onCreate(); mAudioStreamMediaService.onStartCommand(setupIntent(), /* flags= */ 0, /* startId= */ 0); @@ -432,9 +607,66 @@ public class AudioStreamMediaServiceTest { eq(0)); } + @Test + public void byReceiveStateFlagOn_mediaSessionCallback_onPlay_setVolume() { + mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING); + mSetFlagsRule.enableFlags(Flags.FLAG_AUDIO_STREAM_MEDIA_SERVICE_BY_RECEIVE_STATE); + + mAudioStreamMediaService.onCreate(); + Intent intent = setupReceiveDataIntent(1, mDevice, STREAMING); + mAudioStreamMediaService.onStartCommand(intent, /* flags= */ 0, /* startId= */ 0); + assertThat(mAudioStreamMediaService.mMediaSessionCallback).isNotNull(); + mAudioStreamMediaService.mMediaSessionCallback.onPlay(); + + verify(mVolumeControlProfile).setDeviceVolume(any(), anyInt(), anyBoolean()); + verify(mFeatureFactory.metricsFeatureProvider).action(any(), + eq(SettingsEnums.ACTION_AUDIO_STREAM_NOTIFICATION_MUTE_BUTTON_CLICK), eq(0)); + } + + @Test + public void byReceiveStateFlagOn_mediaSessionCallback_onButtonEventPlay_setVolume() { + mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING); + mSetFlagsRule.enableFlags(Flags.FLAG_AUDIO_STREAM_MEDIA_SERVICE_BY_RECEIVE_STATE); + + mAudioStreamMediaService.onCreate(); + Intent intent = setupReceiveDataIntent(1, mDevice, STREAMING); + mAudioStreamMediaService.onStartCommand(intent, /* flags= */ 0, /* startId= */ 0); + assertThat(mAudioStreamMediaService.mMediaSessionCallback).isNotNull(); + + Intent buttonEvent = new Intent(); + buttonEvent.putExtra(Intent.EXTRA_KEY_EVENT, + new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_MEDIA_PLAY)); + mAudioStreamMediaService.mMediaSessionCallback.onMediaButtonEvent(buttonEvent); + + verify(mVolumeControlProfile).setDeviceVolume(any(), anyInt(), anyBoolean()); + verify(mFeatureFactory.metricsFeatureProvider).action(any(), + eq(SettingsEnums.ACTION_AUDIO_STREAM_NOTIFICATION_MUTE_BUTTON_CLICK), eq(0)); + } + + @Test + public void byReceiveStateFlagOn_mediaSessionCallback_onButtonEventPause_setVolume() { + mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING); + mSetFlagsRule.enableFlags(Flags.FLAG_AUDIO_STREAM_MEDIA_SERVICE_BY_RECEIVE_STATE); + + mAudioStreamMediaService.onCreate(); + Intent intent = setupReceiveDataIntent(1, mDevice, STREAMING); + mAudioStreamMediaService.onStartCommand(intent, /* flags= */ 0, /* startId= */ 0); + assertThat(mAudioStreamMediaService.mMediaSessionCallback).isNotNull(); + + Intent buttonEvent = new Intent(); + buttonEvent.putExtra(Intent.EXTRA_KEY_EVENT, + new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_MEDIA_PAUSE)); + mAudioStreamMediaService.mMediaSessionCallback.onMediaButtonEvent(buttonEvent); + + verify(mVolumeControlProfile).setDeviceVolume(any(), anyInt(), anyBoolean()); + verify(mFeatureFactory.metricsFeatureProvider).action(any(), + eq(SettingsEnums.ACTION_AUDIO_STREAM_NOTIFICATION_MUTE_BUTTON_CLICK), eq(1)); + } + @Test public void mediaSessionCallback_onCustomAction_leaveBroadcast() { mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING); + mSetFlagsRule.disableFlags(Flags.FLAG_AUDIO_STREAM_MEDIA_SERVICE_BY_RECEIVE_STATE); mAudioStreamMediaService.onCreate(); mAudioStreamMediaService.onStartCommand(setupIntent(), /* flags= */ 0, /* startId= */ 0); @@ -449,6 +681,23 @@ public class AudioStreamMediaServiceTest { eq(SettingsEnums.ACTION_AUDIO_STREAM_NOTIFICATION_LEAVE_BUTTON_CLICK)); } + @Test + public void byReceiveStateFlagOn_mediaSessionCallback_onCustomAction_leaveBroadcast() { + mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING); + mSetFlagsRule.enableFlags(Flags.FLAG_AUDIO_STREAM_MEDIA_SERVICE_BY_RECEIVE_STATE); + + mAudioStreamMediaService.onCreate(); + Intent intent = setupReceiveDataIntent(1, mDevice, STREAMING); + mAudioStreamMediaService.onStartCommand(intent, /* flags= */ 0, /* startId= */ 0); + assertThat(mAudioStreamMediaService.mMediaSessionCallback).isNotNull(); + mAudioStreamMediaService.mMediaSessionCallback.onCustomAction(LEAVE_BROADCAST_ACTION, + Bundle.EMPTY); + + verify(mAudioStreamsHelper).removeSource(anyInt()); + verify(mFeatureFactory.metricsFeatureProvider).action(any(), + eq(SettingsEnums.ACTION_AUDIO_STREAM_NOTIFICATION_LEAVE_BUTTON_CLICK)); + } + @Test public void onBind_returnNull() { IBinder binder = mAudioStreamMediaService.onBind(new Intent()); @@ -466,4 +715,13 @@ public class AudioStreamMediaServiceTest { intent.putParcelableArrayListExtra(DEVICES, devices); return intent; } + + private Intent setupReceiveDataIntent(int broadcastId, BluetoothDevice device, + LocalBluetoothLeBroadcastAssistant.LocalBluetoothLeBroadcastSourceState state) { + when(mCachedBluetoothDevice.getDevice()).thenReturn(mDevice); + Intent intent = new Intent(); + intent.putExtra(EXTRA_PRIVATE_BROADCAST_RECEIVE_DATA, + new PrivateBroadcastReceiveData(device, 1, broadcastId, "programInfo", state)); + return intent; + } }