Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit d0abb3f7 authored by chelseahao's avatar chelseahao
Browse files

Show "Stream paused" when hysteresis mode in UMO.

Test: atest
Bug: 308368124
Flag: com.android.settingslib.flags.audio_sharing_hysteresis_mode_fix
Change-Id: I72897d9ed00ca0ed584513381d20f417db8e522c
parent cc0c66a7
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -13981,7 +13981,7 @@
    <!-- The preference summary when add source succeed [CHAR LIMIT=NONE] -->
    <string name="audio_streams_listening_now">Listening now</string>
    <!-- The preference summary when source is present on sinks [CHAR LIMIT=NONE] -->
    <string name="audio_streams_present_now">Paused by host</string>
    <string name="audio_streams_present_now">Stream paused</string>
    <!-- Le audio streams service notification leave broadcast text [CHAR LIMIT=NONE] -->
    <string name="audio_streams_media_service_notification_leave_broadcast_text">Stop listening</string>
    <!-- Le audio streams no le device dialog title [CHAR LIMIT=NONE] -->
+55 −2
Original line number Diff line number Diff line
@@ -16,6 +16,9 @@

package com.android.settings.connecteddevice.audiosharing.audiostreams;

import static com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant.LocalBluetoothLeBroadcastSourceState.PAUSED;
import static com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant.LocalBluetoothLeBroadcastSourceState.STREAMING;

import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
@@ -23,6 +26,7 @@ import android.app.Service;
import android.app.settings.SettingsEnums;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothLeBroadcastReceiveState;
import android.bluetooth.BluetoothProfile;
import android.bluetooth.BluetoothVolumeControl;
import android.content.Intent;
@@ -64,7 +68,8 @@ public class AudioStreamMediaService extends Service {
    static final String DEVICES = "audio_stream_media_service_devices";
    private static final String TAG = "AudioStreamMediaService";
    private static final int NOTIFICATION_ID = 1;
    private static final int BROADCAST_CONTENT_TEXT = R.string.audio_streams_listening_now;
    private static final int BROADCAST_LISTENING_NOW_TEXT = R.string.audio_streams_listening_now;
    private static final int BROADCAST_STREAM_PAUSED_TEXT = R.string.audio_streams_present_now;
    @VisibleForTesting static final String LEAVE_BROADCAST_ACTION = "leave_broadcast_action";
    private static final String LEAVE_BROADCAST_TEXT = "Leave Broadcast";
    private static final String CHANNEL_ID = "bluetooth_notification_channel";
@@ -94,11 +99,22 @@ public class AudioStreamMediaService extends Service {
                            LEAVE_BROADCAST_ACTION,
                            LEAVE_BROADCAST_TEXT,
                            com.android.settings.R.drawable.ic_clear);
    private final PlaybackState.Builder mPlayStateHysteresisBuilder =
            new PlaybackState.Builder()
                    .setState(
                            PlaybackState.STATE_STOPPED,
                            STATIC_PLAYBACK_POSITION,
                            ZERO_PLAYBACK_SPEED)
                    .addCustomAction(
                            LEAVE_BROADCAST_ACTION,
                            LEAVE_BROADCAST_TEXT,
                            com.android.settings.R.drawable.ic_clear);

    private final MetricsFeatureProvider mMetricsFeatureProvider =
            FeatureFactory.getFeatureFactory().getMetricsFeatureProvider();
    private final ExecutorService mExecutor = Executors.newSingleThreadExecutor();
    private final AtomicBoolean mIsMuted = new AtomicBoolean(false);
    private final AtomicBoolean mIsHysteresis = new AtomicBoolean(false);
    // Set 25 as default as the volume range from `VolumeControlProfile` is from 0 to 255.
    // 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.
@@ -255,6 +271,9 @@ public class AudioStreamMediaService extends Service {
    }

    private PlaybackState getPlaybackState() {
        if (mIsHysteresis.get()) {
            return mPlayStateHysteresisBuilder.build();
        }
        return mIsMuted.get() ? mPlayStatePausingBuilder.build() : mPlayStatePlayingBuilder.build();
    }

@@ -283,7 +302,9 @@ public class AudioStreamMediaService extends Service {
                new Notification.Builder(this, CHANNEL_ID)
                        .setSmallIcon(com.android.settingslib.R.drawable.ic_bt_le_audio_sharing)
                        .setStyle(mediaStyle)
                        .setContentText(getString(BROADCAST_CONTENT_TEXT))
                        .setContentText(getString(
                                mIsHysteresis.get() ? BROADCAST_STREAM_PAUSED_TEXT :
                                        BROADCAST_LISTENING_NOW_TEXT))
                        .setSilent(true);
        return notificationBuilder.build();
    }
@@ -307,6 +328,38 @@ public class AudioStreamMediaService extends Service {
            handleRemoveSource();
        }

        @Override
        public void onReceiveStateChanged(
                BluetoothDevice sink, int sourceId, BluetoothLeBroadcastReceiveState state) {
            super.onReceiveStateChanged(sink, sourceId, state);
            if (!mHysteresisModeFixAvailable || mDevices == null || !mDevices.contains(sink)) {
                return;
            }
            var sourceState = LocalBluetoothLeBroadcastAssistant.getLocalSourceState(state);
            boolean streaming = sourceState == STREAMING;
            boolean paused = sourceState == PAUSED;
            // Exit early if the state is neither streaming nor paused
            if (!streaming && !paused) {
                return;
            }
            // Atomically update mIsHysteresis if its current value is not the current paused state
            if (mIsHysteresis.compareAndSet(!paused, paused)) {
                synchronized (mLocalSessionLock) {
                    if (mLocalSession == null) {
                        return;
                    }
                    mLocalSession.setPlaybackState(getPlaybackState());
                    if (mNotificationManager != null) {
                        mNotificationManager.notify(
                                NOTIFICATION_ID,
                                buildNotification(mLocalSession.getSessionToken())
                        );
                    }
                    Log.d(TAG, "updating hysteresis mode to : " + paused);
                }
            }
        }

        private void handleRemoveSource() {
            if (mAudioStreamsHelper != null
                    && !mAudioStreamsHelper.getConnectedBroadcastIdAndState(
+61 −0
Original line number Diff line number Diff line
@@ -40,6 +40,7 @@ import android.app.NotificationManager;
import android.app.settings.SettingsEnums;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothLeBroadcastReceiveState;
import android.bluetooth.BluetoothProfile;
import android.bluetooth.BluetoothStatusCodes;
import android.content.Context;
@@ -86,6 +87,7 @@ import org.robolectric.shadow.api.Shadow;
import org.robolectric.util.ReflectionHelpers;

import java.util.ArrayList;
import java.util.List;
import java.util.Set;

@RunWith(RobolectricTestRunner.class)
@@ -99,11 +101,13 @@ import java.util.Set;
public class AudioStreamMediaServiceTest {
    @Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule();
    @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
    private static final String DEVICE_ADDRESS = "00:A1:A1:A1:A1:A1";
    private static final String CHANNEL_ID = "bluetooth_notification_channel";
    private static final String DEVICE_NAME = "name";
    @Mock private Resources mResources;
    @Mock private LocalBluetoothManager mLocalBtManager;
    @Mock private LocalBluetoothLeBroadcastAssistant mLeBroadcastAssistant;
    @Mock private BluetoothLeBroadcastReceiveState mBroadcastReceiveState;
    @Mock private AudioStreamsHelper mAudioStreamsHelper;
    @Mock private NotificationManager mNotificationManager;
    @Mock private MediaSessionManager mMediaSessionManager;
@@ -304,6 +308,63 @@ public class AudioStreamMediaServiceTest {
        verify(mAudioStreamMediaService).stopSelf();
    }

    @Test
    public void assistantCallback_onReceiveStateChanged_connected_doNothing() {
        mSetFlagsRule.enableFlags(Flags.FLAG_AUDIO_SHARING_HYSTERESIS_MODE_FIX);
        mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);

        mAudioStreamMediaService.onCreate();
        mAudioStreamMediaService.onStartCommand(setupIntent(), /* flags= */ 0, /* startId= */ 0);

        assertThat(mAudioStreamMediaService.mBroadcastAssistantCallback).isNotNull();
        List<Long> bisSyncState = new ArrayList<>();
        bisSyncState.add(1L);
        when(mBroadcastReceiveState.getBisSyncState()).thenReturn(bisSyncState);
        when(mDevice.getAddress()).thenReturn(DEVICE_ADDRESS);
        when(mBroadcastReceiveState.getSourceDevice()).thenReturn(mDevice);

        mAudioStreamMediaService.mBroadcastAssistantCallback.onReceiveStateChanged(
                mDevice, /* sourceId= */ 0, /* state= */ mBroadcastReceiveState);

        verify(mNotificationManager, never()).notify(anyInt(), any());
    }

    @Test
    public void assistantCallback_onReceiveStateChanged_hysteresis_updateNotification() {
        mSetFlagsRule.enableFlags(Flags.FLAG_AUDIO_SHARING_HYSTERESIS_MODE_FIX);
        mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);

        mAudioStreamMediaService.onCreate();
        mAudioStreamMediaService.onStartCommand(setupIntent(), /* flags= */ 0, /* startId= */ 0);

        assertThat(mAudioStreamMediaService.mBroadcastAssistantCallback).isNotNull();
        when(mBroadcastReceiveState.getBisSyncState()).thenReturn(new ArrayList<>());
        when(mDevice.getAddress()).thenReturn(DEVICE_ADDRESS);
        when(mBroadcastReceiveState.getSourceDevice()).thenReturn(mDevice);

        mAudioStreamMediaService.mBroadcastAssistantCallback.onReceiveStateChanged(
                mDevice, /* sourceId= */ 0, /* state= */ mBroadcastReceiveState);

        verify(mNotificationManager).notify(anyInt(), any());
    }

    @Test
    public void assistantCallback_onReceiveStateChanged_hysteresis_flagOff_doNothing() {
        mSetFlagsRule.disableFlags(Flags.FLAG_AUDIO_SHARING_HYSTERESIS_MODE_FIX);
        mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);

        mAudioStreamMediaService.onCreate();
        mAudioStreamMediaService.onStartCommand(setupIntent(), /* flags= */ 0, /* startId= */ 0);

        assertThat(mAudioStreamMediaService.mBroadcastAssistantCallback).isNotNull();
        mAudioStreamMediaService.mBroadcastAssistantCallback.onReceiveStateChanged(
                mDevice, /* sourceId= */ 0, /* state= */ mBroadcastReceiveState);

        verify(mBroadcastReceiveState, never()).getBisSyncState();
        verify(mBroadcastReceiveState, never()).getSourceDevice();
        verify(mNotificationManager, never()).notify(anyInt(), any());
    }

    @Test
    public void bluetoothCallback_onBluetoothOff_stopSelf() {
        mAudioStreamMediaService.onCreate();