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

Commit a57462b0 authored by Automerger Merge Worker's avatar Automerger Merge Worker
Browse files

Merge "AVRCP Controller manage active device" am: 9eb36944 am: 9efa17f6

Change-Id: Ib0605bd40270b51a668570910c4fe79baea29b33
parents 97d69379 9efa17f6
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());
    }

    /**