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

Commit a40083e6 authored by Hyundo Moon's avatar Hyundo Moon Committed by Cherrypicker Worker
Browse files

Add AvrcpControllerServiceTest

Bug: 237467631
Test: atest AvrcpControllerServiceTest
Change-Id: I211b4e5ee0c5e850cdbd6608d2a1d04b0d935ef4
(cherry picked from commit a108a06d)
Merged-In: I211b4e5ee0c5e850cdbd6608d2a1d04b0d935ef4
parent 0db97b88
Loading
Loading
Loading
Loading
+46 −26
Original line number Original line Diff line number Diff line
@@ -35,6 +35,7 @@ import com.android.bluetooth.Utils;
import com.android.bluetooth.a2dpsink.A2dpSinkService;
import com.android.bluetooth.a2dpsink.A2dpSinkService;
import com.android.bluetooth.btservice.AdapterService;
import com.android.bluetooth.btservice.AdapterService;
import com.android.bluetooth.btservice.ProfileService;
import com.android.bluetooth.btservice.ProfileService;
import com.android.internal.annotations.VisibleForTesting;
import com.android.modules.utils.SynchronousResultReceiver;
import com.android.modules.utils.SynchronousResultReceiver;


import java.util.ArrayList;
import java.util.ArrayList;
@@ -67,7 +68,8 @@ public class AvrcpControllerService extends ProfileService {
    private static final byte JNI_PLAY_STATUS_PLAYING = 0x01;
    private static final byte JNI_PLAY_STATUS_PLAYING = 0x01;
    private static final byte JNI_PLAY_STATUS_PAUSED = 0x02;
    private static final byte JNI_PLAY_STATUS_PAUSED = 0x02;
    private static final byte JNI_PLAY_STATUS_FWD_SEEK = 0x03;
    private static final byte JNI_PLAY_STATUS_FWD_SEEK = 0x03;
    private static final byte JNI_PLAY_STATUS_REV_SEEK = 0x04;
    @VisibleForTesting
    static final byte JNI_PLAY_STATUS_REV_SEEK = 0x04;
    private static final byte JNI_PLAY_STATUS_ERROR = -1;
    private static final byte JNI_PLAY_STATUS_ERROR = -1;


    /* Folder/Media Item scopes.
    /* Folder/Media Item scopes.
@@ -173,6 +175,7 @@ public class AvrcpControllerService extends ProfileService {
        for (AvrcpControllerStateMachine stateMachine : mDeviceStateMap.values()) {
        for (AvrcpControllerStateMachine stateMachine : mDeviceStateMap.values()) {
            stateMachine.quitNow();
            stateMachine.quitNow();
        }
        }
        mDeviceStateMap.clear();


        sService = null;
        sService = null;
        sBrowseTree = null;
        sBrowseTree = null;
@@ -201,7 +204,8 @@ public class AvrcpControllerService extends ProfileService {
    /**
    /**
     * Set the current active device, notify devices of activity status
     * Set the current active device, notify devices of activity status
     */
     */
    private boolean setActiveDevice(BluetoothDevice device) {
    @VisibleForTesting
    boolean setActiveDevice(BluetoothDevice device) {
        A2dpSinkService a2dpSinkService = A2dpSinkService.getA2dpSinkService();
        A2dpSinkService a2dpSinkService = A2dpSinkService.getA2dpSinkService();
        if (a2dpSinkService == null) {
        if (a2dpSinkService == null) {
            return false;
            return false;
@@ -277,7 +281,8 @@ public class AvrcpControllerService extends ProfileService {
        }
        }
    }
    }


    private void refreshContents(BrowseTree.BrowseNode node) {
    @VisibleForTesting
    void refreshContents(BrowseTree.BrowseNode node) {
        BluetoothDevice device = node.getDevice();
        BluetoothDevice device = node.getDevice();
        if (device == null) {
        if (device == null) {
            return;
            return;
@@ -467,14 +472,16 @@ public class AvrcpControllerService extends ProfileService {


    /* JNI API*/
    /* JNI API*/
    // Called by JNI when a passthrough key was received.
    // Called by JNI when a passthrough key was received.
    private void handlePassthroughRsp(int id, int keyState, byte[] address) {
    @VisibleForTesting
    void handlePassthroughRsp(int id, int keyState, byte[] address) {
        if (DBG) {
        if (DBG) {
            Log.d(TAG, "passthrough response received as: key: " + id
            Log.d(TAG, "passthrough response received as: key: " + id
                    + " state: " + keyState + "address:" + Arrays.toString(address));
                    + " state: " + keyState + "address:" + Arrays.toString(address));
        }
        }
    }
    }


    private void handleGroupNavigationRsp(int id, int keyState) {
    @VisibleForTesting
    void handleGroupNavigationRsp(int id, int keyState) {
        if (DBG) {
        if (DBG) {
            Log.d(TAG, "group navigation response received as: key: " + id + " state: "
            Log.d(TAG, "group navigation response received as: key: " + id + " state: "
                    + keyState);
                    + keyState);
@@ -482,17 +489,14 @@ public class AvrcpControllerService extends ProfileService {
    }
    }


    // Called by JNI when a device has connected or disconnected.
    // Called by JNI when a device has connected or disconnected.
    private synchronized void onConnectionStateChanged(boolean remoteControlConnected,
    @VisibleForTesting
    synchronized void onConnectionStateChanged(boolean remoteControlConnected,
            boolean browsingConnected, byte[] address) {
            boolean browsingConnected, byte[] address) {
        BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address);
        BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address);
        if (DBG) {
        if (DBG) {
            Log.d(TAG, "onConnectionStateChanged " + remoteControlConnected + " "
            Log.d(TAG, "onConnectionStateChanged " + remoteControlConnected + " "
                    + browsingConnected + device);
                    + browsingConnected + device);
        }
        }
        if (device == null) {
            Log.e(TAG, "onConnectionStateChanged Device is null");
            return;
        }


        StackEvent event =
        StackEvent event =
                StackEvent.connectionStateChanged(remoteControlConnected, browsingConnected);
                StackEvent.connectionStateChanged(remoteControlConnected, browsingConnected);
@@ -512,12 +516,14 @@ public class AvrcpControllerService extends ProfileService {
    }
    }


    // Called by JNI to notify Avrcp of features supported by the Remote device.
    // Called by JNI to notify Avrcp of features supported by the Remote device.
    private void getRcFeatures(byte[] address, int features) {
    @VisibleForTesting
    void getRcFeatures(byte[] address, int features) {
        /* Do Nothing. */
        /* Do Nothing. */
    }
    }


    // Called by JNI to notify Avrcp of a remote device's Cover Art PSM
    // Called by JNI to notify Avrcp of a remote device's Cover Art PSM
    private void getRcPsm(byte[] address, int psm) {
    @VisibleForTesting
    void getRcPsm(byte[] address, int psm) {
        BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address);
        BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address);
        if (DBG) Log.d(TAG, "getRcPsm(device=" + device + ", psm=" + psm + ")");
        if (DBG) Log.d(TAG, "getRcPsm(device=" + device + ", psm=" + psm + ")");
        AvrcpControllerStateMachine stateMachine = getOrCreateStateMachine(device);
        AvrcpControllerStateMachine stateMachine = getOrCreateStateMachine(device);
@@ -528,12 +534,14 @@ public class AvrcpControllerService extends ProfileService {
    }
    }


    // Called by JNI
    // Called by JNI
    private void setPlayerAppSettingRsp(byte[] address, byte accepted) {
    @VisibleForTesting
    void setPlayerAppSettingRsp(byte[] address, byte accepted) {
        /* Do Nothing. */
        /* Do Nothing. */
    }
    }


    // Called by JNI when remote wants to receive absolute volume notifications.
    // Called by JNI when remote wants to receive absolute volume notifications.
    private synchronized void handleRegisterNotificationAbsVol(byte[] address, byte label) {
    @VisibleForTesting
    synchronized void handleRegisterNotificationAbsVol(byte[] address, byte label) {
        if (DBG) {
        if (DBG) {
            Log.d(TAG, "handleRegisterNotificationAbsVol");
            Log.d(TAG, "handleRegisterNotificationAbsVol");
        }
        }
@@ -546,7 +554,8 @@ public class AvrcpControllerService extends ProfileService {
    }
    }


    // Called by JNI when remote wants to set absolute volume.
    // Called by JNI when remote wants to set absolute volume.
    private synchronized void handleSetAbsVolume(byte[] address, byte absVol, byte label) {
    @VisibleForTesting
    synchronized void handleSetAbsVolume(byte[] address, byte absVol, byte label) {
        if (DBG) {
        if (DBG) {
            Log.d(TAG, "handleSetAbsVolume ");
            Log.d(TAG, "handleSetAbsVolume ");
        }
        }
@@ -559,7 +568,8 @@ public class AvrcpControllerService extends ProfileService {
    }
    }


    // Called by JNI when a track changes and local AvrcpController is registered for updates.
    // Called by JNI when a track changes and local AvrcpController is registered for updates.
    private synchronized void onTrackChanged(byte[] address, byte numAttributes, int[] attributes,
    @VisibleForTesting
    synchronized void onTrackChanged(byte[] address, byte numAttributes, int[] attributes,
            String[] attribVals) {
            String[] attribVals) {
        if (DBG) {
        if (DBG) {
            Log.d(TAG, "onTrackChanged");
            Log.d(TAG, "onTrackChanged");
@@ -586,7 +596,8 @@ public class AvrcpControllerService extends ProfileService {
    }
    }


    // Called by JNI periodically based upon timer to update play position
    // Called by JNI periodically based upon timer to update play position
    private synchronized void onPlayPositionChanged(byte[] address, int songLen,
    @VisibleForTesting
    synchronized void onPlayPositionChanged(byte[] address, int songLen,
            int currSongPosition) {
            int currSongPosition) {
        if (DBG) {
        if (DBG) {
            Log.d(TAG, "onPlayPositionChanged pos " + currSongPosition);
            Log.d(TAG, "onPlayPositionChanged pos " + currSongPosition);
@@ -601,7 +612,8 @@ public class AvrcpControllerService extends ProfileService {
    }
    }


    // Called by JNI on changes of play status
    // Called by JNI on changes of play status
    private synchronized void onPlayStatusChanged(byte[] address, byte playStatus) {
    @VisibleForTesting
    synchronized void onPlayStatusChanged(byte[] address, byte playStatus) {
        if (DBG) {
        if (DBG) {
            Log.d(TAG, "onPlayStatusChanged " + playStatus);
            Log.d(TAG, "onPlayStatusChanged " + playStatus);
        }
        }
@@ -615,7 +627,8 @@ public class AvrcpControllerService extends ProfileService {
    }
    }


    // Called by JNI to report remote Player's capabilities
    // Called by JNI to report remote Player's capabilities
    private synchronized void handlePlayerAppSetting(byte[] address, byte[] playerAttribRsp,
    @VisibleForTesting
    synchronized void handlePlayerAppSetting(byte[] address, byte[] playerAttribRsp,
            int rspLen) {
            int rspLen) {
        if (DBG) {
        if (DBG) {
            Log.d(TAG, "handlePlayerAppSetting rspLen = " + rspLen);
            Log.d(TAG, "handlePlayerAppSetting rspLen = " + rspLen);
@@ -631,7 +644,8 @@ public class AvrcpControllerService extends ProfileService {
        }
        }
    }
    }


    private synchronized void onPlayerAppSettingChanged(byte[] address, byte[] playerAttribRsp,
    @VisibleForTesting
    synchronized void onPlayerAppSettingChanged(byte[] address, byte[] playerAttribRsp,
            int rspLen) {
            int rspLen) {
        if (DBG) {
        if (DBG) {
            Log.d(TAG, "onPlayerAppSettingChanged ");
            Log.d(TAG, "onPlayerAppSettingChanged ");
@@ -648,7 +662,8 @@ public class AvrcpControllerService extends ProfileService {
        }
        }
    }
    }


    private void onAvailablePlayerChanged(byte[] address) {
    @VisibleForTesting
    void onAvailablePlayerChanged(byte[] address) {
        if (DBG) {
        if (DBG) {
            Log.d(TAG," onAvailablePlayerChanged");
            Log.d(TAG," onAvailablePlayerChanged");
        }
        }
@@ -766,7 +781,8 @@ public class AvrcpControllerService extends ProfileService {
        return apb.build();
        return apb.build();
    }
    }


    private void handleChangeFolderRsp(byte[] address, int count) {
    @VisibleForTesting
    void handleChangeFolderRsp(byte[] address, int count) {
        if (DBG) {
        if (DBG) {
            Log.d(TAG, "handleChangeFolderRsp count: " + count);
            Log.d(TAG, "handleChangeFolderRsp count: " + count);
        }
        }
@@ -778,7 +794,8 @@ public class AvrcpControllerService extends ProfileService {
        }
        }
    }
    }


    private void handleSetBrowsedPlayerRsp(byte[] address, int items, int depth) {
    @VisibleForTesting
    void handleSetBrowsedPlayerRsp(byte[] address, int items, int depth) {
        if (DBG) {
        if (DBG) {
            Log.d(TAG, "handleSetBrowsedPlayerRsp depth: " + depth);
            Log.d(TAG, "handleSetBrowsedPlayerRsp depth: " + depth);
        }
        }
@@ -791,7 +808,8 @@ public class AvrcpControllerService extends ProfileService {
        }
        }
    }
    }


    private void handleSetAddressedPlayerRsp(byte[] address, int status) {
    @VisibleForTesting
    void handleSetAddressedPlayerRsp(byte[] address, int status) {
        if (DBG) {
        if (DBG) {
            Log.d(TAG, "handleSetAddressedPlayerRsp status: " + status);
            Log.d(TAG, "handleSetAddressedPlayerRsp status: " + status);
        }
        }
@@ -804,7 +822,8 @@ public class AvrcpControllerService extends ProfileService {
        }
        }
    }
    }


    private void handleAddressedPlayerChanged(byte[] address, int id) {
    @VisibleForTesting
    void handleAddressedPlayerChanged(byte[] address, int id) {
        if (DBG) {
        if (DBG) {
            Log.d(TAG, "handleAddressedPlayerChanged id: " + id);
            Log.d(TAG, "handleAddressedPlayerChanged id: " + id);
        }
        }
@@ -817,7 +836,8 @@ public class AvrcpControllerService extends ProfileService {
        }
        }
    }
    }


    private void handleNowPlayingContentChanged(byte[] address) {
    @VisibleForTesting
    void handleNowPlayingContentChanged(byte[] address) {
        if (DBG) {
        if (DBG) {
            Log.d(TAG, "handleNowPlayingContentChanged");
            Log.d(TAG, "handleNowPlayingContentChanged");
        }
        }
+357 −11
Original line number Original line Diff line number Diff line
@@ -15,45 +15,59 @@
 */
 */
package com.android.bluetooth.avrcpcontroller;
package com.android.bluetooth.avrcpcontroller;


import static com.google.common.truth.Truth.assertThat;

import static org.mockito.Mockito.any;
import static org.mockito.Mockito.anyString;
import static org.mockito.Mockito.anyString;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;


import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothAdapter;
import android.content.Context;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothProfile;
import android.support.v4.media.session.PlaybackStateCompat;


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


import com.android.bluetooth.R;
import com.android.bluetooth.TestUtils;
import com.android.bluetooth.TestUtils;
import com.android.bluetooth.btservice.AdapterService;
import com.android.bluetooth.btservice.AdapterService;


import org.junit.After;
import org.junit.After;
import org.junit.Assert;
import org.junit.Assume;
import org.junit.Assume;
import org.junit.Before;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Rule;
import org.junit.Test;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.mockito.MockitoAnnotations;


import java.util.ArrayList;
import java.util.Arrays;

@MediumTest
@MediumTest
@RunWith(AndroidJUnit4.class)
@RunWith(AndroidJUnit4.class)
public class AvrcpControllerServiceTest {
public class AvrcpControllerServiceTest {
    private static final String REMOTE_DEVICE_ADDRESS = "00:00:00:00:00:00";
    private static final byte[] REMOTE_DEVICE_ADDRESS_AS_ARRAY = new byte[] {0, 0, 0, 0, 0, 0};

    private AvrcpControllerService mService = null;
    private AvrcpControllerService mService = null;
    private BluetoothAdapter mAdapter = null;
    private BluetoothAdapter mAdapter = null;
    private Context mTargetContext;


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


    @Mock private AdapterService mAdapterService;
    @Mock private AdapterService mAdapterService;
    @Mock private AvrcpControllerStateMachine mStateMachine;

    private BluetoothDevice mRemoteDevice;


    @Before
    @Before
    public void setUp() throws Exception {
    public void setUp() throws Exception {
        mTargetContext = InstrumentationRegistry.getTargetContext();
        Assume.assumeTrue("Ignore test when AvrcpControllerService is not enabled",
        Assume.assumeTrue("Ignore test when AvrcpControllerService is not enabled",
                AvrcpControllerService.isEnabled());
                AvrcpControllerService.isEnabled());
        MockitoAnnotations.initMocks(this);
        MockitoAnnotations.initMocks(this);
@@ -61,10 +75,12 @@ public class AvrcpControllerServiceTest {
        doReturn(true, false).when(mAdapterService).isStartedProfile(anyString());
        doReturn(true, false).when(mAdapterService).isStartedProfile(anyString());
        TestUtils.startService(mServiceRule, AvrcpControllerService.class);
        TestUtils.startService(mServiceRule, AvrcpControllerService.class);
        mService = AvrcpControllerService.getAvrcpControllerService();
        mService = AvrcpControllerService.getAvrcpControllerService();
        Assert.assertNotNull(mService);
        assertThat(mService).isNotNull();
        // Try getting the Bluetooth adapter
        // Try getting the Bluetooth adapter
        mAdapter = BluetoothAdapter.getDefaultAdapter();
        mAdapter = BluetoothAdapter.getDefaultAdapter();
        Assert.assertNotNull(mAdapter);
        assertThat(mAdapter).isNotNull();
        mRemoteDevice = mAdapter.getRemoteDevice(REMOTE_DEVICE_ADDRESS);
        mService.mDeviceStateMap.put(mRemoteDevice, mStateMachine);
    }
    }


    @After
    @After
@@ -74,12 +90,342 @@ public class AvrcpControllerServiceTest {
        }
        }
        TestUtils.stopService(mServiceRule, AvrcpControllerService.class);
        TestUtils.stopService(mServiceRule, AvrcpControllerService.class);
        mService = AvrcpControllerService.getAvrcpControllerService();
        mService = AvrcpControllerService.getAvrcpControllerService();
        Assert.assertNull(mService);
        assertThat(mService).isNull();
        TestUtils.clearAdapterService(mAdapterService);
        TestUtils.clearAdapterService(mAdapterService);
    }
    }


    @Test
    @Test
    public void testInitialize() {
    public void initialize() {
        Assert.assertNotNull(AvrcpControllerService.getAvrcpControllerService());
        assertThat(AvrcpControllerService.getAvrcpControllerService()).isNotNull();
    }

    @Test
    public void disconnect_whenDisconnected_returnsFalse() {
        when(mStateMachine.getState()).thenReturn(BluetoothProfile.STATE_DISCONNECTED);

        assertThat(mService.disconnect(mRemoteDevice)).isFalse();
    }

    @Test
    public void disconnect_whenDisconnected_returnsTrue() {
        when(mStateMachine.getState()).thenReturn(BluetoothProfile.STATE_CONNECTED);

        assertThat(mService.disconnect(mRemoteDevice)).isTrue();
        verify(mStateMachine).disconnect();
    }

    @Test
    public void removeStateMachine() {
        when(mStateMachine.getDevice()).thenReturn(mRemoteDevice);

        mService.removeStateMachine(mStateMachine);

        assertThat(mService.mDeviceStateMap).doesNotContainKey(mRemoteDevice);
    }

    @Test
    public void getConnectedDevices() {
        when(mAdapterService.getBondedDevices()).thenReturn(
                new BluetoothDevice[]{mRemoteDevice});
        when(mStateMachine.getState()).thenReturn(BluetoothProfile.STATE_CONNECTED);

        assertThat(mService.getConnectedDevices()).contains(mRemoteDevice);
    }

    @Test
    public void setActiveDevice_whenA2dpSinkServiceIsNotInitailized_returnsFalse() {
        assertThat(mService.setActiveDevice(mRemoteDevice)).isFalse();

        assertThat(mService.getActiveDevice()).isNull();
    }

    @Test
    public void getCurrentMetadataIfNoCoverArt_doesNotCrash() {
        mService.getCurrentMetadataIfNoCoverArt(mRemoteDevice);
    }

    @Test
    public void refreshContents() {
        BrowseTree.BrowseNode node = mock(BrowseTree.BrowseNode.class);
        when(node.getDevice()).thenReturn(mRemoteDevice);

        mService.refreshContents(node);

        verify(mStateMachine).requestContents(node);
    }

    @Test
    public void playItem() {
        String parentMediaId = "test_parent_media_id";
        BrowseTree.BrowseNode node = mock(BrowseTree.BrowseNode.class);
        when(mStateMachine.findNode(parentMediaId)).thenReturn(node);

        mService.playItem(parentMediaId);

        verify(mStateMachine).playItem(node);
    }

    @Test
    public void getContents() {
        String parentMediaId = "test_parent_media_id";
        BrowseTree.BrowseNode node = mock(BrowseTree.BrowseNode.class);
        when(mStateMachine.findNode(parentMediaId)).thenReturn(node);

        mService.getContents(parentMediaId);

        verify(node).getContents();
    }

    @Test
    public void createFromNativeMediaItem() {
        long uid = 1;
        int type = 2;
        int[] attrIds = new int[] { 0x01 }; // MEDIA_ATTRIBUTE_TITLE}
        String[] attrVals = new String[] {"test_title"};

        AvrcpItem item = mService.createFromNativeMediaItem(
                REMOTE_DEVICE_ADDRESS_AS_ARRAY, uid, type, "unused_name", attrIds, attrVals);

        assertThat(item.getDevice().getAddress()).isEqualTo(REMOTE_DEVICE_ADDRESS);
        assertThat(item.getItemType()).isEqualTo(AvrcpItem.TYPE_MEDIA);
        assertThat(item.getType()).isEqualTo(type);
        assertThat(item.getUid()).isEqualTo(uid);
        assertThat(item.getUuid()).isNotNull(); // Random uuid
        assertThat(item.getTitle()).isEqualTo(attrVals[0]);
        assertThat(item.isPlayable()).isTrue();
    }

    @Test
    public void createFromNativeFolderItem() {
        long uid = 1;
        int type = 2;
        String folderName = "test_folder_name";
        int playable = 0x01; // Playable folder

        AvrcpItem item = mService.createFromNativeFolderItem(
                REMOTE_DEVICE_ADDRESS_AS_ARRAY, uid, type, folderName, playable);

        assertThat(item.getDevice().getAddress()).isEqualTo(REMOTE_DEVICE_ADDRESS);
        assertThat(item.getItemType()).isEqualTo(AvrcpItem.TYPE_FOLDER);
        assertThat(item.getType()).isEqualTo(type);
        assertThat(item.getUid()).isEqualTo(uid);
        assertThat(item.getUuid()).isNotNull(); // Random uuid
        assertThat(item.getDisplayableName()).isEqualTo(folderName);
        assertThat(item.isPlayable()).isTrue();
    }

    @Test
    public void createFromNativePlayerItem() {
        int playerId = 1;
        String name = "test_name";
        byte[] transportFlags = new byte[] {1, 0, 0, 0, 0, 0, 0, 0};
        int playStatus = AvrcpControllerService.JNI_PLAY_STATUS_REV_SEEK;
        int playerType = AvrcpPlayer.TYPE_AUDIO; // No getter exists

        AvrcpPlayer player = mService.createFromNativePlayerItem(
                REMOTE_DEVICE_ADDRESS_AS_ARRAY, playerId, name, transportFlags,
                playStatus, playerType);

        assertThat(player.getDevice().getAddress()).isEqualTo(REMOTE_DEVICE_ADDRESS);
        assertThat(player.getId()).isEqualTo(playerId);
        assertThat(player.supportsFeature(0)).isTrue();
        assertThat(player.getName()).isEqualTo(name);
        assertThat(player.getPlayStatus()).isEqualTo(PlaybackStateCompat.STATE_REWINDING);
    }

    @Test
    public void handleChangeFolderRsp() {
        int count = 1;

        mService.handleChangeFolderRsp(REMOTE_DEVICE_ADDRESS_AS_ARRAY, count);

        verify(mStateMachine).sendMessage(
                AvrcpControllerStateMachine.MESSAGE_PROCESS_FOLDER_PATH, count);
    }

    @Test
    public void handleSetBrowsedPlayerRsp() {
        int items = 3;
        int depth = 5;

        mService.handleSetBrowsedPlayerRsp(REMOTE_DEVICE_ADDRESS_AS_ARRAY, items, depth);

        verify(mStateMachine).sendMessage(
                AvrcpControllerStateMachine.MESSAGE_PROCESS_SET_BROWSED_PLAYER, items, depth);
    }

    @Test
    public void handleSetAddressedPlayerRsp() {
        int status = 1;

        mService.handleSetAddressedPlayerRsp(REMOTE_DEVICE_ADDRESS_AS_ARRAY, status);

        verify(mStateMachine).sendMessage(
                AvrcpControllerStateMachine.MESSAGE_PROCESS_SET_ADDRESSED_PLAYER);
    }

    @Test
    public void handleAddressedPlayerChanged() {
        int id = 1;

        mService.handleAddressedPlayerChanged(REMOTE_DEVICE_ADDRESS_AS_ARRAY, id);

        verify(mStateMachine).sendMessage(
                AvrcpControllerStateMachine.MESSAGE_PROCESS_ADDRESSED_PLAYER_CHANGED, id);
    }

    @Test
    public void handleNowPlayingContentChanged() {
        mService.handleNowPlayingContentChanged(REMOTE_DEVICE_ADDRESS_AS_ARRAY);

        verify(mStateMachine).nowPlayingContentChanged();
    }

    @Test
    public void JniApisWithNoBehaviors_doNotCrash() {
        mService.handlePassthroughRsp(1, 2, new byte[0]);
        mService.handleGroupNavigationRsp(1, 2);
        mService.getRcFeatures(new byte[0], 1);
        mService.setPlayerAppSettingRsp(new byte[0], (byte) 0);
    }

    @Test
    public void onConnectionStateChanged_connectCase() {
        boolean remoteControlConnected = true;
        boolean browsingConnected = true; // Calls connect when any of them is true.

        mService.onConnectionStateChanged(remoteControlConnected, browsingConnected,
                REMOTE_DEVICE_ADDRESS_AS_ARRAY);

        ArgumentCaptor<StackEvent> captor = ArgumentCaptor.forClass(StackEvent.class);
        verify(mStateMachine).connect(captor.capture());
        StackEvent event = captor.getValue();
        assertThat(event.mType).isEqualTo(StackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED);
        assertThat(event.mRemoteControlConnected).isEqualTo(remoteControlConnected);
        assertThat(event.mBrowsingConnected).isEqualTo(browsingConnected);
    }

    @Test
    public void onConnectionStateChanged_disconnectCase() {
        boolean remoteControlConnected = false;
        boolean browsingConnected = false; // Calls disconnect when both of them are false.

        mService.onConnectionStateChanged(
                remoteControlConnected, browsingConnected, REMOTE_DEVICE_ADDRESS_AS_ARRAY);

        verify(mStateMachine).disconnect();
    }

    @Test
    public void getRcPsm() {
        int psm = 1;

        mService.getRcPsm(REMOTE_DEVICE_ADDRESS_AS_ARRAY, psm);

        verify(mStateMachine).sendMessage(
                AvrcpControllerStateMachine.MESSAGE_PROCESS_RECEIVED_COVER_ART_PSM, psm);
    }

    @Test
    public void handleRegisterNotificationAbsVol() {
        byte label = 1;

        mService.handleRegisterNotificationAbsVol(REMOTE_DEVICE_ADDRESS_AS_ARRAY, label);

        verify(mStateMachine).sendMessage(
                AvrcpControllerStateMachine.MESSAGE_PROCESS_REGISTER_ABS_VOL_NOTIFICATION);
    }

    @Test
    public void handleSetAbsVolume() {
        byte absVol = 15;
        byte label = 1;

        mService.handleSetAbsVolume(REMOTE_DEVICE_ADDRESS_AS_ARRAY, absVol, label);

        verify(mStateMachine).sendMessage(
                AvrcpControllerStateMachine.MESSAGE_PROCESS_SET_ABS_VOL_CMD, absVol);
    }

    @Test
    public void onTrackChanged() {
        byte numAttrs = 0;
        int[] attrs = new int[0];
        String[] attrVals = new String[0];

        mService.onTrackChanged(REMOTE_DEVICE_ADDRESS_AS_ARRAY, numAttrs, attrs, attrVals);

        ArgumentCaptor<AvrcpItem> captor = ArgumentCaptor.forClass(AvrcpItem.class);
        verify(mStateMachine).sendMessage(
                eq(AvrcpControllerStateMachine.MESSAGE_PROCESS_TRACK_CHANGED), captor.capture());
        AvrcpItem item = captor.getValue();
        assertThat(item.getDevice().getAddress()).isEqualTo(REMOTE_DEVICE_ADDRESS);
        assertThat(item.getItemType()).isEqualTo(AvrcpItem.TYPE_MEDIA);
        assertThat(item.getUuid()).isNotNull(); // Random uuid
    }

    @Test
    public void onPlayPositionChanged() {
        int songLen = 100;
        int currSongPos = 33;

        mService.onPlayPositionChanged(REMOTE_DEVICE_ADDRESS_AS_ARRAY, songLen, currSongPos);

        verify(mStateMachine).sendMessage(
                AvrcpControllerStateMachine.MESSAGE_PROCESS_PLAY_POS_CHANGED, songLen, currSongPos);
    }

    @Test
    public void onPlayStatusChanged() {
        byte status = AvrcpControllerService.JNI_PLAY_STATUS_REV_SEEK;

        mService.onPlayStatusChanged(REMOTE_DEVICE_ADDRESS_AS_ARRAY, status);

        verify(mStateMachine).sendMessage(
                AvrcpControllerStateMachine.MESSAGE_PROCESS_PLAY_STATUS_CHANGED,
                PlaybackStateCompat.STATE_REWINDING);
    }

    @Test
    public void onPlayerAppSettingChanged() {
        byte[] playerAttribRsp = new byte[] {PlayerApplicationSettings.REPEAT_STATUS,
                PlayerApplicationSettings.JNI_REPEAT_STATUS_ALL_TRACK_REPEAT};

        mService.onPlayerAppSettingChanged(REMOTE_DEVICE_ADDRESS_AS_ARRAY, playerAttribRsp, 2);

        verify(mStateMachine).sendMessage(
                eq(AvrcpControllerStateMachine.MESSAGE_PROCESS_CURRENT_APPLICATION_SETTINGS),
                any(PlayerApplicationSettings.class));
    }

    @Test
    public void onAvailablePlayerChanged() {
        mService.onAvailablePlayerChanged(REMOTE_DEVICE_ADDRESS_AS_ARRAY);

        verify(mStateMachine).sendMessage(
                AvrcpControllerStateMachine.MESSAGE_PROCESS_AVAILABLE_PLAYER_CHANGED);
    }

    @Test
    public void handleGetFolderItemsRsp() {
        int status = 2;
        AvrcpItem[] items = new AvrcpItem[] {mock(AvrcpItem.class)};

        mService.handleGetFolderItemsRsp(REMOTE_DEVICE_ADDRESS_AS_ARRAY, status, items);

        verify(mStateMachine).sendMessage(
                eq(AvrcpControllerStateMachine.MESSAGE_PROCESS_GET_FOLDER_ITEMS),
                eq(new ArrayList<>(Arrays.asList(items))));
    }

    @Test
    public void handleGetPlayerItemsRsp() {
        AvrcpPlayer[] items = new AvrcpPlayer[] {mock(AvrcpPlayer.class)};

        mService.handleGetPlayerItemsRsp(REMOTE_DEVICE_ADDRESS_AS_ARRAY, items);

        verify(mStateMachine).sendMessage(
                eq(AvrcpControllerStateMachine.MESSAGE_PROCESS_GET_PLAYER_ITEMS),
                eq(new ArrayList<>(Arrays.asList(items))));
    }
    }
}
}