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

Commit 00292aec authored by Treehugger Robot's avatar Treehugger Robot Committed by Gerrit Code Review
Browse files

Merge "Make pause/stop cancel playback reestablishment during transient loss"

parents 78fa6692 a0fe0932
Loading
Loading
Loading
Loading
+11 −54
Original line number Diff line number Diff line
@@ -16,7 +16,6 @@

package com.android.bluetooth.a2dpsink;

import android.bluetooth.BluetoothDevice;
import android.content.pm.PackageManager;
import android.media.AudioAttributes;
import android.media.AudioFocusRequest;
@@ -25,15 +24,10 @@ import android.media.AudioManager.OnAudioFocusChangeListener;
import android.media.MediaPlayer;
import android.os.Handler;
import android.os.Message;
import android.support.v4.media.session.PlaybackStateCompat;
import android.util.Log;

import com.android.bluetooth.R;
import com.android.bluetooth.avrcpcontroller.BluetoothMediaBrowserService;
import com.android.bluetooth.hfpclient.HeadsetClientService;
import com.android.bluetooth.hfpclient.HfpClientCall;

import java.util.List;
import com.android.bluetooth.avrcpcontroller.AvrcpControllerService;

/**
 * Bluetooth A2DP SINK Streaming Handler.
@@ -71,7 +65,6 @@ public class A2dpSinkStreamHandler extends Handler {
    public static final int DISCONNECT = 6; // Remote device was disconnected
    public static final int AUDIO_FOCUS_CHANGE = 7; // Audio focus callback with associated change
    public static final int REQUEST_FOCUS = 8; // Request focus when the media service is active
    public static final int DELAYED_PAUSE = 9; // If a call just started allow stack time to settle

    // Used to indicate focus lost
    private static final int STATE_FOCUS_LOST = 0;
@@ -84,7 +77,6 @@ public class A2dpSinkStreamHandler extends Handler {
    private AudioManager mAudioManager;
    // Keep track if the remote device is providing audio
    private boolean mStreamAvailable = false;
    private boolean mSentPause = false;
    // Keep track of the relevant audio focus (None, Transient, Gain)
    private int mAudioFocus = AudioManager.AUDIOFOCUS_NONE;

@@ -187,7 +179,7 @@ public class A2dpSinkStreamHandler extends Handler {

            case DISCONNECT:
                // Remote device has disconnected, restore everything to default state.
                mSentPause = false;
                mStreamAvailable = false;
                break;

            case AUDIO_FOCUS_CHANGE:
@@ -195,13 +187,8 @@ public class A2dpSinkStreamHandler extends Handler {
                // message.obj is the newly granted audio focus.
                switch (mAudioFocus) {
                    case AudioManager.AUDIOFOCUS_GAIN:
                        removeMessages(DELAYED_PAUSE);
                        // Begin playing audio, if we paused the remote, send a play now.
                        // Begin playing audio
                        startFluorideStreaming();
                        if (mSentPause) {
                            sendAvrcpPlay();
                            mSentPause = false;
                        }
                        break;

                    case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
@@ -220,27 +207,23 @@ public class A2dpSinkStreamHandler extends Handler {
                        break;

                    case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
                        // Temporary loss of focus, if we are actively streaming pause the remote
                        // and make sure we resume playback when we regain focus.
                        sendMessageDelayed(obtainMessage(DELAYED_PAUSE), SETTLE_TIMEOUT);
                        // Temporary loss of focus. Set gain to zero.
                        setFluorideAudioTrackGain(0);
                        break;

                    case AudioManager.AUDIOFOCUS_LOSS:
                        // Permanent loss of focus probably due to another audio app, abandon focus
                        // and stop playback.
                        abandonAudioFocus();
                        sendAvrcpPause();
                        break;
                }
                break;

            case DELAYED_PAUSE:
                if (BluetoothMediaBrowserService.getPlaybackState()
                            == PlaybackStateCompat.STATE_PLAYING && !inCallFromStreamingDevice()) {
                    sendAvrcpPause();
                    mSentPause = true;
                    mStreamAvailable = false;
                // Route new focus state to AVRCP Controller to handle media player states
                AvrcpControllerService avrcpControllerService =
                        AvrcpControllerService.getAvrcpControllerService();
                if (avrcpControllerService != null) {
                    avrcpControllerService.onAudioFocusStateChanged(mAudioFocus);
                } else {
                    Log.w(TAG, "AVRCP Controller Service not available to send focus events to.");
                }
                break;

@@ -316,7 +299,6 @@ public class A2dpSinkStreamHandler extends Handler {
        }

        mMediaPlayer.start();
        BluetoothMediaBrowserService.setActive(true);
    }

    private synchronized void abandonAudioFocus() {
@@ -335,7 +317,6 @@ public class A2dpSinkStreamHandler extends Handler {
        if (mMediaPlayer == null) {
            return;
        }
        BluetoothMediaBrowserService.setActive(false);
        mMediaPlayer.stop();
        mMediaPlayer.release();
        mMediaPlayer = null;
@@ -356,30 +337,6 @@ public class A2dpSinkStreamHandler extends Handler {
        mNativeInterface.informAudioTrackGain(gain);
    }

    private void sendAvrcpPause() {
        BluetoothMediaBrowserService.pause();
    }

    private void sendAvrcpPlay() {
        BluetoothMediaBrowserService.play();
    }

    private boolean inCallFromStreamingDevice() {
        BluetoothDevice targetDevice = null;
        List<BluetoothDevice> connectedDevices = mA2dpSinkService.getConnectedDevices();
        if (!connectedDevices.isEmpty()) {
            targetDevice = connectedDevices.get(0);
        }
        HeadsetClientService headsetClientService = HeadsetClientService.getHeadsetClientService();
        if (targetDevice != null && headsetClientService != null) {
            List<HfpClientCall> currentCalls =
                    headsetClientService.getCurrentCalls(targetDevice);
            if (currentCalls == null) return false;
            return currentCalls.size() > 0;
        }
        return false;
    }

    private boolean isIotDevice() {
        return mA2dpSinkService.getPackageManager().hasSystemFeature(
                PackageManager.FEATURE_EMBEDDED);
+29 −0
Original line number Diff line number Diff line
@@ -502,11 +502,13 @@ public class AvrcpControllerService extends ProfileService {
            // The first device to connect gets to be the active device
            if (getActiveDevice() == null) {
                setActiveDevice(device);
                BluetoothMediaBrowserService.setActive(true);
            }
        } else {
            stateMachine.disconnect();
            if (device.equals(getActiveDevice())) {
                setActiveDevice(null);
                BluetoothMediaBrowserService.setActive(false);
            }
        }
    }
@@ -558,6 +560,33 @@ public class AvrcpControllerService extends ProfileService {
        }
    }

    /**
     * Notify AVRCP Controller of an audio focus state change so we can make requests of the active
     * player to stop and start playing.
     */
    public void onAudioFocusStateChanged(int state) {
        if (DBG) {
            Log.d(TAG, "onAudioFocusStateChanged(state=" + state + ")");
        }

        // Make sure the active device isn't changed while we're processing the event so play/pause
        // commands get routed to the correct device
        synchronized (mActiveDeviceLock) {
            BluetoothDevice device = getActiveDevice();
            if (device == null) {
                Log.w(TAG, "No active device set, ignore focus change");
                return;
            }

            AvrcpControllerStateMachine stateMachine = mDeviceStateMap.get(device);
            if (stateMachine == null) {
                Log.w(TAG, "No state machine for active device.");
                return;
            }
            stateMachine.sendMessage(AvrcpControllerStateMachine.AUDIO_FOCUS_STATE_CHANGE, state);
        }
    }

    // Called by JNI when a track changes and local AvrcpController is registered for updates.
    private synchronized void onTrackChanged(byte[] address, byte numAttributes, int[] attributes,
            String[] attribVals) {
+78 −9
Original line number Diff line number Diff line
@@ -58,6 +58,7 @@ class AvrcpControllerStateMachine extends StateMachine {
    public static final int CONNECT = 1;
    public static final int DISCONNECT = 2;
    public static final int ACTIVE_DEVICE_CHANGE = 3;
    public static final int AUDIO_FOCUS_STATE_CHANGE = 4;

    //100->199 Internal Events
    protected static final int CLEANUP = 100;
@@ -110,6 +111,7 @@ class AvrcpControllerStateMachine extends StateMachine {

    private static BluetoothDevice sActiveDevice;
    private final AudioManager mAudioManager;
    private boolean mShouldSendPlayOnFocusRecovery = false;
    private final boolean mIsVolumeFixed;

    protected final BluetoothDevice mDevice;
@@ -480,9 +482,59 @@ class AvrcpControllerStateMachine extends StateMachine {
                        BluetoothMediaBrowserService.notifyChanged(
                                mAddressedPlayer.getPlaybackState());
                        BluetoothMediaBrowserService.notifyChanged(mBrowseTree.mNowPlayingNode);

                        // If we switch to a device that is playing and we don't have focus, pause
                        int focusState = getFocusState();
                        if (mAddressedPlayer.getPlaybackState().getState()
                                == PlaybackStateCompat.STATE_PLAYING
                                && focusState == AudioManager.AUDIOFOCUS_NONE) {
                            sendMessage(MSG_AVRCP_PASSTHRU,
                                    AvrcpControllerService.PASS_THRU_CMD_ID_PAUSE);
                        }
                    } else {
                        sendMessage(MSG_AVRCP_PASSTHRU,
                                AvrcpControllerService.PASS_THRU_CMD_ID_PAUSE);
                        mShouldSendPlayOnFocusRecovery = false;
                    }
                    return true;

                case AUDIO_FOCUS_STATE_CHANGE:
                    int newState = msg.arg1;
                    logD("Audio focus changed -> " + newState);
                    switch (newState) {
                        case AudioManager.AUDIOFOCUS_GAIN:
                            // Begin playing audio again if we paused the remote
                            if (mShouldSendPlayOnFocusRecovery) {
                                logD("Regained focus, establishing play status");
                                sendMessage(MSG_AVRCP_PASSTHRU,
                                        AvrcpControllerService.PASS_THRU_CMD_ID_PLAY);
                            }
                            mShouldSendPlayOnFocusRecovery = false;
                            break;

                        case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
                            // Temporary loss of focus. Send a courtesy pause if we are playing and
                            // note we should recover
                            if (mAddressedPlayer.getPlaybackState().getState()
                                    == PlaybackStateCompat.STATE_PLAYING) {
                                logD("Transient loss, temporarily pause with intent to recover");
                                sendMessage(MSG_AVRCP_PASSTHRU,
                                        AvrcpControllerService.PASS_THRU_CMD_ID_PAUSE);
                                mShouldSendPlayOnFocusRecovery = true;
                            }
                            break;

                        case AudioManager.AUDIOFOCUS_LOSS:
                            // Permanent loss of focus probably due to another audio app. Send a
                            // courtesy pause
                            logD("Lost focus, send a courtesy pause");
                            if (mAddressedPlayer.getPlaybackState().getState()
                                    == PlaybackStateCompat.STATE_PLAYING) {
                                sendMessage(MSG_AVRCP_PASSTHRU,
                                        AvrcpControllerService.PASS_THRU_CMD_ID_PAUSE);
                            }
                            mShouldSendPlayOnFocusRecovery = false;
                            break;
                    }
                    return true;

@@ -537,6 +589,7 @@ class AvrcpControllerStateMachine extends StateMachine {
                    return true;

                case MESSAGE_PROCESS_PLAY_STATUS_CHANGED:
                    logd("Playback status changed to " + msg.arg1);
                    mAddressedPlayer.setPlayStatus(msg.arg1);
                    if (!isActive()) {
                        sendMessage(MSG_AVRCP_PASSTHRU,
@@ -544,22 +597,17 @@ class AvrcpControllerStateMachine extends StateMachine {
                        return true;
                    }

                    PlaybackStateCompat playbackState = mAddressedPlayer.getPlaybackState();
                    BluetoothMediaBrowserService.notifyChanged(playbackState);

                    int focusState = AudioManager.ERROR;
                    A2dpSinkService a2dpSinkService = A2dpSinkService.getA2dpSinkService();
                    if (a2dpSinkService != null) {
                        focusState = a2dpSinkService.getFocusState();
                    }
                    BluetoothMediaBrowserService.notifyChanged(mAddressedPlayer.getPlaybackState());

                    int focusState = getFocusState();
                    if (focusState == AudioManager.ERROR) {
                        sendMessage(MSG_AVRCP_PASSTHRU,
                                AvrcpControllerService.PASS_THRU_CMD_ID_PAUSE);
                        return true;
                    }

                    if (playbackState.getState() == PlaybackStateCompat.STATE_PLAYING
                    if (mAddressedPlayer.getPlaybackState().getState()
                            == PlaybackStateCompat.STATE_PLAYING
                            && focusState == AudioManager.AUDIOFOCUS_NONE) {
                        if (shouldRequestFocus()) {
                            mSessionCallbacks.onPrepare();
@@ -1141,6 +1189,15 @@ class AvrcpControllerStateMachine extends StateMachine {
        }
    }

    private int getFocusState() {
        int focusState = AudioManager.ERROR;
        A2dpSinkService a2dpSinkService = A2dpSinkService.getA2dpSinkService();
        if (a2dpSinkService != null) {
            focusState = a2dpSinkService.getFocusState();
        }
        return focusState;
    }

    MediaSessionCompat.Callback mSessionCallbacks = new MediaSessionCompat.Callback() {
        @Override
        public void onPlay() {
@@ -1152,6 +1209,12 @@ class AvrcpControllerStateMachine extends StateMachine {
        @Override
        public void onPause() {
            logD("onPause");
            // If we receive a local pause/stop request and send it out then we need to signal that
            // the intent is to stay paused if we recover focus from a transient loss
            if (getFocusState() == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT) {
                logD("Received a pause while in a transient loss. Do not recover anymore.");
                mShouldSendPlayOnFocusRecovery = false;
            }
            sendMessage(MSG_AVRCP_PASSTHRU, AvrcpControllerService.PASS_THRU_CMD_ID_PAUSE);
        }

@@ -1182,6 +1245,12 @@ class AvrcpControllerStateMachine extends StateMachine {
        @Override
        public void onStop() {
            logD("onStop");
            // If we receive a local pause/stop request and send it out then we need to signal that
            // the intent is to stay paused if we recover focus from a transient loss
            if (getFocusState() == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT) {
                logD("Received a stop while in a transient loss. Do not recover anymore.");
                mShouldSendPlayOnFocusRecovery = false;
            }
            sendMessage(MSG_AVRCP_PASSTHRU, AvrcpControllerService.PASS_THRU_CMD_ID_STOP);
        }

+4 −10
Original line number Diff line number Diff line
@@ -233,20 +233,14 @@ public class A2dpSinkStreamHandlerTest {
    }

    @Test
    public void testFocusGainTransient() {
        // Focus was lost then regained.
        testSnkPlay();
        mStreamHandler.handleMessage(
                mStreamHandler.obtainMessage(A2dpSinkStreamHandler.AUDIO_FOCUS_CHANGE,
                        AudioManager.AUDIOFOCUS_LOSS_TRANSIENT));
        mStreamHandler.handleMessage(
                mStreamHandler.obtainMessage(A2dpSinkStreamHandler.DELAYED_PAUSE));
    public void testFocusGainFromTransientLoss() {
        // Focus was lost transiently and then regained.
        testFocusLostTransient();

        mStreamHandler.handleMessage(
                mStreamHandler.obtainMessage(A2dpSinkStreamHandler.AUDIO_FOCUS_CHANGE,
                        AudioManager.AUDIOFOCUS_GAIN));
        verify(mMockAudioManager, times(0)).abandonAudioFocus(any());
        verify(mMockNativeInterface, times(0)).informAudioFocusState(0);
        verify(mMockNativeInterface, times(1)).informAudioTrackGain(0);
        verify(mMockNativeInterface, times(2)).informAudioTrackGain(1.0f);

        TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper());
+173 −0
Original line number Diff line number Diff line
@@ -187,6 +187,15 @@ public class AvrcpControllerStateMachineTest {
        }
    }

    /**
     * Send an audio focus changed event to the state machine under test
     */
    private void sendAudioFocusUpdate(int state) {
        when(mA2dpSinkService.getFocusState()).thenReturn(state);
        mAvrcpStateMachine.sendMessage(
                AvrcpControllerStateMachine.AUDIO_FOCUS_STATE_CHANGE, state);
    }

    /**
     * Setup Connected State for a given state machine
     *
@@ -1362,4 +1371,168 @@ public class AvrcpControllerStateMachineTest {
        List<MediaSessionCompat.QueueItem> queue = controller.getQueue();
        Assert.assertNull(queue);
    }

    /**
     * Test receiving an audio focus changed event when we are not recovering from a transient loss.
     * This should result in no play command being sent.
     */
    @Test
    public void testOnAudioFocusGain_playNotSent() {
        setUpConnectedState(true, true);
        sendAudioFocusUpdate(AudioManager.AUDIOFOCUS_GAIN);
        TestUtils.waitForLooperToBeIdle(mAvrcpStateMachine.getHandler().getLooper());
        verify(mAvrcpControllerService, never()).sendPassThroughCommandNative(eq(mTestAddress),
                eq(AvrcpControllerService.PASS_THRU_CMD_ID_PAUSE), eq(KEY_DOWN));
        verify(mAvrcpControllerService,never()).sendPassThroughCommandNative(eq(mTestAddress),
                eq(AvrcpControllerService.PASS_THRU_CMD_ID_PAUSE), eq(KEY_UP));
    }

    /**
     * Test receiving a transient loss audio focus changed event while playing. A pause should be
     * sent
     */
    @Test
    public void testOnAudioFocusTransientLossWhilePlaying_pauseSent() {
        when(mMockResources.getBoolean(R.bool.a2dp_sink_automatically_request_audio_focus))
                .thenReturn(false);
        setUpConnectedState(true, true);
        sendAudioFocusUpdate(AudioManager.AUDIOFOCUS_GAIN);
        setPlaybackState(PlaybackStateCompat.STATE_PLAYING);
        sendAudioFocusUpdate(AudioManager.AUDIOFOCUS_LOSS_TRANSIENT);

        TestUtils.waitForLooperToBeIdle(mAvrcpStateMachine.getHandler().getLooper());
        verify(mAvrcpControllerService, times(1)).sendPassThroughCommandNative(eq(mTestAddress),
                eq(AvrcpControllerService.PASS_THRU_CMD_ID_PAUSE), eq(KEY_DOWN));
        verify(mAvrcpControllerService, times(1)).sendPassThroughCommandNative(eq(mTestAddress),
                eq(AvrcpControllerService.PASS_THRU_CMD_ID_PAUSE), eq(KEY_UP));
    }

    /**
     * Test receiving a transient loss audio focus changed event while paused. No pause should be
     * sent
     */
    @Test
    public void testOnAudioFocusTransientLossWhilePaused_pauseNotSent() {
        setUpConnectedState(true, true);
        sendAudioFocusUpdate(AudioManager.AUDIOFOCUS_GAIN);
        setPlaybackState(PlaybackStateCompat.STATE_PAUSED);
        sendAudioFocusUpdate(AudioManager.AUDIOFOCUS_LOSS_TRANSIENT);

        TestUtils.waitForLooperToBeIdle(mAvrcpStateMachine.getHandler().getLooper());
        verify(mAvrcpControllerService, never()).sendPassThroughCommandNative(eq(mTestAddress),
                eq(AvrcpControllerService.PASS_THRU_CMD_ID_PAUSE), eq(KEY_DOWN));
        verify(mAvrcpControllerService, never()).sendPassThroughCommandNative(eq(mTestAddress),
                eq(AvrcpControllerService.PASS_THRU_CMD_ID_PAUSE), eq(KEY_UP));
    }

    /**
     * Test receiving an audio focus loss event. A pause should be sent
     */
    @Test
    public void testOnAudioFocusLoss_pauseSent() {
        setUpConnectedState(true, true);
        sendAudioFocusUpdate(AudioManager.AUDIOFOCUS_GAIN);
        sendAudioFocusUpdate(AudioManager.AUDIOFOCUS_LOSS);

        TestUtils.waitForLooperToBeIdle(mAvrcpStateMachine.getHandler().getLooper());
        verify(mAvrcpControllerService, times(1)).sendPassThroughCommandNative(eq(mTestAddress),
                eq(AvrcpControllerService.PASS_THRU_CMD_ID_PAUSE), eq(KEY_DOWN));
        verify(mAvrcpControllerService, times(1)).sendPassThroughCommandNative(eq(mTestAddress),
                eq(AvrcpControllerService.PASS_THRU_CMD_ID_PAUSE), eq(KEY_UP));

    }

    /**
     * Test receiving an audio focus gained event following a transient loss where we sent a pause
     * and no event happened in between that should cause us to remain paused.
     */
    @Test
    public void testOnAudioFocusGainFromTransientLossWhilePlaying_playSent() {
        testOnAudioFocusTransientLossWhilePlaying_pauseSent();
        clearInvocations(mAvrcpControllerService);
        setPlaybackState(PlaybackStateCompat.STATE_PAUSED);
        sendAudioFocusUpdate(AudioManager.AUDIOFOCUS_GAIN);

        TestUtils.waitForLooperToBeIdle(mAvrcpStateMachine.getHandler().getLooper());
        verify(mAvrcpControllerService, times(1)).sendPassThroughCommandNative(eq(mTestAddress),
                eq(AvrcpControllerService.PASS_THRU_CMD_ID_PLAY), eq(KEY_DOWN));
        verify(mAvrcpControllerService, times(1)).sendPassThroughCommandNative(eq(mTestAddress),
                eq(AvrcpControllerService.PASS_THRU_CMD_ID_PLAY), eq(KEY_UP));
    }

    /**
     * Test receiving an audio focus changed event following a transient loss where
     */
    @Test
    public void testOnAudioFocusGainFromTransientLossWhilePlayingWithPause_playNotSent() {
        testOnAudioFocusTransientLossWhilePlaying_pauseSent();
        clearInvocations(mAvrcpControllerService);
        setPlaybackState(PlaybackStateCompat.STATE_PAUSED);
        TestUtils.waitForLooperToBeIdle(mAvrcpStateMachine.getHandler().getLooper());

        MediaControllerCompat.TransportControls transportControls =
                BluetoothMediaBrowserService.getTransportControls();
        transportControls.pause();
        verify(mAvrcpControllerService,
                timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(1)).sendPassThroughCommandNative(
                eq(mTestAddress), eq(AvrcpControllerService.PASS_THRU_CMD_ID_PAUSE), eq(KEY_DOWN));
        verify(mAvrcpControllerService,
                timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(1)).sendPassThroughCommandNative(
                eq(mTestAddress), eq(AvrcpControllerService.PASS_THRU_CMD_ID_PAUSE), eq(KEY_UP));

        sendAudioFocusUpdate(AudioManager.AUDIOFOCUS_GAIN);
        TestUtils.waitForLooperToBeIdle(mAvrcpStateMachine.getHandler().getLooper());

        verify(mAvrcpControllerService, never()).sendPassThroughCommandNative(eq(mTestAddress),
                eq(AvrcpControllerService.PASS_THRU_CMD_ID_PLAY), eq(KEY_DOWN));
        verify(mAvrcpControllerService, never()).sendPassThroughCommandNative(eq(mTestAddress),
                eq(AvrcpControllerService.PASS_THRU_CMD_ID_PLAY), eq(KEY_UP));
    }

    /**
     * Test receiving an audio focus gain event coming out of a transient loss where a stop command
     * has been sent
     */
    @Test
    public void testOnAudioFocusGainFromTransientLossWithStop_playNotSent() {
        testOnAudioFocusTransientLossWhilePlaying_pauseSent();
        clearInvocations(mAvrcpControllerService);
        setPlaybackState(PlaybackStateCompat.STATE_PAUSED);
        TestUtils.waitForLooperToBeIdle(mAvrcpStateMachine.getHandler().getLooper());

        MediaControllerCompat.TransportControls transportControls =
                BluetoothMediaBrowserService.getTransportControls();
        transportControls.stop();
        verify(mAvrcpControllerService,
                timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(1)).sendPassThroughCommandNative(
                eq(mTestAddress), eq(AvrcpControllerService.PASS_THRU_CMD_ID_STOP), eq(KEY_DOWN));
        verify(mAvrcpControllerService,
                timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(1)).sendPassThroughCommandNative(
                eq(mTestAddress), eq(AvrcpControllerService.PASS_THRU_CMD_ID_STOP), eq(KEY_UP));

        sendAudioFocusUpdate(AudioManager.AUDIOFOCUS_GAIN);
        TestUtils.waitForLooperToBeIdle(mAvrcpStateMachine.getHandler().getLooper());

        verify(mAvrcpControllerService, never()).sendPassThroughCommandNative(eq(mTestAddress),
                eq(AvrcpControllerService.PASS_THRU_CMD_ID_PLAY), eq(KEY_DOWN));
        verify(mAvrcpControllerService, never()).sendPassThroughCommandNative(eq(mTestAddress),
                eq(AvrcpControllerService.PASS_THRU_CMD_ID_PLAY), eq(KEY_UP));
    }

    /**
     * Test receiving an audio focus gain coming out of a transient loss where we were paused going
     * into the transient loss. No play should be sent because not play state needs to be recovered
     */
    @Test
    public void testOnAudioFocusGainFromTransientLossWhilePaused_playNotSent() {
        testOnAudioFocusTransientLossWhilePaused_pauseNotSent();
        clearInvocations(mAvrcpControllerService);
        sendAudioFocusUpdate(AudioManager.AUDIOFOCUS_GAIN);

        TestUtils.waitForLooperToBeIdle(mAvrcpStateMachine.getHandler().getLooper());
        verify(mAvrcpControllerService, never()).sendPassThroughCommandNative(eq(mTestAddress),
                eq(AvrcpControllerService.PASS_THRU_CMD_ID_PLAY), eq(KEY_DOWN));
        verify(mAvrcpControllerService, never()).sendPassThroughCommandNative(eq(mTestAddress),
                eq(AvrcpControllerService.PASS_THRU_CMD_ID_PLAY), eq(KEY_UP));
    }
}