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

Commit 42d5b74e authored by Sandeep Samdaria's avatar Sandeep Samdaria
Browse files

Media session should start on audio focus

Problem: Currently, media session is activated immediately once a
device is connected and gets deactivated when a device is disconnected.
However, this results in clients of media session to create controllers
for the respective active media session even though BT audio focus was
not requested. An active controller results in media events to be sent
to bluetooth.

Solution: Bluetooth requests audio focus when the media service is
prepared. Only once the audio focus is granted, bluetooth module will
activate the media session. Similarly, media session will be deactivated
when the audio focus is lost. On phone being disconnected, media session
will also be de-activated.

Bug: 262309425
Test: Unit-test to validate media session is deactivated on different scenarios.
Tested on device to validate media & assistant use-cases.

Tag: #stability
Change-Id: I90242c92d537bf0e7bc4c30749811af09e5821e5
parent 6089760e
Loading
Loading
Loading
Loading
+13 −4
Original line number Diff line number Diff line
@@ -135,7 +135,7 @@ public class A2dpSinkStreamHandler extends Handler {
    public void handleMessage(Message message) {
        if (DBG) {
            Log.d(TAG, " process message: " + message.what);
            Log.d(TAG, " audioFocus =  " + mAudioFocus);
            Log.d(TAG, " current audioFocus state =  " + mAudioFocus);
        }
        switch (message.what) {
            case SRC_STR_START:
@@ -183,7 +183,12 @@ public class A2dpSinkStreamHandler extends Handler {
                break;

            case AUDIO_FOCUS_CHANGE:
                mAudioFocus = (int) message.obj;
                final int focusChangeCode = (int) message.obj;
                if (DBG) {
                    Log.d(TAG, "New audioFocus =  " + focusChangeCode
                            + " Previous audio focus = " + mAudioFocus);
                }
                mAudioFocus = focusChangeCode;
                // message.obj is the newly granted audio focus.
                switch (mAudioFocus) {
                    case AudioManager.AUDIOFOCUS_GAIN:
@@ -221,7 +226,7 @@ public class A2dpSinkStreamHandler extends Handler {
                AvrcpControllerService avrcpControllerService =
                        AvrcpControllerService.getAvrcpControllerService();
                if (avrcpControllerService != null) {
                    avrcpControllerService.onAudioFocusStateChanged(mAudioFocus);
                    avrcpControllerService.onAudioFocusStateChanged(focusChangeCode);
                } else {
                    Log.w(TAG, "AVRCP Controller Service not available to send focus events to.");
                }
@@ -259,8 +264,12 @@ public class A2dpSinkStreamHandler extends Handler {
        int focusRequestStatus = mAudioManager.requestAudioFocus(focusRequest);
        // If the request is granted begin streaming immediately and schedule an upgrade.
        if (focusRequestStatus == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
            startFluorideStreaming();
            mAudioFocus = AudioManager.AUDIOFOCUS_GAIN;
            final Message a2dpSinkStreamHandlerMessage = A2dpSinkStreamHandler.this
                    .obtainMessage(AUDIO_FOCUS_CHANGE, mAudioFocus);
            A2dpSinkStreamHandler.this.sendMessageAtFrontOfQueue(a2dpSinkStreamHandlerMessage);
        } else {
            Log.e(TAG, "Audio focus was not granted:" + focusRequestStatus);
        }
        return focusRequestStatus;
    }
+9 −2
Original line number Diff line number Diff line
@@ -24,6 +24,7 @@ import android.bluetooth.BluetoothProfile;
import android.bluetooth.IBluetoothAvrcpController;
import android.content.AttributionSource;
import android.content.Intent;
import android.media.AudioManager;
import android.support.v4.media.MediaBrowserCompat.MediaItem;
import android.support.v4.media.session.PlaybackStateCompat;
import android.sysprop.BluetoothProperties;
@@ -510,13 +511,11 @@ public class AvrcpControllerService extends ProfileService {
            // The first device to connect gets to be the active device
            if (getActiveDevice() == null) {
                setActiveDevice(device);
                BluetoothMediaBrowserService.setActive(true);
            }
        } else {
            stateMachine.disconnect();
            if (device.equals(getActiveDevice())) {
                setActiveDevice(null);
                BluetoothMediaBrowserService.setActive(false);
            }
        }
    }
@@ -585,6 +584,14 @@ public class AvrcpControllerService extends ProfileService {
        // Make sure the active device isn't changed while we're processing the event so play/pause
        // commands get routed to the correct device
        synchronized (mActiveDeviceLock) {
            switch (state) {
                case AudioManager.AUDIOFOCUS_GAIN:
                    BluetoothMediaBrowserService.setActive(true);
                    break;
                case AudioManager.AUDIOFOCUS_LOSS:
                    BluetoothMediaBrowserService.setActive(false);
                    break;
            }
            BluetoothDevice device = getActiveDevice();
            if (device == null) {
                Log.w(TAG, "No active device set, ignore focus change");
+14 −0
Original line number Diff line number Diff line
@@ -33,6 +33,7 @@ import androidx.media.MediaBrowserServiceCompat;

import com.android.bluetooth.BluetoothPrefs;
import com.android.bluetooth.R;
import com.android.internal.annotations.VisibleForTesting;

import java.util.ArrayList;
import java.util.List;
@@ -304,12 +305,24 @@ public class BluetoothMediaBrowserService extends MediaBrowserServiceCompat {
     */
    public static synchronized void setActive(boolean active) {
        if (sBluetoothMediaBrowserService != null) {
            if (DBG) Log.d(TAG, "Setting the session active state to:" + active);
            sBluetoothMediaBrowserService.mSession.setActive(active);
        } else {
            Log.w(TAG, "setActive Unavailable");
        }
    }

    /**
     * Checks if the media session is active or not.
     * @return true if media session is active, false otherwise.
     */
    @VisibleForTesting
    public static synchronized boolean isActive() {
        if (sBluetoothMediaBrowserService != null) {
            return sBluetoothMediaBrowserService.mSession.isActive();
        }
        return false;
    }
    /**
     * Get Media session for updating state
     */
@@ -371,6 +384,7 @@ public class BluetoothMediaBrowserService extends MediaBrowserServiceCompat {
            sb.append("\n    playbackState=" + playbackState);
            sb.append("\n    queue=" + queue);
            sb.append("\n    internal_queue=" + sBluetoothMediaBrowserService.mMediaQueue);
            sb.append("\n    session active state=").append(isActive());
        } else {
            Log.w(TAG, "dump Unavailable");
            sb.append(" null");
+3 −3
Original line number Diff line number Diff line
@@ -94,7 +94,7 @@ public class TestUtils {
            throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
        Assert.assertNull("AdapterService.getAdapterService() must be null before setting another"
                + " AdapterService", AdapterService.getAdapterService());
        Assert.assertNotNull(adapterService);
        Assert.assertNotNull("Adapter service should not be null", adapterService);
        // We cannot mock AdapterService.getAdapterService() with Mockito.
        // Hence we need to use reflection to call a private method to
        // initialize properly the AdapterService.sAdapterService field.
@@ -119,7 +119,7 @@ public class TestUtils {
        Assert.assertSame("AdapterService.getAdapterService() must return the same object as the"
                        + " supplied adapterService in this method", adapterService,
                AdapterService.getAdapterService());
        Assert.assertNotNull(adapterService);
        Assert.assertNotNull("Adapter service should not be null", adapterService);
        Method method =
                AdapterService.class.getDeclaredMethod("clearAdapterService", AdapterService.class);
        method.setAccessible(true);
@@ -142,7 +142,7 @@ public class TestUtils {
    public static <T extends ProfileService> void startService(ServiceTestRule serviceTestRule,
            Class<T> profileServiceClass) throws TimeoutException {
        AdapterService adapterService = AdapterService.getAdapterService();
        Assert.assertNotNull(adapterService);
        Assert.assertNotNull("Adapter service should not be null", adapterService);
        Assert.assertTrue("AdapterService.getAdapterService() must return a mocked or spied object"
                + " before calling this method", MockUtil.isMock(adapterService));
        Intent startIntent =
+40 −8
Original line number Diff line number Diff line
@@ -29,13 +29,19 @@ import android.os.Looper;

import androidx.test.InstrumentationRegistry;
import androidx.test.filters.MediumTest;
import androidx.test.rule.ServiceTestRule;
import androidx.test.runner.AndroidJUnit4;

import com.android.bluetooth.R;
import com.android.bluetooth.TestUtils;
import com.android.bluetooth.avrcpcontroller.AvrcpControllerService;
import com.android.bluetooth.avrcpcontroller.BluetoothMediaBrowserService;
import com.android.bluetooth.btservice.AdapterService;

import org.junit.After;
import org.junit.Assume;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
@@ -59,14 +65,23 @@ public class A2dpSinkStreamHandlerTest {

    @Mock private PackageManager mMockPackageManager;

    @Rule
    public final ServiceTestRule mServiceRule = new ServiceTestRule();

    @Mock
    private AdapterService mAdapterService;

    @Before
    public void setUp() {
    public void setUp() throws Exception{
        mTargetContext = InstrumentationRegistry.getTargetContext();
        MockitoAnnotations.initMocks(this);
        // Mock the looper
        if (Looper.myLooper() == null) {
            Looper.prepare();
        }
        TestUtils.setAdapterService(mAdapterService);
        doReturn(true, false).when(mAdapterService).isStartedProfile(anyString());
        TestUtils.startService(mServiceRule, AvrcpControllerService.class);

        mHandlerThread = new HandlerThread("A2dpSinkStreamHandlerTest");
        mHandlerThread.start();
@@ -86,6 +101,13 @@ public class A2dpSinkStreamHandlerTest {
        when(mMockPackageManager.hasSystemFeature(any())).thenReturn(false);

        mStreamHandler = spy(new A2dpSinkStreamHandler(mMockA2dpSink, mMockNativeInterface));
        BluetoothMediaBrowserService.setActive(false);
    }

    @After
    public void tearDown() throws Exception {
        TestUtils.stopService(mServiceRule, AvrcpControllerService.class);
        TestUtils.clearAdapterService(mAdapterService);
    }

    @Test
@@ -97,6 +119,7 @@ public class A2dpSinkStreamHandlerTest {
        verify(mMockNativeInterface, times(0)).informAudioFocusState(1);
        verify(mMockNativeInterface, times(0)).informAudioTrackGain(1.0f);
        assertThat(mStreamHandler.isPlaying()).isFalse();
        assertThat(BluetoothMediaBrowserService.isActive()).isFalse();
    }

    @Test
@@ -108,6 +131,7 @@ public class A2dpSinkStreamHandlerTest {
        verify(mMockNativeInterface, times(0)).informAudioFocusState(1);
        verify(mMockNativeInterface, times(0)).informAudioTrackGain(1.0f);
        assertThat(mStreamHandler.isPlaying()).isFalse();
        assertThat(BluetoothMediaBrowserService.isActive()).isFalse();
    }

    @Test
@@ -115,9 +139,8 @@ public class A2dpSinkStreamHandlerTest {
        // Play was pressed locally, expect streaming to start soon.
        mStreamHandler.handleMessage(mStreamHandler.obtainMessage(A2dpSinkStreamHandler.SNK_PLAY));
        verify(mMockAudioManager, times(1)).requestAudioFocus(any());
        verify(mMockNativeInterface, times(1)).informAudioFocusState(1);
        verify(mMockNativeInterface, times(1)).informAudioTrackGain(1.0f);
        assertThat(mStreamHandler.isPlaying()).isFalse();
        assertThat(BluetoothMediaBrowserService.isActive()).isFalse();
    }

    @Test
@@ -128,6 +151,7 @@ public class A2dpSinkStreamHandlerTest {
        verify(mMockNativeInterface, times(0)).informAudioFocusState(1);
        verify(mMockNativeInterface, times(0)).informAudioTrackGain(1.0f);
        assertThat(mStreamHandler.isPlaying()).isFalse();
        assertThat(BluetoothMediaBrowserService.isActive()).isFalse();
    }

    @Test
@@ -139,6 +163,7 @@ public class A2dpSinkStreamHandlerTest {
        verify(mMockAudioManager, times(0)).abandonAudioFocus(any());
        verify(mMockNativeInterface, times(0)).informAudioFocusState(0);
        assertThat(mStreamHandler.isPlaying()).isFalse();
        assertThat(BluetoothMediaBrowserService.isActive()).isFalse();
    }

    @Test
@@ -149,6 +174,7 @@ public class A2dpSinkStreamHandlerTest {
        verify(mMockNativeInterface, times(0)).informAudioFocusState(1);
        verify(mMockNativeInterface, times(0)).informAudioTrackGain(1.0f);
        assertThat(mStreamHandler.isPlaying()).isFalse();
        assertThat(BluetoothMediaBrowserService.isActive()).isFalse();
    }

    @Test
@@ -157,8 +183,7 @@ public class A2dpSinkStreamHandlerTest {
        when(mMockPackageManager.hasSystemFeature(any())).thenReturn(true);
        mStreamHandler.handleMessage(mStreamHandler.obtainMessage(A2dpSinkStreamHandler.SRC_PLAY));
        verify(mMockAudioManager, times(1)).requestAudioFocus(any());
        verify(mMockNativeInterface, times(1)).informAudioFocusState(1);
        verify(mMockNativeInterface, times(1)).informAudioTrackGain(1.0f);
        TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper());
        assertThat(mStreamHandler.isPlaying()).isTrue();
    }

@@ -180,11 +205,12 @@ public class A2dpSinkStreamHandlerTest {
                mStreamHandler.obtainMessage(A2dpSinkStreamHandler.AUDIO_FOCUS_CHANGE,
                        AudioManager.AUDIOFOCUS_GAIN));
        verify(mMockAudioManager, times(1)).requestAudioFocus(any());
        verify(mMockNativeInterface, times(2)).informAudioFocusState(1);
        verify(mMockNativeInterface, times(2)).informAudioTrackGain(1.0f);
        verify(mMockNativeInterface, times(1)).informAudioFocusState(1);
        verify(mMockNativeInterface, times(1)).informAudioTrackGain(1.0f);

        TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper());
        assertThat(mStreamHandler.getFocusState()).isEqualTo(AudioManager.AUDIOFOCUS_GAIN);
        assertThat(BluetoothMediaBrowserService.isActive()).isTrue();
    }

    @Test
@@ -199,6 +225,7 @@ public class A2dpSinkStreamHandlerTest {
        TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper());
        assertThat(mStreamHandler.getFocusState()).isEqualTo(
                AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK);
        assertThat(BluetoothMediaBrowserService.isActive()).isFalse();
    }

    @Test
@@ -215,6 +242,7 @@ public class A2dpSinkStreamHandlerTest {
        TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper());
        assertThat(mStreamHandler.getFocusState()).isEqualTo(
                AudioManager.AUDIOFOCUS_LOSS_TRANSIENT);
        assertThat(BluetoothMediaBrowserService.isActive()).isFalse();
    }

    @Test
@@ -230,6 +258,8 @@ public class A2dpSinkStreamHandlerTest {
        mStreamHandler.handleMessage(
                mStreamHandler.obtainMessage(A2dpSinkStreamHandler.REQUEST_FOCUS, true));
        verify(mMockAudioManager, times(2)).requestAudioFocus(any());
        assertThat(BluetoothMediaBrowserService.isActive()).isFalse();
        TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper());
    }

    @Test
@@ -241,9 +271,10 @@ public class A2dpSinkStreamHandlerTest {
                mStreamHandler.obtainMessage(A2dpSinkStreamHandler.AUDIO_FOCUS_CHANGE,
                        AudioManager.AUDIOFOCUS_GAIN));
        verify(mMockAudioManager, times(0)).abandonAudioFocus(any());
        verify(mMockNativeInterface, times(2)).informAudioTrackGain(1.0f);
        verify(mMockNativeInterface, times(1)).informAudioTrackGain(1.0f);

        TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper());
        assertThat(BluetoothMediaBrowserService.isActive()).isTrue();
        assertThat(mStreamHandler.getFocusState()).isEqualTo(AudioManager.AUDIOFOCUS_GAIN);
    }

@@ -258,6 +289,7 @@ public class A2dpSinkStreamHandlerTest {
        verify(mMockNativeInterface, times(1)).informAudioFocusState(0);

        TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper());
        assertThat(BluetoothMediaBrowserService.isActive()).isFalse();
        assertThat(mStreamHandler.getFocusState()).isEqualTo(AudioManager.AUDIOFOCUS_NONE);
        assertThat(mStreamHandler.isPlaying()).isFalse();
    }
Loading