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

Commit 90992009 authored by Jakub Pawlowski's avatar Jakub Pawlowski Committed by Automerger Merge Worker
Browse files

Merge "MediaControlProfileTest" am: 927009be

Original change: https://android-review.googlesource.com/c/platform/packages/apps/Bluetooth/+/1798209

Change-Id: Iad36341f46094254001dfa8daa97f7c1b2b16870
parents 3ed161a7 927009be
Loading
Loading
Loading
Loading
+470 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2021 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.mcp;

import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;

import static org.mockito.Mockito.*;

import android.bluetooth.BluetoothAdapter;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.media.session.PlaybackState;

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

import com.android.bluetooth.R;
import com.android.bluetooth.TestUtils;
import com.android.bluetooth.audio_util.MediaData;
import com.android.bluetooth.audio_util.MediaPlayerList;
import com.android.bluetooth.audio_util.MediaPlayerWrapper;
import com.android.bluetooth.audio_util.Metadata;
import com.android.bluetooth.btservice.AdapterService;

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

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;

@MediumTest
@RunWith(AndroidJUnit4.class)
public class MediaControlProfileTest {
    private final String mFlagDexmarker = System.getProperty("dexmaker.share_classloader", "false");

    private BluetoothAdapter mAdapter;
    private MediaControlProfile mMediaControlProfile;
    private List<Integer> mPendingStateRequest;
    private Context mTargetContext;

    private String packageName = "TestPackage";

    private String name = "TestPlayer";
    private CharSequence charSequence = "TestPlayer";
    private MediaControlServiceCallbacks mMcpServiceCallbacks;

    @Mock private AdapterService mAdapterService;
    @Mock private MediaData mMockMediaData;
    @Mock private MediaPlayerList mMockMediaPlayerList;
    @Mock private Metadata mMockMetadata;
    @Mock private MediaPlayerWrapper mMockMediaPlayerWrapper;
    @Mock private PackageManager mMockPackageManager;
    @Mock private ApplicationInfo mMockApplicationInfo;
    @Mock private MediaControlGattServiceInterface mMockGMcsService;
    @Mock private McpService mMockMcpService;

    @Captor private ArgumentCaptor<HashMap> stateMapCaptor;
    @Captor private ArgumentCaptor<Long> positionCaptor;
    @Captor private ArgumentCaptor<MediaControlProfile.ListCallback> listCallbackCaptor;
    @Captor private ArgumentCaptor<MediaControlServiceCallbacks> mcpServiceCallbacksCaptor;

    @Before
    public void setUp() throws Exception {
        if (!mFlagDexmarker.equals("true")) {
            System.setProperty("dexmaker.share_classloader", "true");
        }

        mTargetContext = InstrumentationRegistry.getTargetContext();
        Assume.assumeTrue("Ignore test when MCP Server is not enabled",
                mTargetContext.getResources().getBoolean(
                        R.bool.profile_supported_mcp_server));
        MediaControlProfile.ListCallback listCallback;
        MockitoAnnotations.initMocks(this);

        TestUtils.setAdapterService(mAdapterService);
        mAdapter = BluetoothAdapter.getDefaultAdapter();

        mMockMediaData.metadata = mMockMetadata;

        mMockMediaData.state = null;
        mMockMetadata.duration = Long.toString(0);
        mMockMetadata.title = null;
        doReturn(mMockMediaPlayerWrapper).when(mMockMediaPlayerList).getActivePlayer();
        doReturn(mMockMcpService).when(mMockMcpService).getApplicationContext();
        doReturn(mMockPackageManager).when(mMockMcpService).getPackageManager();
        doReturn(getInstrumentation().getTargetContext().getMainThreadHandler())
                .when(mMockMcpService)
                .getMainThreadHandler();
        doReturn(packageName).when(mMockMcpService).getPackageName();
        doReturn(name).when(mMockMediaPlayerWrapper).getPackageName();
        doReturn(charSequence).when(mMockApplicationInfo).loadLabel(any(PackageManager.class));
        try {
            doReturn(mMockApplicationInfo)
                    .when(mMockPackageManager)
                    .getApplicationInfo(anyString(), anyInt());
        } catch (PackageManager.NameNotFoundException e) {
            Assert.fail();
        }

        mPendingStateRequest = new ArrayList<>();

        MediaControlProfile.setsMediaPlayerListForTesting(mMockMediaPlayerList);
        mMediaControlProfile = new MediaControlProfile(mMockMcpService);

        //this is equivalent of what usually happens inside init class
        mMediaControlProfile.injectGattServiceForTesting(packageName, mMockGMcsService);
        mMediaControlProfile.onServiceInstanceRegistered(ServiceStatus.OK, mMockGMcsService);
        mMcpServiceCallbacks = mMediaControlProfile;

        mMediaControlProfile.init();


        verify(mMockMediaPlayerList).init(listCallbackCaptor.capture());
        listCallback = listCallbackCaptor.getValue();
        listCallback.run(mMockMediaData);
        // Give some time to verify if post function finishes on update player state method call
        // TODO: Is there a possibility to get rid of this timeout?
        verify(mMockGMcsService, timeout(100).times(1)).updatePlayerState(any(HashMap.class));
    }

    @After
    public void tearDown() throws Exception {
        if (!mTargetContext.getResources().getBoolean(R.bool.profile_supported_mcp_server)) {
            return;
        }

        TestUtils.clearAdapterService(mAdapterService);

        mMediaControlProfile.cleanup();
        mMediaControlProfile = null;
        reset(mMockMediaPlayerList);

        if (!mFlagDexmarker.equals("true")) {
            System.setProperty("dexmaker.share_classloader", mFlagDexmarker);
        }
    }

    @Test
    public void testGetCurrentTrackDuration() {
        long duration = 10;

        // Some duration
        mMockMetadata.duration = Long.toString(duration);
        Assert.assertEquals(duration, mMediaControlProfile.getCurrentTrackDuration());

        // No metadata equals no track duration
        mMockMediaData.metadata = null;
        Assert.assertEquals(MediaControlGattServiceInterface.TRACK_DURATION_UNAVAILABLE,
                mMediaControlProfile.getCurrentTrackDuration());
    }

    @Test
    public void testPlayerState2McsState() {
        Assert.assertEquals(mMediaControlProfile.playerState2McsState(PlaybackState.STATE_PLAYING),
                MediaState.PLAYING);
        Assert.assertEquals(mMediaControlProfile.playerState2McsState(PlaybackState.STATE_NONE),
                MediaState.INACTIVE);
        Assert.assertEquals(mMediaControlProfile.playerState2McsState(PlaybackState.STATE_STOPPED),
                MediaState.PAUSED);
        Assert.assertEquals(mMediaControlProfile.playerState2McsState(PlaybackState.STATE_PAUSED),
                MediaState.PAUSED);
        Assert.assertEquals(mMediaControlProfile.playerState2McsState(PlaybackState.STATE_PLAYING),
                MediaState.PLAYING);
        Assert.assertEquals(
                mMediaControlProfile.playerState2McsState(PlaybackState.STATE_FAST_FORWARDING),
                MediaState.SEEKING);
        Assert.assertEquals(
                mMediaControlProfile.playerState2McsState(PlaybackState.STATE_REWINDING),
                MediaState.SEEKING);
        Assert.assertEquals(
                mMediaControlProfile.playerState2McsState(PlaybackState.STATE_BUFFERING),
                MediaState.PAUSED);
        Assert.assertEquals(mMediaControlProfile.playerState2McsState(PlaybackState.STATE_ERROR),
                MediaState.INACTIVE);
        Assert.assertEquals(
                mMediaControlProfile.playerState2McsState(PlaybackState.STATE_CONNECTING),
                MediaState.INACTIVE);
        Assert.assertEquals(
                mMediaControlProfile.playerState2McsState(PlaybackState.STATE_SKIPPING_TO_PREVIOUS),
                MediaState.PAUSED);
        Assert.assertEquals(
                mMediaControlProfile.playerState2McsState(PlaybackState.STATE_SKIPPING_TO_NEXT),
                MediaState.PAUSED);
        Assert.assertEquals(mMediaControlProfile.playerState2McsState(
                                    PlaybackState.STATE_SKIPPING_TO_QUEUE_ITEM),
                MediaState.PAUSED);
    }

    @Test
    public void testGetLatestTrackPosition() {
        int state = PlaybackState.STATE_PLAYING;
        long position = 10;
        float playback_speed = 1.5f;
        long update_time = 77;

        Assert.assertEquals(mMcpServiceCallbacks.onGetCurrentTrackPosition(),
                MediaControlGattServiceInterface.TRACK_POSITION_UNAVAILABLE);

        PlaybackState.Builder bob = new PlaybackState.Builder(mMockMediaData.state);
        bob.setState(state, position, playback_speed);
        mMockMediaData.state = bob.build();
        doReturn(mMockMediaData.state).when(mMockMediaPlayerWrapper).getPlaybackState();

        Assert.assertNotEquals(mMcpServiceCallbacks.onGetCurrentTrackPosition(),
                MediaControlGattServiceInterface.TRACK_POSITION_UNAVAILABLE);
    }

    @Test
    public void testOnCurrentPlayerStateUpdate() {
        HashMap stateMap;
        int state = PlaybackState.STATE_PLAYING;
        long position = 10;
        float playback_speed = 1.5f;
        long update_time = 77;
        long duration = 10;
        String title = "TestTrackTitle";

        mMockMetadata.duration = Long.toString(duration);
        mMockMetadata.title = title;

        PlaybackState.Builder bob = new PlaybackState.Builder(mMockMediaData.state);
        bob.setState(state, position, playback_speed, update_time);
        mMockMediaData.state = bob.build();

        mMediaControlProfile.onCurrentPlayerStateUpdated(true, true);
        // First time called from ListCallback. Give some time to verify if post function
        // finishes on update player state method call
        // TODO: Is there a possibility to get rid of this timeout?
        verify(mMockGMcsService, timeout(100).times(2)).updatePlayerState(stateMapCaptor.capture());
        stateMap = stateMapCaptor.getValue();

        Assert.assertTrue(stateMap.containsKey(PlayerStateField.PLAYER_NAME));

        // state changed
        Assert.assertTrue(stateMap.containsKey(PlayerStateField.PLAYBACK_STATE));
        Assert.assertTrue(stateMap.containsKey(PlayerStateField.OPCODES_SUPPORTED));
        Assert.assertTrue(stateMap.containsKey(PlayerStateField.SEEKING_SPEED));
        Assert.assertTrue(stateMap.containsKey(PlayerStateField.PLAYBACK_SPEED));
        Assert.assertTrue(stateMap.containsKey(PlayerStateField.TRACK_POSITION));

        // metadata changed
        Assert.assertTrue(stateMap.containsKey(PlayerStateField.TRACK_DURATION));
        Assert.assertTrue(stateMap.containsKey(PlayerStateField.TRACK_TITLE));
    }

    private void testHandleTrackPositionSetRequest(long position, long duration, int times) {
        mMcpServiceCallbacks.onTrackPositionSetRequest(position);
        verify(mMockMediaPlayerWrapper, timeout(100).times(times)).seekTo(positionCaptor.capture());

        // position cannot be negative and bigger than track duration
        if (position < 0)
            Assert.assertEquals(positionCaptor.getValue().longValue(), 0);
        else if (position > duration) {
            Assert.assertEquals(positionCaptor.getValue().longValue(), duration);
        } else {
            Assert.assertEquals(positionCaptor.getValue().longValue(), position);
        }
    }

    @Test
    public void testHandleTrackPositionsSetRequest() {
        long duration = 50;
        long actions = PlaybackState.ACTION_SEEK_TO;
        int times = 1;

        mMockMetadata.duration = Long.toString(duration);

        PlaybackState.Builder bob = new PlaybackState.Builder(mMockMediaData.state);
        bob.setActions(actions);
        mMockMediaData.state = bob.build();

        testHandleTrackPositionSetRequest(-duration, duration, times++);
        testHandleTrackPositionSetRequest(duration + duration, duration, times++);
        testHandleTrackPositionSetRequest(Math.round(duration / 2), duration, times++);

        actions = 0;
        bob.setActions(actions);
        mMockMediaData.state = bob.build();

        mMcpServiceCallbacks.onTrackPositionSetRequest(duration);
        // First time called from ListCallback. Give some time to verify if post function
        // finishes on update player state method call
        // TODO: Is there a possibility to get rid of this timeout?
        verify(mMockGMcsService, timeout(100).times(2)).updatePlayerState(any(HashMap.class));
    }

    @Test
    public void testHandlePlaybackSpeedSetRequest() {
        float speed = 1.5f;
        int times = 1;

        mMcpServiceCallbacks.onPlaybackSpeedSetRequest(speed);
        verify(mMockMediaPlayerWrapper, timeout(100).times(times)).setPlaybackSpeed(anyFloat());

        // Playback speed wouldn't be set if no active player
        doReturn(null).when(mMockMediaPlayerList).getActivePlayer();
        mMcpServiceCallbacks.onPlaybackSpeedSetRequest(speed);
        verify(mMockMediaPlayerWrapper, timeout(100).times(times)).setPlaybackSpeed(anyFloat());
    }

    @Test
    public void testHandleMediaControlRequest() {
        long actions = PlaybackState.ACTION_PLAY | PlaybackState.ACTION_PAUSE
                | PlaybackState.ACTION_STOP | PlaybackState.ACTION_SKIP_TO_PREVIOUS
                | PlaybackState.ACTION_SKIP_TO_NEXT | PlaybackState.ACTION_REWIND
                | PlaybackState.ACTION_FAST_FORWARD | PlaybackState.ACTION_SEEK_TO;
        long duration = 10;

        PlaybackState.Builder bob = new PlaybackState.Builder(mMockMediaData.state);
        bob.setActions(actions);
        mMockMediaData.state = bob.build();

        Request request =
                new Request(Request.Opcodes.PLAY, 0);
        mMcpServiceCallbacks.onMediaControlRequest(request);
        verify(mMockMediaPlayerWrapper, timeout(100)).playCurrent();
        request = new Request(Request.Opcodes.PAUSE, 0);
        mMcpServiceCallbacks.onMediaControlRequest(request);
        verify(mMockMediaPlayerWrapper, timeout(100)).pauseCurrent();
        request = new Request(Request.Opcodes.STOP, 0);
        mMcpServiceCallbacks.onMediaControlRequest(request);
        verify(mMockMediaPlayerWrapper, timeout(100)).seekTo(0);
        verify(mMockMediaPlayerWrapper).stopCurrent();
        request = new Request(
                Request.Opcodes.PREVIOUS_TRACK, 0);
        mMcpServiceCallbacks.onMediaControlRequest(request);
        verify(mMockMediaPlayerWrapper, timeout(100)).skipToPrevious();
        request = new Request(
                Request.Opcodes.NEXT_TRACK, 0);
        mMcpServiceCallbacks.onMediaControlRequest(request);
        verify(mMockMediaPlayerWrapper, timeout(100)).skipToNext();
        request = new Request(
                Request.Opcodes.FAST_REWIND, 0);
        mMcpServiceCallbacks.onMediaControlRequest(request);
        verify(mMockMediaPlayerWrapper, timeout(100)).rewind();
        request = new Request(
                Request.Opcodes.FAST_FORWARD, 0);
        mMcpServiceCallbacks.onMediaControlRequest(request);
        verify(mMockMediaPlayerWrapper, timeout(100)).fastForward();

        mMockMetadata.duration = Long.toString(duration);
        Assert.assertEquals(duration, mMediaControlProfile.getCurrentTrackDuration());
        request = new Request(
                Request.Opcodes.MOVE_RELATIVE, 100);
        mMcpServiceCallbacks.onMediaControlRequest(request);
        verify(mMockMediaPlayerWrapper, timeout(100)).seekTo(duration);
    }

    @Test
    public void testPlayerActions2McsSupportedOpcodes() {
        long actions = PlaybackState.ACTION_PLAY | PlaybackState.ACTION_PAUSE
                | PlaybackState.ACTION_STOP | PlaybackState.ACTION_SKIP_TO_PREVIOUS
                | PlaybackState.ACTION_SKIP_TO_NEXT | PlaybackState.ACTION_REWIND
                | PlaybackState.ACTION_FAST_FORWARD | PlaybackState.ACTION_SEEK_TO;
        int opcodes_supported = Request.SupportedOpcodes.STOP
                | Request.SupportedOpcodes.PAUSE
                | Request.SupportedOpcodes.PLAY
                | Request.SupportedOpcodes.FAST_REWIND
                | Request.SupportedOpcodes.PREVIOUS_TRACK
                | Request.SupportedOpcodes.NEXT_TRACK
                | Request.SupportedOpcodes.FAST_FORWARD
                | Request.SupportedOpcodes.MOVE_RELATIVE;

        Assert.assertEquals(
                mMediaControlProfile.playerActions2McsSupportedOpcodes(actions), opcodes_supported);
    }

    @Test
    public void testProcessPendingPlayerStateRequest() {
        HashMap stateMap;
        int state = PlaybackState.STATE_PLAYING;
        long position = 10;
        float playback_speed = 1.5f;

        PlaybackState.Builder bob = new PlaybackState.Builder(mMockMediaData.state);
        bob.setState(state, position, playback_speed);
        mMockMediaData.state = bob.build();
        doReturn(mMockMediaData.state).when(mMockMediaPlayerWrapper).getPlaybackState();

        PlayerStateField[] state_fields = new PlayerStateField[] {PlayerStateField.PLAYBACK_STATE,
                PlayerStateField.TRACK_DURATION, PlayerStateField.PLAYBACK_SPEED,
                PlayerStateField.SEEKING_SPEED, PlayerStateField.PLAYING_ORDER,
                PlayerStateField.TRACK_POSITION, PlayerStateField.PLAYER_NAME,
                PlayerStateField.PLAYING_ORDER_SUPPORTED, PlayerStateField.OPCODES_SUPPORTED};

        mMcpServiceCallbacks.onPlayerStateRequest(state_fields);
        // First time called from ListCallback. Give some time to verify if post function
        // finishes on update player state method call
        // TODO: Is there a possibility to get rid of this timeout?
        verify(mMockGMcsService, timeout(100).times(2)).updatePlayerState(stateMapCaptor.capture());
        stateMap = stateMapCaptor.getValue();

        Assert.assertTrue(stateMap.containsKey(PlayerStateField.PLAYBACK_STATE));
        Assert.assertTrue(stateMap.containsKey(PlayerStateField.TRACK_DURATION));
        Assert.assertTrue(stateMap.containsKey(PlayerStateField.PLAYBACK_SPEED));
        Assert.assertTrue(stateMap.containsKey(PlayerStateField.SEEKING_SPEED));
        Assert.assertTrue(stateMap.containsKey(PlayerStateField.PLAYING_ORDER));
        Assert.assertTrue(stateMap.containsKey(PlayerStateField.TRACK_POSITION));
        Assert.assertTrue(stateMap.containsKey(PlayerStateField.PLAYER_NAME));
        Assert.assertTrue(stateMap.containsKey(PlayerStateField.PLAYING_ORDER_SUPPORTED));
        Assert.assertTrue(stateMap.containsKey(PlayerStateField.OPCODES_SUPPORTED));
    }

    private void testGetCurrentPlayerPlayingOrder(
            PlayingOrder expected_value, boolean is_shuffle_set, boolean is_repeat_set) {
        doReturn(is_shuffle_set).when(mMockMediaPlayerWrapper).isShuffleSet();
        doReturn(is_repeat_set).when(mMockMediaPlayerWrapper).isRepeatSet();
        Assert.assertEquals(expected_value, mMediaControlProfile.getCurrentPlayerPlayingOrder());
    }

    @Test
    public void testGetCurrentPlayerPlayingOrders() {
        testGetCurrentPlayerPlayingOrder(PlayingOrder.SHUFFLE_REPEAT, true, true);
        testGetCurrentPlayerPlayingOrder(PlayingOrder.SHUFFLE_ONCE, true, false);
        testGetCurrentPlayerPlayingOrder(PlayingOrder.IN_ORDER_REPEAT, false, true);
        testGetCurrentPlayerPlayingOrder(PlayingOrder.IN_ORDER_ONCE, false, false);
    }

    private void testGetSupportedPlayingOrder(boolean is_shuffle_set, boolean is_repeat_set) {
        int expected_value = SupportedPlayingOrder.IN_ORDER_ONCE;

        if (is_repeat_set)
            expected_value |= SupportedPlayingOrder.IN_ORDER_REPEAT;
        if (is_shuffle_set) {
            if (is_repeat_set)
                expected_value |= SupportedPlayingOrder.SHUFFLE_REPEAT;
            else
                expected_value |= SupportedPlayingOrder.SHUFFLE_ONCE;
        }

        doReturn(is_shuffle_set).when(mMockMediaPlayerWrapper).isShuffleSupported();
        doReturn(is_repeat_set).when(mMockMediaPlayerWrapper).isRepeatSupported();
        Assert.assertEquals(
                expected_value, mMediaControlProfile.getSupportedPlayingOrder().intValue());
    }

    @Test
    public void testGetSupportedPlayingOrders() {
        testGetSupportedPlayingOrder(true, true);
        testGetSupportedPlayingOrder(true, false);
        testGetSupportedPlayingOrder(false, true);
        testGetSupportedPlayingOrder(false, false);
    }
}