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

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

Merge "When source is not added by the current phone (for example during...

Merge "When source is not added by the current phone (for example during multi-point), we won't be able to sync and desync, so we don't show play pause button." into main
parents 74e07f2a 91e4882d
Loading
Loading
Loading
Loading
+64 −20
Original line number Diff line number Diff line
@@ -79,6 +79,7 @@ public class AudioStreamMediaService extends Service {
    static final String BROADCAST_TITLE = "audio_stream_media_service_broadcast_title";
    static final String DEVICES = "audio_stream_media_service_devices";
    private static final String TAG = "AudioStreamMediaService";
    private static final String DEFAULT_BROADCAST_NAME = "Broadcast";
    private static final int NOTIFICATION_ID = R.string.audio_streams_title;
    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;
@@ -89,7 +90,7 @@ public class AudioStreamMediaService extends Service {
    private static final int STATIC_PLAYBACK_DURATION = 100;
    private static final int STATIC_PLAYBACK_POSITION = 30;
    private static final int ZERO_PLAYBACK_SPEED = 0;
    private final PlaybackState.Builder mPlayStatePlayingBuilder =
    @VisibleForTesting final PlaybackState.Builder mPlayStatePlayingBuilder =
            new PlaybackState.Builder()
                    .setActions(PlaybackState.ACTION_PLAY_PAUSE | PlaybackState.ACTION_SEEK_TO)
                    .setState(
@@ -100,7 +101,17 @@ public class AudioStreamMediaService extends Service {
                            LEAVE_BROADCAST_ACTION,
                            LEAVE_BROADCAST_TEXT,
                            com.android.settings.R.drawable.ic_clear);
    private final PlaybackState.Builder mPlayStatePausedByReceiverBuilder =
    @VisibleForTesting final PlaybackState.Builder mPlayStatePlayingNoActionBuilder =
            new PlaybackState.Builder()
                    .setState(
                            PlaybackState.STATE_PLAYING,
                            STATIC_PLAYBACK_POSITION,
                            ZERO_PLAYBACK_SPEED)
                    .addCustomAction(
                            LEAVE_BROADCAST_ACTION,
                            LEAVE_BROADCAST_TEXT,
                            com.android.settings.R.drawable.ic_clear);
    @VisibleForTesting final PlaybackState.Builder mPlayStatePausedBuilder =
            new PlaybackState.Builder()
                    .setActions(PlaybackState.ACTION_PLAY_PAUSE | PlaybackState.ACTION_SEEK_TO)
                    .setState(
@@ -111,7 +122,7 @@ public class AudioStreamMediaService extends Service {
                            LEAVE_BROADCAST_ACTION,
                            LEAVE_BROADCAST_TEXT,
                            com.android.settings.R.drawable.ic_clear);
    private final PlaybackState.Builder mPlayStatePausedByHostBuilder =
    @VisibleForTesting final PlaybackState.Builder mPlayStatePausedNoActionBuilder =
            new PlaybackState.Builder()
                    .setState(
                            PlaybackState.STATE_PAUSED,
@@ -238,7 +249,11 @@ public class AudioStreamMediaService extends Service {
                                mBroadcastAssistantCallback);
                    }
                    if (mVolumeControl != null && mVolumeControlCallback != null) {
                        try {
                            mVolumeControl.unregisterCallback(mVolumeControlCallback);
                        } catch (IllegalArgumentException e) {
                            Log.w(TAG, "VolumeControl unregister failed. " + e.getMessage());
                        }
                    }
                });
        mHandlerThread.quitSafely();
@@ -410,25 +425,46 @@ public class AudioStreamMediaService extends Service {
        return mLocalSession.getSessionToken();
    }

    private PlaybackState getPlaybackState() {
    @VisibleForTesting
    PlaybackState getPlaybackState() {
        if (Flags.audioStreamPlayPauseByModifySource()) {
            if (isAnyDeviceStreaming()) {
                return mPlayStatePlayingBuilder.build();
            }
            if (isAnyDeviceReceiverPaused()) {
                return mPlayStatePausedByReceiverBuilder.build();
            List<BluetoothDevice> deviceStreaming = getDeviceStreaming();
            if (!deviceStreaming.isEmpty()) {
                // Only if any LE headset's source was added by this phone, we can potentially
                // retrieve the selected channel and perform play/pause actions. Otherwise, we hide
                // action buttons (this could happen during multi-point).
                boolean canPlayPause = deviceStreaming.stream().anyMatch(
                        mSelectedChannelCacheByDevice::containsKey);
                return canPlayPause ? mPlayStatePlayingBuilder.build()
                        : mPlayStatePlayingNoActionBuilder.build();
            }
            List<BluetoothDevice> deviceReceiverPaused = getDeviceReceiverPaused();
            if (!deviceReceiverPaused.isEmpty()) {
                // Similarly, only if any paused LE headset's source was added by this phone,
                // we can potentially perform play/pause actions. Otherwise, we hide action buttons.
                boolean canPlayPause = deviceReceiverPaused.stream().anyMatch(
                        mSelectedChannelCacheByDevice::containsKey);
                return canPlayPause ? mPlayStatePausedBuilder.build()
                        : mPlayStatePausedNoActionBuilder.build();
            }
            if (isAllDeviceHysteresis()) {
                return mPlayStatePausedByHostBuilder.build();
                return mPlayStatePausedNoActionBuilder.build();
            }
            Log.w(TAG, "getPlaybackState() : devices in unexpected state: " + mStateByDevice);
            return mPlayStatePausedByHostBuilder.build();
            return mPlayStatePausedNoActionBuilder.build();
        }
        if (isAllDeviceHysteresis()) {
            return mPlayStatePausedByHostBuilder.build();
            return mPlayStatePausedNoActionBuilder.build();
        }
        return mIsMuted ? mPlayStatePausedByReceiverBuilder.build()
                : mPlayStatePlayingBuilder.build();
        return mIsMuted ? mPlayStatePausedBuilder.build() : mPlayStatePlayingBuilder.build();
    }

    private List<BluetoothDevice> getDeviceStreaming() {
        if (mStateByDevice == null || mStateByDevice.isEmpty()) {
            return emptyList();
        }
        return mStateByDevice.entrySet().stream().filter(
                entry -> STREAMING.equals(entry.getValue())).map(Map.Entry::getKey).toList();
    }

    private boolean isAnyDeviceStreaming() {
@@ -436,9 +472,13 @@ public class AudioStreamMediaService extends Service {
                && mStateByDevice.values().stream().anyMatch(v -> v == STREAMING);
    }

    private boolean isAnyDeviceReceiverPaused() {
        return mStateByDevice != null
                && mStateByDevice.values().stream().anyMatch(v -> v == PAUSED_BY_RECEIVER);
    private List<BluetoothDevice> getDeviceReceiverPaused() {
        if (mStateByDevice == null || mStateByDevice.isEmpty()) {
            return emptyList();
        }
        return mStateByDevice.entrySet().stream().filter(
                entry -> PAUSED_BY_RECEIVER.equals(entry.getValue())).map(
                Map.Entry::getKey).toList();
    }

    private boolean isAllDeviceHysteresis() {
@@ -501,10 +541,14 @@ public class AudioStreamMediaService extends Service {
        var metadata = mLeBroadcastAssistant.getSourceMetadata(sink, sourceId);
        if (metadata == null || metadata.getBroadcastId() != mBroadcastId
                || metadata.getBroadcastName() == null || metadata.getBroadcastName().isEmpty()) {
            if (!programInfo.isEmpty()) {
                Log.d(TAG, "getBroadcastName(): source metadata not found, using programInfo: "
                        + programInfo);
                return programInfo;
            }
            Log.d(TAG, "getBroadcastName(): programInfo empty, using default.");
            return DEFAULT_BROADCAST_NAME;
        }
        return metadata.getBroadcastName();
    }

+65 −0
Original line number Diff line number Diff line
@@ -22,6 +22,7 @@ import static com.android.settings.connecteddevice.audiosharing.audiostreams.Aud
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.PAUSED_BY_RECEIVER;
import static com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant.LocalBluetoothLeBroadcastSourceState.STREAMING;

import static com.google.common.truth.Truth.assertThat;
@@ -38,6 +39,8 @@ import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.mockito.kotlin.VerificationKt.times;

import static java.util.Collections.emptyList;

import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
@@ -430,6 +433,68 @@ public class AudioStreamMediaServiceTest {
        verify(mNotificationManager).notify(anyInt(), any());
    }

    @Test
    public void byReceiveStateFlagOn_getPlayState_streaming_noActionButton() {
        mSetFlagsRule.enableFlags(Flags.FLAG_AUDIO_STREAM_MEDIA_SERVICE_BY_RECEIVE_STATE);
        mSetFlagsRule.enableFlags(Flags.FLAG_AUDIO_STREAM_PLAY_PAUSE_BY_MODIFY_SOURCE);
        mAudioStreamMediaService.onCreate();
        Intent intent1 = setupReceiveDataIntent(1, mDevice, STREAMING, new HashSet<>(emptyList()));
        mAudioStreamMediaService.onStartCommand(intent1, /* flags= */ 0, /* startId= */ 0);

        assertThat(mAudioStreamMediaService.getPlaybackState().toString()).isEqualTo(
                mAudioStreamMediaService.mPlayStatePlayingNoActionBuilder.build().toString());
    }

    @Test
    public void byReceiveStateFlagOn_getPlayState_streaming_hasActionButton() {
        mSetFlagsRule.enableFlags(Flags.FLAG_AUDIO_STREAM_MEDIA_SERVICE_BY_RECEIVE_STATE);
        mSetFlagsRule.enableFlags(Flags.FLAG_AUDIO_STREAM_PLAY_PAUSE_BY_MODIFY_SOURCE);
        mAudioStreamMediaService.onCreate();
        Intent intent1 = setupReceiveDataIntent(1, mDevice, STREAMING, new HashSet<>(List.of(1)));
        mAudioStreamMediaService.onStartCommand(intent1, /* flags= */ 0, /* startId= */ 0);

        assertThat(mAudioStreamMediaService.getPlaybackState().toString()).isEqualTo(
                mAudioStreamMediaService.mPlayStatePlayingBuilder.build().toString());
    }

    @Test
    public void byReceiveStateFlagOn_getPlayState_pausedByReceiver_noActionButton() {
        mSetFlagsRule.enableFlags(Flags.FLAG_AUDIO_STREAM_MEDIA_SERVICE_BY_RECEIVE_STATE);
        mSetFlagsRule.enableFlags(Flags.FLAG_AUDIO_STREAM_PLAY_PAUSE_BY_MODIFY_SOURCE);
        mAudioStreamMediaService.onCreate();
        Intent intent1 = setupReceiveDataIntent(1, mDevice, PAUSED_BY_RECEIVER,
                new HashSet<>(emptyList()));
        mAudioStreamMediaService.onStartCommand(intent1, /* flags= */ 0, /* startId= */ 0);

        assertThat(mAudioStreamMediaService.getPlaybackState().toString()).isEqualTo(
                mAudioStreamMediaService.mPlayStatePausedNoActionBuilder.build().toString());
    }

    @Test
    public void byReceiveStateFlagOn_getPlayState_pausedByReceiver_hasActionButton() {
        mSetFlagsRule.enableFlags(Flags.FLAG_AUDIO_STREAM_MEDIA_SERVICE_BY_RECEIVE_STATE);
        mSetFlagsRule.enableFlags(Flags.FLAG_AUDIO_STREAM_PLAY_PAUSE_BY_MODIFY_SOURCE);
        mAudioStreamMediaService.onCreate();
        Intent intent1 = setupReceiveDataIntent(1, mDevice, PAUSED_BY_RECEIVER,
                new HashSet<>(List.of(1)));
        mAudioStreamMediaService.onStartCommand(intent1, /* flags= */ 0, /* startId= */ 0);

        assertThat(mAudioStreamMediaService.getPlaybackState().toString()).isEqualTo(
                mAudioStreamMediaService.mPlayStatePausedBuilder.build().toString());
    }

    @Test
    public void byReceiveStateFlagOn_getPlayState_paused_noActionButton() {
        mSetFlagsRule.enableFlags(Flags.FLAG_AUDIO_STREAM_MEDIA_SERVICE_BY_RECEIVE_STATE);
        mSetFlagsRule.enableFlags(Flags.FLAG_AUDIO_STREAM_PLAY_PAUSE_BY_MODIFY_SOURCE);
        mAudioStreamMediaService.onCreate();
        Intent intent1 = setupReceiveDataIntent(1, mDevice, PAUSED, new HashSet<>(List.of(1)));
        mAudioStreamMediaService.onStartCommand(intent1, /* flags= */ 0, /* startId= */ 0);

        assertThat(mAudioStreamMediaService.getPlaybackState().toString()).isEqualTo(
                mAudioStreamMediaService.mPlayStatePausedNoActionBuilder.build().toString());
    }

    @Test
    public void onStartCommand_createSessionAndStartForeground() {
        mSetFlagsRule.disableFlags(Flags.FLAG_AUDIO_STREAM_MEDIA_SERVICE_BY_RECEIVE_STATE);