blob: d1b3963adcbbd75d1ac95ca2eecbf48fee945aa2 [file] [log] [blame]
/*
* Copyright 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
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.atLeastOnce;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothProfile;
import android.content.Intent;
import android.media.AudioManager;
import android.support.v4.media.session.PlaybackStateCompat;
import androidx.test.filters.MediumTest;
import androidx.test.rule.ServiceTestRule;
import androidx.test.runner.AndroidJUnit4;
import com.android.bluetooth.avrcpcontroller.BluetoothMediaBrowserService.BrowseResult;
import com.android.bluetooth.TestUtils;
import com.android.bluetooth.btservice.AdapterService;
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import java.util.ArrayList;
import java.util.Arrays;
@MediumTest
@RunWith(AndroidJUnit4.class)
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 BluetoothAdapter mAdapter = null;
@Rule public final ServiceTestRule mServiceRule = new ServiceTestRule();
@Rule
public final ServiceTestRule mBluetoothBrowserMediaServiceTestRule = new ServiceTestRule();
@Mock private AdapterService mAdapterService;
@Mock private AvrcpControllerStateMachine mStateMachine;
private BluetoothDevice mRemoteDevice;
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
TestUtils.setAdapterService(mAdapterService);
doReturn(true, false).when(mAdapterService).isStartedProfile(anyString());
TestUtils.startService(mServiceRule, AvrcpControllerService.class);
mService = AvrcpControllerService.getAvrcpControllerService();
assertThat(mService).isNotNull();
// Try getting the Bluetooth adapter
mAdapter = BluetoothAdapter.getDefaultAdapter();
assertThat(mAdapter).isNotNull();
mRemoteDevice = mAdapter.getRemoteDevice(REMOTE_DEVICE_ADDRESS);
mService.mDeviceStateMap.put(mRemoteDevice, mStateMachine);
final Intent bluetoothBrowserMediaServiceStartIntent =
TestUtils.prepareIntentToStartBluetoothBrowserMediaService();
mBluetoothBrowserMediaServiceTestRule.startService(bluetoothBrowserMediaServiceStartIntent);
}
@After
public void tearDown() throws Exception {
TestUtils.stopService(mServiceRule, AvrcpControllerService.class);
mService = AvrcpControllerService.getAvrcpControllerService();
assertThat(mService).isNull();
TestUtils.clearAdapterService(mAdapterService);
}
@Test
public void initialize() {
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, atLeastOnce()).getContents();
}
/**
* Pre-conditions: No node in BrowseTree for specified media ID
* Test: Call AvrcpControllerService.getContents()
* Expected Output: BrowseResult object with status ERROR_MEDIA_ID_INVALID
*/
@Test
public void testGetContentsNoNode_returnInvalidMediaIdStatus() {
String parentMediaId = "test_parent_media_id";
when(mStateMachine.findNode(parentMediaId)).thenReturn(null);
BrowseResult result = mService.getContents(parentMediaId);
assertThat(result.getStatus()).isEqualTo(BrowseResult.ERROR_MEDIA_ID_INVALID);
}
/**
* Pre-conditions: No device is connected - parent media ID is at the root of the BrowseTree
* Test: Call AvrcpControllerService.getContents()
* Expected Output: BrowseResult object with status NO_DEVICE_CONNECTED
*/
@Test
public void getContentsNoDeviceConnected_returnNoDeviceConnectedStatus() {
String parentMediaId = BrowseTree.ROOT;
BrowseResult result = mService.getContents(parentMediaId);
assertThat(result.getStatus()).isEqualTo(BrowseResult.NO_DEVICE_CONNECTED);
}
/**
* Pre-conditions: At least one device is connected
* Test: Call AvrcpControllerService.getContents()
* Expected Output: BrowseResult object with status SUCCESS
*/
@Test
public void getContentsOneDeviceConnected_returnSuccessStatus() {
String parentMediaId = BrowseTree.ROOT;
mService.sBrowseTree.onConnected(mRemoteDevice);
BrowseResult result = mService.getContents(parentMediaId);
assertThat(result.getStatus()).isEqualTo(BrowseResult.SUCCESS);
}
/**
* Pre-conditions: Node for specified media ID is not cached
* Test: {@link BrowseTree.BrowseNode#getContents} returns {@code null} when the node has no
* children/items and the node is not cached.
* When {@link AvrcpControllerService#getContents} receives a node that is not cached,
* it should interpret the status as `DOWNLOAD_PENDING`.
* Expected Output: BrowseResult object with status DOWNLOAD_PENDING; verify that a download
* request has been sent by checking if mStateMachine.requestContents() is called
*/
@Test
public void getContentsNodeNotCached_returnDownloadPendingStatus() {
String parentMediaId = "test_parent_media_id";
BrowseTree.BrowseNode node = mock(BrowseTree.BrowseNode.class);
when(mStateMachine.findNode(parentMediaId)).thenReturn(node);
when(node.isCached()).thenReturn(false);
when(node.getDevice()).thenReturn(mRemoteDevice);
when(node.getID()).thenReturn(parentMediaId);
BrowseResult result = mService.getContents(parentMediaId);
verify(mStateMachine, times(1)).requestContents(eq(node));
assertThat(result.getStatus()).isEqualTo(BrowseResult.DOWNLOAD_PENDING);
}
/**
* Pre-conditions: Parent media ID that is not BrowseTree.ROOT; isCached returns true
* Test: Call AvrcpControllerService.getContents()
* Expected Output: BrowseResult object with status SUCCESS
*/
@Test
public void getContentsNoErrorConditions_returnsSuccessStatus() {
String parentMediaId = "test_parent_media_id";
BrowseTree.BrowseNode node = mock(BrowseTree.BrowseNode.class);
when(mStateMachine.findNode(parentMediaId)).thenReturn(node);
when(node.getContents()).thenReturn(new ArrayList(0));
when(node.isCached()).thenReturn(true);
BrowseResult result = mService.getContents(parentMediaId);
assertThat(result.getStatus()).isEqualTo(BrowseResult.SUCCESS);
}
@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);
assertThat(BluetoothMediaBrowserService.isActive()).isFalse();
}
@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);
assertThat(BluetoothMediaBrowserService.isActive()).isFalse();
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))));
}
@Test
public void dump_doesNotCrash() {
mService.getRcPsm(REMOTE_DEVICE_ADDRESS_AS_ARRAY, 1);
mService.dump(new StringBuilder());
}
@Test
public void testOnFocusChange_audioGainDeviceActive_sessionActivated() {
mService.onAudioFocusStateChanged(AudioManager.AUDIOFOCUS_GAIN);
assertThat(BluetoothMediaBrowserService.isActive()).isTrue();
}
@Test
public void testOnFocusChange_audioLoss_sessionDeactivated() {
mService.onAudioFocusStateChanged(AudioManager.AUDIOFOCUS_LOSS);
assertThat(BluetoothMediaBrowserService.isActive()).isFalse();
}
}