Loading android/app/src/com/android/bluetooth/a2dpsink/A2dpSinkStreamHandler.java +11 −54 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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. Loading Loading @@ -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; Loading @@ -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; Loading Loading @@ -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: Loading @@ -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: Loading @@ -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; Loading Loading @@ -316,7 +299,6 @@ public class A2dpSinkStreamHandler extends Handler { } mMediaPlayer.start(); BluetoothMediaBrowserService.setActive(true); } private synchronized void abandonAudioFocus() { Loading @@ -335,7 +317,6 @@ public class A2dpSinkStreamHandler extends Handler { if (mMediaPlayer == null) { return; } BluetoothMediaBrowserService.setActive(false); mMediaPlayer.stop(); mMediaPlayer.release(); mMediaPlayer = null; Loading @@ -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); Loading android/app/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerService.java +29 −0 Original line number Diff line number Diff line Loading @@ -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); } } } Loading Loading @@ -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) { Loading android/app/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerStateMachine.java +78 −9 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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; Loading Loading @@ -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; Loading Loading @@ -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, Loading @@ -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(); Loading Loading @@ -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() { Loading @@ -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); } Loading Loading @@ -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); } Loading android/app/tests/unit/src/com/android/bluetooth/a2dpsink/A2dpSinkStreamHandlerTest.java +4 −10 Original line number Diff line number Diff line Loading @@ -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()); Loading android/app/tests/unit/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerStateMachineTest.java +173 −0 Original line number Diff line number Diff line Loading @@ -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 * Loading Loading @@ -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)); } } Loading
android/app/src/com/android/bluetooth/a2dpsink/A2dpSinkStreamHandler.java +11 −54 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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. Loading Loading @@ -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; Loading @@ -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; Loading Loading @@ -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: Loading @@ -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: Loading @@ -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; Loading Loading @@ -316,7 +299,6 @@ public class A2dpSinkStreamHandler extends Handler { } mMediaPlayer.start(); BluetoothMediaBrowserService.setActive(true); } private synchronized void abandonAudioFocus() { Loading @@ -335,7 +317,6 @@ public class A2dpSinkStreamHandler extends Handler { if (mMediaPlayer == null) { return; } BluetoothMediaBrowserService.setActive(false); mMediaPlayer.stop(); mMediaPlayer.release(); mMediaPlayer = null; Loading @@ -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); Loading
android/app/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerService.java +29 −0 Original line number Diff line number Diff line Loading @@ -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); } } } Loading Loading @@ -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) { Loading
android/app/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerStateMachine.java +78 −9 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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; Loading Loading @@ -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; Loading Loading @@ -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, Loading @@ -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(); Loading Loading @@ -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() { Loading @@ -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); } Loading Loading @@ -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); } Loading
android/app/tests/unit/src/com/android/bluetooth/a2dpsink/A2dpSinkStreamHandlerTest.java +4 −10 Original line number Diff line number Diff line Loading @@ -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()); Loading
android/app/tests/unit/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerStateMachineTest.java +173 −0 Original line number Diff line number Diff line Loading @@ -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 * Loading Loading @@ -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)); } }