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

Commit 9eb36944 authored by Joseph Pirozzo's avatar Joseph Pirozzo Committed by Gerrit Code Review
Browse files

Merge "AVRCP Controller manage active device"

parents 2fc8dc9e 6a28b170
Loading
Loading
Loading
Loading
+24 −0
Original line number Diff line number Diff line
@@ -218,6 +218,29 @@ static void informAudioTrackGainNative(JNIEnv* env, jobject object,
  sBluetoothA2dpInterface->set_audio_track_gain((float)gain);
}

static jboolean setActiveDeviceNative(JNIEnv* env, jobject object,
                                      jbyteArray address) {
  if (!sBluetoothA2dpInterface) return JNI_FALSE;

  ALOGI("%s: sBluetoothA2dpInterface: %p", __func__, sBluetoothA2dpInterface);

  jbyte* addr = env->GetByteArrayElements(address, NULL);
  if (!addr) {
    jniThrowIOException(env, EINVAL);
    return JNI_FALSE;
  }

  RawAddress rawAddress;
  rawAddress.FromOctets((uint8_t*)addr);
  bt_status_t status = sBluetoothA2dpInterface->set_active_device(rawAddress);
  if (status != BT_STATUS_SUCCESS) {
    ALOGE("Failed sending passthru command, status: %d", status);
  }
  env->ReleaseByteArrayElements(address, addr, 0);

  return (status == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE;
}

static JNINativeMethod sMethods[] = {
    {"classInitNative", "()V", (void*)classInitNative},
    {"initNative", "()V", (void*)initNative},
@@ -226,6 +249,7 @@ static JNINativeMethod sMethods[] = {
    {"disconnectA2dpNative", "([B)Z", (void*)disconnectA2dpNative},
    {"informAudioFocusStateNative", "(I)V", (void*)informAudioFocusStateNative},
    {"informAudioTrackGainNative", "(F)V", (void*)informAudioTrackGainNative},
    {"setActiveDeviceNative", "([B)Z", (void*)setActiveDeviceNative},
};

int register_com_android_bluetooth_a2dp_sink(JNIEnv* env) {
+22 −0
Original line number Diff line number Diff line
@@ -75,6 +75,17 @@ public class A2dpSinkService extends ProfileService {
        return sService;
    }

    /**
     * Testing API to inject a mockA2dpSinkService.
     * @hide
     */
    @VisibleForTesting
    public static void setA2dpSinkService(A2dpSinkService service) {
        sService = service;
        sService.mA2dpSinkStreamHandler = new A2dpSinkStreamHandler(sService, sService);
    }


    public A2dpSinkService() {
        mAdapter = BluetoothAdapter.getDefaultAdapter();
    }
@@ -404,6 +415,17 @@ public class A2dpSinkService extends ProfileService {

    native boolean disconnectA2dpNative(byte[] address);

    /**
     * set A2DP state machine as the active device
     * the active device is the only one that will receive passthrough commands and the only one
     * that will have its audio decoded
     *
     * @hide
     * @param address
     * @return active device request has been scheduled
     */
    public native boolean setActiveDeviceNative(byte[] address);

    /**
     * inform A2DP decoder of the current audio focus
     *
+17 −0
Original line number Diff line number Diff line
@@ -149,6 +149,23 @@ public class AvrcpControllerService extends ProfileService {
        }
    }

    void playItem(String parentMediaId) {
        if (DBG) Log.d(TAG, "playItem(" + parentMediaId + ")");
        // Check if the requestedNode is a player rather than a song
        BrowseTree.BrowseNode requestedNode = sBrowseTree.findBrowseNodeByID(parentMediaId);
        if (requestedNode == null) {
            for (AvrcpControllerStateMachine stateMachine : mDeviceStateMap.values()) {
                // Check each state machine for the song and then play it
                requestedNode = stateMachine.findNode(parentMediaId);
                if (requestedNode != null) {
                    if (DBG) Log.d(TAG, "Found a node");
                    stateMachine.playItem(requestedNode);
                    break;
                }
            }
        }
    }

    /*Java API*/

    /**
+81 −16
Original line number Diff line number Diff line
@@ -39,6 +39,7 @@ import com.android.bluetooth.btservice.MetricsLogger;
import com.android.bluetooth.btservice.ProfileService;
import com.android.bluetooth.statemachine.State;
import com.android.bluetooth.statemachine.StateMachine;
import com.android.internal.annotations.VisibleForTesting;

import java.util.ArrayList;
import java.util.List;
@@ -101,6 +102,7 @@ class AvrcpControllerStateMachine extends StateMachine {
    private static final byte NOTIFICATION_RSP_TYPE_INTERIM = 0x00;
    private static final byte NOTIFICATION_RSP_TYPE_CHANGED = 0x01;

    private static BluetoothDevice sActiveDevice;
    private final AudioManager mAudioManager;
    private final boolean mIsVolumeFixed;

@@ -206,6 +208,42 @@ class AvrcpControllerStateMachine extends StateMachine {
    public void dump(StringBuilder sb) {
        ProfileService.println(sb, "mDevice: " + mDevice.getAddress() + "("
                + mDevice.getName() + ") " + this.toString());
        ProfileService.println(sb, "isActive: " + isActive());
    }

    @VisibleForTesting
    boolean isActive() {
        return mDevice == sActiveDevice;
    }

    /*
     * requestActive
     *
     * Set the current device active if nothing an already connected device isn't playing
     */
    private boolean requestActive() {
        if (sActiveDevice == null
                || BluetoothMediaBrowserService.getPlaybackState()
                != PlaybackStateCompat.STATE_PLAYING) {
            return setActive();
        }
        return false;
    }

    /*
     * setActive
     *
     * Set this state machine as the active device and update media browse service
     */
    private boolean setActive() {
        if (DBG) Log.d(TAG, "setActive" + mDevice);
        if (A2dpSinkService.getA2dpSinkService().setActiveDeviceNative(mDeviceAddress)) {
            sActiveDevice = mDevice;
            BluetoothMediaBrowserService.addressedPlayerChanged(mSessionCallbacks);
            BluetoothMediaBrowserService.notifyChanged(mAddressedPlayer.getPlaybackState());
            BluetoothMediaBrowserService.notifyChanged(mBrowseTree.mNowPlayingNode);
        }
        return mDevice == sActiveDevice;
    }

    @Override
@@ -232,7 +270,9 @@ class AvrcpControllerStateMachine extends StateMachine {
        mAddressedPlayer.setPlayStatus(PlaybackStateCompat.STATE_ERROR);
        mAddressedPlayer.updateCurrentTrack(null);
        mBrowseTree.mNowPlayingNode.setCached(false);
        if (isActive()) {
            BluetoothMediaBrowserService.notifyChanged(mBrowseTree.mNowPlayingNode);
        }
        mService.sBrowseTree.mRootNode.removeChild(
                mBrowseTree.mRootNode);
        BluetoothMediaBrowserService.notifyChanged(mService
@@ -244,12 +284,21 @@ class AvrcpControllerStateMachine extends StateMachine {
        BluetoothMediaBrowserService.notifyChanged(node);
    }

    private void notifyChanged(PlaybackStateCompat state) {
        if (isActive()) {
            BluetoothMediaBrowserService.notifyChanged(state);
        }
    }

    void requestContents(BrowseTree.BrowseNode node) {
        sendMessage(MESSAGE_GET_FOLDER_ITEMS, node);

        logD("Fetching " + node);
    }

    public void playItem(BrowseTree.BrowseNode node) {
        sendMessage(MESSAGE_PLAY_ITEM, node);
    }

    void nowPlayingContentChanged() {
        mBrowseTree.mNowPlayingNode.setCached(false);
        sendMessage(MESSAGE_GET_FOLDER_ITEMS, mBrowseTree.mNowPlayingNode);
@@ -297,6 +346,7 @@ class AvrcpControllerStateMachine extends StateMachine {
        @Override
        public void enter() {
            if (mMostRecentState == BluetoothProfile.STATE_CONNECTING) {
                requestActive();
                BluetoothMediaBrowserService.addressedPlayerChanged(mSessionCallbacks);
                BluetoothMediaBrowserService.notifyChanged(mAddressedPlayer.getPlaybackState());
                broadcastConnectionStateChanged(BluetoothProfile.STATE_CONNECTED);
@@ -331,7 +381,7 @@ class AvrcpControllerStateMachine extends StateMachine {

                case MESSAGE_PLAY_ITEM:
                    //Set Addressed Player
                    playItem((BrowseTree.BrowseNode) msg.obj);
                    processPlayItem((BrowseTree.BrowseNode) msg.obj);
                    return true;

                case MSG_AVRCP_PASSTHRU:
@@ -348,12 +398,15 @@ class AvrcpControllerStateMachine extends StateMachine {

                case MESSAGE_PROCESS_TRACK_CHANGED:
                    mAddressedPlayer.updateCurrentTrack((MediaMetadata) msg.obj);
                    if (isActive()) {
                        BluetoothMediaBrowserService.trackChanged((MediaMetadata) msg.obj);
                    }
                    return true;

                case MESSAGE_PROCESS_PLAY_STATUS_CHANGED:
                    mAddressedPlayer.setPlayStatus(msg.arg1);
                    BluetoothMediaBrowserService.notifyChanged(mAddressedPlayer.getPlaybackState());
                    BluetoothMediaBrowserService.notifyChanged(
                            mAddressedPlayer.getPlaybackState());
                    if (mAddressedPlayer.getPlaybackState().getState()
                            == PlaybackStateCompat.STATE_PLAYING
                            && A2dpSinkService.getFocusState() == AudioManager.AUDIOFOCUS_NONE) {
@@ -369,10 +422,11 @@ class AvrcpControllerStateMachine extends StateMachine {
                case MESSAGE_PROCESS_PLAY_POS_CHANGED:
                    if (msg.arg2 != -1) {
                        mAddressedPlayer.setPlayTime(msg.arg2);

                        if (isActive()) {
                            BluetoothMediaBrowserService.notifyChanged(
                                    mAddressedPlayer.getPlaybackState());
                        }
                    }
                    return true;

                case MESSAGE_PROCESS_ADDRESSED_PLAYER_CHANGED:
@@ -392,13 +446,13 @@ class AvrcpControllerStateMachine extends StateMachine {
                case MESSAGE_PROCESS_SUPPORTED_APPLICATION_SETTINGS:
                    mAddressedPlayer.setSupportedPlayerApplicationSettings(
                            (PlayerApplicationSettings) msg.obj);
                    BluetoothMediaBrowserService.notifyChanged(mAddressedPlayer.getPlaybackState());
                    notifyChanged(mAddressedPlayer.getPlaybackState());
                    return true;

                case MESSAGE_PROCESS_CURRENT_APPLICATION_SETTINGS:
                    mAddressedPlayer.setCurrentPlayerApplicationSettings(
                            (PlayerApplicationSettings) msg.obj);
                    BluetoothMediaBrowserService.notifyChanged(mAddressedPlayer.getPlaybackState());
                    notifyChanged(mAddressedPlayer.getPlaybackState());
                    return true;

                case MESSAGE_PROCESS_AVAILABLE_PLAYER_CHANGED:
@@ -415,7 +469,8 @@ class AvrcpControllerStateMachine extends StateMachine {

        }

        private void playItem(BrowseTree.BrowseNode node) {
        private void processPlayItem(BrowseTree.BrowseNode node) {
            setActive();
            if (node == null) {
                Log.w(TAG, "Invalid item to play");
            } else {
@@ -722,8 +777,11 @@ class AvrcpControllerStateMachine extends StateMachine {
        @Override
        public void enter() {
            onBrowsingDisconnected();
            if (isActive()) {
                sActiveDevice = null;
                BluetoothMediaBrowserService.trackChanged(null);
                BluetoothMediaBrowserService.addressedPlayerChanged(null);
            }
            broadcastConnectionStateChanged(BluetoothProfile.STATE_DISCONNECTING);
            transitionTo(mDisconnected);
        }
@@ -856,7 +914,14 @@ class AvrcpControllerStateMachine extends StateMachine {
            // Play the item if possible.
            onPrepare();
            BrowseTree.BrowseNode node = mBrowseTree.findBrowseNodeByID(mediaId);
            if (node != null) {
                // node was found on this bluetooth device
                sendMessage(MESSAGE_PLAY_ITEM, node);
            } else {
                // node was not found on this device, pause here, and play on another device
                sendMessage(MSG_AVRCP_PASSTHRU, AvrcpControllerService.PASS_THRU_CMD_ID_PAUSE);
                mService.playItem(mediaId);
            }
        }

        @Override
+46 −6
Original line number Diff line number Diff line
@@ -63,8 +63,8 @@ public class AvrcpControllerStateMachineTest {
    private static final int CONNECT_TIMEOUT_TEST_MILLIS = 1000;
    private static final int KEY_DOWN = 0;
    private static final int KEY_UP = 1;
    private AvrcpControllerStateMachine mAvrcpStateMachine;
    private BluetoothAdapter mAdapter;
    private AvrcpControllerStateMachine mAvrcpControllerStateMachine;
    private Context mTargetContext;
    private BluetoothDevice mTestDevice;
    private ArgumentCaptor<Intent> mIntentArgument = ArgumentCaptor.forClass(Intent.class);
@@ -83,11 +83,12 @@ public class AvrcpControllerStateMachineTest {
    private AudioManager mAudioManager;
    @Mock
    private AvrcpControllerService mAvrcpControllerService;
    @Mock
    private A2dpSinkService mA2dpSinkService;

    @Mock
    private Resources mMockResources;

    AvrcpControllerStateMachine mAvrcpStateMachine;

    @Before
    public void setUp() throws Exception {
@@ -107,9 +108,12 @@ public class AvrcpControllerStateMachineTest {
        TestUtils.clearAdapterService(mAvrcpAdapterService);
        TestUtils.setAdapterService(mA2dpAdapterService);
        TestUtils.startService(mA2dpServiceRule, A2dpSinkService.class);
        when(mA2dpSinkService.setActiveDeviceNative(any())).thenReturn(true);

        when(mMockResources.getBoolean(R.bool.a2dp_sink_automatically_request_audio_focus))
                .thenReturn(true);
        doReturn(mMockResources).when(mAvrcpControllerService).getResources();
        A2dpSinkService.setA2dpSinkService(mA2dpSinkService);
        doReturn(15).when(mAudioManager).getStreamMaxVolume(anyInt());
        doReturn(8).when(mAudioManager).getStreamVolume(anyInt());
        doReturn(true).when(mAudioManager).isVolumeFixed();
@@ -247,7 +251,8 @@ public class AvrcpControllerStateMachineTest {
        mAvrcpStateMachine.dump(sb);
        Assert.assertEquals(sb.toString(),
                "  mDevice: " + mTestDevice.toString()
                + "(null) name=AvrcpControllerStateMachine state=(null)\n");
                + "(null) name=AvrcpControllerStateMachine state=(null)\n"
                + "  isActive: false\n");
    }

    /**
@@ -625,7 +630,7 @@ public class AvrcpControllerStateMachineTest {
                eq(mTestAddress), eq(AvrcpControllerService.PASS_THRU_CMD_ID_PAUSE), eq(KEY_DOWN));
        TestUtils.waitForLooperToFinishScheduledTask(
                A2dpSinkService.getA2dpSinkService().getMainLooper());
        Assert.assertEquals(AudioManager.AUDIOFOCUS_NONE, A2dpSinkService.getFocusState());
        Assert.assertEquals(AudioManager.AUDIOFOCUS_NONE, mA2dpSinkService.getFocusState());
    }

    /**
@@ -641,8 +646,43 @@ public class AvrcpControllerStateMachineTest {
                PlaybackStateCompat.STATE_PLAYING);
        TestUtils.waitForLooperToFinishScheduledTask(mAvrcpStateMachine.getHandler().getLooper());
        TestUtils.waitForLooperToFinishScheduledTask(
                A2dpSinkService.getA2dpSinkService().getMainLooper());
        Assert.assertEquals(AudioManager.AUDIOFOCUS_GAIN, A2dpSinkService.getFocusState());
                mA2dpSinkService.getMainLooper());
        verify(mA2dpSinkService).requestAudioFocus(mTestDevice, true);
    }

    /**
     * Test that the correct device becomes active
     *
     * The first connected device is automatically active, additional ones are not.
     * After an explicit play command a device becomes active.
     */
    @Test
    public void testActiveDeviceManagement() {
        // Setup structures and verify initial conditions
        final String rootName = "__ROOT__";
        final String playerName = "Player 1";
        byte[] secondTestAddress = new byte[]{00, 01, 02, 03, 04, 06};
        BluetoothDevice secondTestDevice = mAdapter.getRemoteDevice(secondTestAddress);
        AvrcpControllerStateMachine secondAvrcpStateMachine =
                new AvrcpControllerStateMachine(secondTestDevice, mAvrcpControllerService);
        secondAvrcpStateMachine.start();
        Assert.assertFalse(mAvrcpStateMachine.isActive());

        // Connect device 1 and 2 and verify first one is set as active
        setUpConnectedState(true, true);
        secondAvrcpStateMachine.connect(StackEvent.connectionStateChanged(true, true));
        Assert.assertTrue(mAvrcpStateMachine.isActive());
        Assert.assertFalse(secondAvrcpStateMachine.isActive());

        // Request the second device to play an item and verify active device switched
        BrowseTree.BrowseNode results = mAvrcpStateMachine.findNode(rootName);
        Assert.assertEquals(rootName + mTestDevice.toString(), results.getID());
        BrowseTree.BrowseNode playerNodes = mAvrcpStateMachine.findNode(results.getID());
        secondAvrcpStateMachine.playItem(playerNodes);
        TestUtils.waitForLooperToFinishScheduledTask(secondAvrcpStateMachine.getHandler()
                .getLooper());
        Assert.assertFalse(mAvrcpStateMachine.isActive());
        Assert.assertTrue(secondAvrcpStateMachine.isActive());
    }

    /**