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

Commit c1ac5c11 authored by Chelsea Hao's avatar Chelsea Hao Committed by Android (Google) Code Review
Browse files

Merge "Show "Stream paused" when hysteresis mode in UMO." into main

parents ce463e41 d0abb3f7
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -14035,7 +14035,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();