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

Commit 80e1b69a authored by chelseahao's avatar chelseahao
Browse files

Create only one media session per audio stream media service.

This might cause callback not being cleaned up properly even if the service is destroyed.

Test: atest
Flag: com.android.settingslib.flags.enable_le_audio_sharing
Bug: 362140159 369452993
Change-Id: I389735162c1f9e41cea25f63f881c20be4effb60
parent ccaa62ec
Loading
Loading
Loading
Loading
+93 −88
Original line number Diff line number Diff line
@@ -106,7 +106,7 @@ public class AudioStreamMediaService extends Service {
    // 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.
    private final AtomicInteger mLatestPositiveVolume = new AtomicInteger(25);
    private final AtomicBoolean mHasStopped = new AtomicBoolean(false);
    private final Object mLocalSessionLock = new Object();
    private int mBroadcastId;
    @Nullable private List<BluetoothDevice> mDevices;
    @Nullable private LocalBluetoothManager mLocalBtManager;
@@ -125,7 +125,7 @@ public class AudioStreamMediaService extends Service {
        if (!BluetoothUtils.isAudioSharingEnabled()) {
            return;
        }

        Log.d(TAG, "onCreate()");
        super.onCreate();
        mLocalBtManager = Utils.getLocalBtManager(this);
        if (mLocalBtManager == null) {
@@ -146,6 +146,13 @@ public class AudioStreamMediaService extends Service {
            return;
        }

        mExecutor.execute(
                () -> {
                    if (mLocalBtManager == null
                            || mLeBroadcastAssistant == null
                            || mNotificationManager == null) {
                        return;
                    }
                    if (mNotificationManager.getNotificationChannel(CHANNEL_ID) == null) {
                        NotificationChannel notificationChannel =
                                new NotificationChannel(
@@ -165,7 +172,9 @@ public class AudioStreamMediaService extends Service {
                    }

                    mBroadcastAssistantCallback = new AssistantCallback();
        mLeBroadcastAssistant.registerServiceCallBack(mExecutor, mBroadcastAssistantCallback);
                    mLeBroadcastAssistant.registerServiceCallBack(
                            mExecutor, mBroadcastAssistantCallback);
                });
    }

    @Override
@@ -175,19 +184,29 @@ public class AudioStreamMediaService extends Service {
        if (!BluetoothUtils.isAudioSharingEnabled()) {
            return;
        }
        if (mDevices != null) {
            mDevices.clear();
            mDevices = null;
        }
        synchronized (mLocalSessionLock) {
            if (mLocalSession != null) {
                mLocalSession.release();
                mLocalSession = null;
            }
        }
        mExecutor.execute(
                () -> {
                    if (mLocalBtManager != null) {
                        mLocalBtManager.getEventManager().unregisterCallback(mBluetoothCallback);
                    }
                    if (mLeBroadcastAssistant != null && mBroadcastAssistantCallback != null) {
            mLeBroadcastAssistant.unregisterServiceCallBack(mBroadcastAssistantCallback);
                        mLeBroadcastAssistant.unregisterServiceCallBack(
                                mBroadcastAssistantCallback);
                    }
                    if (mVolumeControl != null && mVolumeControlCallback != null) {
                        mVolumeControl.unregisterCallback(mVolumeControlCallback);
                    }
        if (mLocalSession != null) {
            mLocalSession.release();
            mLocalSession = null;
        }
                });
    }

    @Override
@@ -195,33 +214,33 @@ public class AudioStreamMediaService extends Service {
        Log.d(TAG, "onStartCommand()");
        if (intent == null) {
            Log.w(TAG, "Intent is null. Service will not start.");
            mHasStopped.set(true);
            stopSelf();
            return START_NOT_STICKY;
        }
        mBroadcastId = intent.getIntExtra(BROADCAST_ID, -1);
        if (mBroadcastId == -1) {
            Log.w(TAG, "Invalid broadcast ID. Service will not start.");
            mHasStopped.set(true);
            stopSelf();
            return START_NOT_STICKY;
        }
        var extra = intent.getParcelableArrayListExtra(DEVICES, BluetoothDevice.class);
        if (extra == null || extra.isEmpty()) {
            Log.w(TAG, "No device. Service will not start.");
            mHasStopped.set(true);
            stopSelf();
            return START_NOT_STICKY;
        }
        mDevices = Collections.synchronizedList(extra);
        createLocalMediaSession(intent.getStringExtra(BROADCAST_TITLE));
        startForeground(NOTIFICATION_ID, buildNotification());
        // Reset in case the service is previously stopped but not yet destroyed.
        mHasStopped.set(false);
        MediaSession.Token token =
                getOrCreateLocalMediaSession(intent.getStringExtra(BROADCAST_TITLE));
        startForeground(NOTIFICATION_ID, buildNotification(token));
        return START_NOT_STICKY;
    }

    private void createLocalMediaSession(String title) {
    private MediaSession.Token getOrCreateLocalMediaSession(String title) {
        synchronized (mLocalSessionLock) {
            if (mLocalSession != null) {
                return mLocalSession.getSessionToken();
            }
            mLocalSession = new MediaSession(this, TAG);
            mLocalSession.setMetadata(
                    new MediaMetadata.Builder()
@@ -232,6 +251,8 @@ public class AudioStreamMediaService extends Service {
            mLocalSession.setPlaybackState(getPlaybackState());
            mMediaSessionCallback = new MediaSessionCallback();
            mLocalSession.setCallback(mMediaSessionCallback);
            return mLocalSession.getSessionToken();
        }
    }

    private PlaybackState getPlaybackState() {
@@ -252,12 +273,9 @@ public class AudioStreamMediaService extends Service {
        return device != null ? device.getName() : DEFAULT_DEVICE_NAME;
    }

    private Notification buildNotification() {
    private Notification buildNotification(MediaSession.Token token) {
        String deviceName = getDeviceName();
        Notification.MediaStyle mediaStyle =
                new Notification.MediaStyle()
                        .setMediaSession(
                                mLocalSession != null ? mLocalSession.getSessionToken() : null);
        Notification.MediaStyle mediaStyle = new Notification.MediaStyle().setMediaSession(token);
        if (deviceName != null && !deviceName.isEmpty()) {
            mediaStyle.setRemotePlaybackInfo(
                    deviceName, com.android.settingslib.R.drawable.ic_bt_le_audio, null);
@@ -291,9 +309,6 @@ public class AudioStreamMediaService extends Service {
        }

        private void handleRemoveSource() {
            var unused =
                    ThreadUtils.postOnBackgroundThread(
                            () -> {
            List<BluetoothLeBroadcastReceiveState> connected =
                    mAudioStreamsHelper == null
                            ? emptyList()
@@ -301,10 +316,8 @@ public class AudioStreamMediaService extends Service {
            if (connected.stream()
                    .map(BluetoothLeBroadcastReceiveState::getBroadcastId)
                    .noneMatch(id -> id == mBroadcastId)) {
                                    mHasStopped.set(true);
                stopSelf();
            }
                            });
        }
    }

@@ -326,7 +339,11 @@ public class AudioStreamMediaService extends Service {
                    mIsMuted.set(false);
                    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) {
            if (BluetoothAdapter.STATE_OFF == bluetoothState) {
                Log.d(TAG, "onBluetoothStateChanged() : stopSelf");
                mHasStopped.set(true);
                stopSelf();
            }
        }
@@ -362,7 +378,6 @@ public class AudioStreamMediaService extends Service {
            }
            if (mDevices == null || mDevices.isEmpty()) {
                Log.d(TAG, "onProfileConnectionStateChanged() : stopSelf");
                mHasStopped.set(true);
                stopSelf();
            }
        }
@@ -371,7 +386,11 @@ public class AudioStreamMediaService extends Service {
    private class MediaSessionCallback extends MediaSession.Callback {
        public void onSeekTo(long pos) {
            Log.d(TAG, "onSeekTo: " + pos);
            updateNotification(getPlaybackState());
            synchronized (mLocalSessionLock) {
                if (mLocalSession != null) {
                    mLocalSession.setPlaybackState(getPlaybackState());
                }
            }
        }

        @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());
                                }
                            }
                        });
    }
}
+0 −2
Original line number Diff line number Diff line
@@ -139,7 +139,6 @@ public class AudioStreamsHelper {
    }

    /** Retrieves a list of all LE broadcast receive states from active sinks. */
    @VisibleForTesting
    public List<BluetoothLeBroadcastReceiveState> getAllConnectedSources() {
        if (mLeBroadcastAssistant == null) {
            Log.w(TAG, "getAllSources(): LeBroadcastAssistant is null!");
@@ -165,7 +164,6 @@ public class AudioStreamsHelper {
    }

    /** Retrieves LocalBluetoothLeBroadcastAssistant. */
    @VisibleForTesting
    @Nullable
    public LocalBluetoothLeBroadcastAssistant getLeBroadcastAssistant() {
        return mLeBroadcastAssistant;
+3 −25
Original line number Diff line number Diff line
@@ -80,6 +80,7 @@ import org.mockito.Mock;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.android.util.concurrent.InlineExecutorService;
import org.robolectric.annotation.Config;
import org.robolectric.shadow.api.Shadow;
import org.robolectric.util.ReflectionHelpers;
@@ -143,6 +144,8 @@ public class AudioStreamMediaServiceTest {

        mAudioStreamMediaService = spy(new AudioStreamMediaService());
        ReflectionHelpers.setField(mAudioStreamMediaService, "mBase", mContext);
        ReflectionHelpers.setField(
                mAudioStreamMediaService, "mExecutor", new InlineExecutorService());
        when(mAudioStreamMediaService.getSystemService(anyString()))
                .thenReturn(mMediaSessionManager);
        when(mMediaSessionManager.createSession(any(), anyString(), any())).thenReturn(mISession);
@@ -352,18 +355,6 @@ public class AudioStreamMediaServiceTest {
        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
    public void mediaSessionCallback_onPause_setVolume() {
        mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
@@ -415,19 +406,6 @@ public class AudioStreamMediaServiceTest {
                        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
    public void onBind_returnNull() {
        IBinder binder = mAudioStreamMediaService.onBind(new Intent());