Loading jni/com_android_bluetooth_a2dp_sink.cpp +24 −0 Original line number Diff line number Diff line Loading @@ -218,6 +218,29 @@ static void informAudioTrackGainNative(JNIEnv* env, jobject object, sBluetoothA2dpInterface->set_audio_track_gain((float)gain); } static jboolean setActiveDeviceNative(JNIEnv* env, jobject object, jbyteArray address) { if (!sBluetoothA2dpInterface) return JNI_FALSE; ALOGI("%s: sBluetoothA2dpInterface: %p", __func__, sBluetoothA2dpInterface); jbyte* addr = env->GetByteArrayElements(address, NULL); if (!addr) { jniThrowIOException(env, EINVAL); return JNI_FALSE; } RawAddress rawAddress; rawAddress.FromOctets((uint8_t*)addr); bt_status_t status = sBluetoothA2dpInterface->set_active_device(rawAddress); if (status != BT_STATUS_SUCCESS) { ALOGE("Failed sending passthru command, status: %d", status); } env->ReleaseByteArrayElements(address, addr, 0); return (status == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE; } static JNINativeMethod sMethods[] = { {"classInitNative", "()V", (void*)classInitNative}, {"initNative", "()V", (void*)initNative}, Loading @@ -226,6 +249,7 @@ static JNINativeMethod sMethods[] = { {"disconnectA2dpNative", "([B)Z", (void*)disconnectA2dpNative}, {"informAudioFocusStateNative", "(I)V", (void*)informAudioFocusStateNative}, {"informAudioTrackGainNative", "(F)V", (void*)informAudioTrackGainNative}, {"setActiveDeviceNative", "([B)Z", (void*)setActiveDeviceNative}, }; int register_com_android_bluetooth_a2dp_sink(JNIEnv* env) { Loading src/com/android/bluetooth/a2dpsink/A2dpSinkService.java +22 −0 Original line number Diff line number Diff line Loading @@ -75,6 +75,17 @@ public class A2dpSinkService extends ProfileService { return sService; } /** * Testing API to inject a mockA2dpSinkService. * @hide */ @VisibleForTesting public static void setA2dpSinkService(A2dpSinkService service) { sService = service; sService.mA2dpSinkStreamHandler = new A2dpSinkStreamHandler(sService, sService); } public A2dpSinkService() { mAdapter = BluetoothAdapter.getDefaultAdapter(); } Loading Loading @@ -404,6 +415,17 @@ public class A2dpSinkService extends ProfileService { native boolean disconnectA2dpNative(byte[] address); /** * set A2DP state machine as the active device * the active device is the only one that will receive passthrough commands and the only one * that will have its audio decoded * * @hide * @param address * @return active device request has been scheduled */ public native boolean setActiveDeviceNative(byte[] address); /** * inform A2DP decoder of the current audio focus * Loading src/com/android/bluetooth/avrcpcontroller/AvrcpControllerService.java +17 −0 Original line number Diff line number Diff line Loading @@ -149,6 +149,23 @@ public class AvrcpControllerService extends ProfileService { } } void playItem(String parentMediaId) { if (DBG) Log.d(TAG, "playItem(" + parentMediaId + ")"); // Check if the requestedNode is a player rather than a song BrowseTree.BrowseNode requestedNode = sBrowseTree.findBrowseNodeByID(parentMediaId); if (requestedNode == null) { for (AvrcpControllerStateMachine stateMachine : mDeviceStateMap.values()) { // Check each state machine for the song and then play it requestedNode = stateMachine.findNode(parentMediaId); if (requestedNode != null) { if (DBG) Log.d(TAG, "Found a node"); stateMachine.playItem(requestedNode); break; } } } } /*Java API*/ /** Loading src/com/android/bluetooth/avrcpcontroller/AvrcpControllerStateMachine.java +81 −16 Original line number Diff line number Diff line Loading @@ -39,6 +39,7 @@ import com.android.bluetooth.btservice.MetricsLogger; import com.android.bluetooth.btservice.ProfileService; import com.android.bluetooth.statemachine.State; import com.android.bluetooth.statemachine.StateMachine; import com.android.internal.annotations.VisibleForTesting; import java.util.ArrayList; import java.util.List; Loading Loading @@ -101,6 +102,7 @@ class AvrcpControllerStateMachine extends StateMachine { private static final byte NOTIFICATION_RSP_TYPE_INTERIM = 0x00; private static final byte NOTIFICATION_RSP_TYPE_CHANGED = 0x01; private static BluetoothDevice sActiveDevice; private final AudioManager mAudioManager; private final boolean mIsVolumeFixed; Loading Loading @@ -206,6 +208,42 @@ class AvrcpControllerStateMachine extends StateMachine { public void dump(StringBuilder sb) { ProfileService.println(sb, "mDevice: " + mDevice.getAddress() + "(" + mDevice.getName() + ") " + this.toString()); ProfileService.println(sb, "isActive: " + isActive()); } @VisibleForTesting boolean isActive() { return mDevice == sActiveDevice; } /* * requestActive * * Set the current device active if nothing an already connected device isn't playing */ private boolean requestActive() { if (sActiveDevice == null || BluetoothMediaBrowserService.getPlaybackState() != PlaybackStateCompat.STATE_PLAYING) { return setActive(); } return false; } /* * setActive * * Set this state machine as the active device and update media browse service */ private boolean setActive() { if (DBG) Log.d(TAG, "setActive" + mDevice); if (A2dpSinkService.getA2dpSinkService().setActiveDeviceNative(mDeviceAddress)) { sActiveDevice = mDevice; BluetoothMediaBrowserService.addressedPlayerChanged(mSessionCallbacks); BluetoothMediaBrowserService.notifyChanged(mAddressedPlayer.getPlaybackState()); BluetoothMediaBrowserService.notifyChanged(mBrowseTree.mNowPlayingNode); } return mDevice == sActiveDevice; } @Override Loading @@ -232,7 +270,9 @@ class AvrcpControllerStateMachine extends StateMachine { mAddressedPlayer.setPlayStatus(PlaybackStateCompat.STATE_ERROR); mAddressedPlayer.updateCurrentTrack(null); mBrowseTree.mNowPlayingNode.setCached(false); if (isActive()) { BluetoothMediaBrowserService.notifyChanged(mBrowseTree.mNowPlayingNode); } mService.sBrowseTree.mRootNode.removeChild( mBrowseTree.mRootNode); BluetoothMediaBrowserService.notifyChanged(mService Loading @@ -244,12 +284,21 @@ class AvrcpControllerStateMachine extends StateMachine { BluetoothMediaBrowserService.notifyChanged(node); } private void notifyChanged(PlaybackStateCompat state) { if (isActive()) { BluetoothMediaBrowserService.notifyChanged(state); } } void requestContents(BrowseTree.BrowseNode node) { sendMessage(MESSAGE_GET_FOLDER_ITEMS, node); logD("Fetching " + node); } public void playItem(BrowseTree.BrowseNode node) { sendMessage(MESSAGE_PLAY_ITEM, node); } void nowPlayingContentChanged() { mBrowseTree.mNowPlayingNode.setCached(false); sendMessage(MESSAGE_GET_FOLDER_ITEMS, mBrowseTree.mNowPlayingNode); Loading Loading @@ -297,6 +346,7 @@ class AvrcpControllerStateMachine extends StateMachine { @Override public void enter() { if (mMostRecentState == BluetoothProfile.STATE_CONNECTING) { requestActive(); BluetoothMediaBrowserService.addressedPlayerChanged(mSessionCallbacks); BluetoothMediaBrowserService.notifyChanged(mAddressedPlayer.getPlaybackState()); broadcastConnectionStateChanged(BluetoothProfile.STATE_CONNECTED); Loading Loading @@ -331,7 +381,7 @@ class AvrcpControllerStateMachine extends StateMachine { case MESSAGE_PLAY_ITEM: //Set Addressed Player playItem((BrowseTree.BrowseNode) msg.obj); processPlayItem((BrowseTree.BrowseNode) msg.obj); return true; case MSG_AVRCP_PASSTHRU: Loading @@ -348,12 +398,15 @@ class AvrcpControllerStateMachine extends StateMachine { case MESSAGE_PROCESS_TRACK_CHANGED: mAddressedPlayer.updateCurrentTrack((MediaMetadata) msg.obj); if (isActive()) { BluetoothMediaBrowserService.trackChanged((MediaMetadata) msg.obj); } return true; case MESSAGE_PROCESS_PLAY_STATUS_CHANGED: mAddressedPlayer.setPlayStatus(msg.arg1); BluetoothMediaBrowserService.notifyChanged(mAddressedPlayer.getPlaybackState()); BluetoothMediaBrowserService.notifyChanged( mAddressedPlayer.getPlaybackState()); if (mAddressedPlayer.getPlaybackState().getState() == PlaybackStateCompat.STATE_PLAYING && A2dpSinkService.getFocusState() == AudioManager.AUDIOFOCUS_NONE) { Loading @@ -369,10 +422,11 @@ class AvrcpControllerStateMachine extends StateMachine { case MESSAGE_PROCESS_PLAY_POS_CHANGED: if (msg.arg2 != -1) { mAddressedPlayer.setPlayTime(msg.arg2); if (isActive()) { BluetoothMediaBrowserService.notifyChanged( mAddressedPlayer.getPlaybackState()); } } return true; case MESSAGE_PROCESS_ADDRESSED_PLAYER_CHANGED: Loading @@ -392,13 +446,13 @@ class AvrcpControllerStateMachine extends StateMachine { case MESSAGE_PROCESS_SUPPORTED_APPLICATION_SETTINGS: mAddressedPlayer.setSupportedPlayerApplicationSettings( (PlayerApplicationSettings) msg.obj); BluetoothMediaBrowserService.notifyChanged(mAddressedPlayer.getPlaybackState()); notifyChanged(mAddressedPlayer.getPlaybackState()); return true; case MESSAGE_PROCESS_CURRENT_APPLICATION_SETTINGS: mAddressedPlayer.setCurrentPlayerApplicationSettings( (PlayerApplicationSettings) msg.obj); BluetoothMediaBrowserService.notifyChanged(mAddressedPlayer.getPlaybackState()); notifyChanged(mAddressedPlayer.getPlaybackState()); return true; case MESSAGE_PROCESS_AVAILABLE_PLAYER_CHANGED: Loading @@ -415,7 +469,8 @@ class AvrcpControllerStateMachine extends StateMachine { } private void playItem(BrowseTree.BrowseNode node) { private void processPlayItem(BrowseTree.BrowseNode node) { setActive(); if (node == null) { Log.w(TAG, "Invalid item to play"); } else { Loading Loading @@ -722,8 +777,11 @@ class AvrcpControllerStateMachine extends StateMachine { @Override public void enter() { onBrowsingDisconnected(); if (isActive()) { sActiveDevice = null; BluetoothMediaBrowserService.trackChanged(null); BluetoothMediaBrowserService.addressedPlayerChanged(null); } broadcastConnectionStateChanged(BluetoothProfile.STATE_DISCONNECTING); transitionTo(mDisconnected); } Loading Loading @@ -856,7 +914,14 @@ class AvrcpControllerStateMachine extends StateMachine { // Play the item if possible. onPrepare(); BrowseTree.BrowseNode node = mBrowseTree.findBrowseNodeByID(mediaId); if (node != null) { // node was found on this bluetooth device sendMessage(MESSAGE_PLAY_ITEM, node); } else { // node was not found on this device, pause here, and play on another device sendMessage(MSG_AVRCP_PASSTHRU, AvrcpControllerService.PASS_THRU_CMD_ID_PAUSE); mService.playItem(mediaId); } } @Override Loading tests/unit/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerStateMachineTest.java +46 −6 Original line number Diff line number Diff line Loading @@ -63,8 +63,8 @@ public class AvrcpControllerStateMachineTest { private static final int CONNECT_TIMEOUT_TEST_MILLIS = 1000; private static final int KEY_DOWN = 0; private static final int KEY_UP = 1; private AvrcpControllerStateMachine mAvrcpStateMachine; private BluetoothAdapter mAdapter; private AvrcpControllerStateMachine mAvrcpControllerStateMachine; private Context mTargetContext; private BluetoothDevice mTestDevice; private ArgumentCaptor<Intent> mIntentArgument = ArgumentCaptor.forClass(Intent.class); Loading @@ -83,11 +83,12 @@ public class AvrcpControllerStateMachineTest { private AudioManager mAudioManager; @Mock private AvrcpControllerService mAvrcpControllerService; @Mock private A2dpSinkService mA2dpSinkService; @Mock private Resources mMockResources; AvrcpControllerStateMachine mAvrcpStateMachine; @Before public void setUp() throws Exception { Loading @@ -107,9 +108,12 @@ public class AvrcpControllerStateMachineTest { TestUtils.clearAdapterService(mAvrcpAdapterService); TestUtils.setAdapterService(mA2dpAdapterService); TestUtils.startService(mA2dpServiceRule, A2dpSinkService.class); when(mA2dpSinkService.setActiveDeviceNative(any())).thenReturn(true); when(mMockResources.getBoolean(R.bool.a2dp_sink_automatically_request_audio_focus)) .thenReturn(true); doReturn(mMockResources).when(mAvrcpControllerService).getResources(); A2dpSinkService.setA2dpSinkService(mA2dpSinkService); doReturn(15).when(mAudioManager).getStreamMaxVolume(anyInt()); doReturn(8).when(mAudioManager).getStreamVolume(anyInt()); doReturn(true).when(mAudioManager).isVolumeFixed(); Loading Loading @@ -247,7 +251,8 @@ public class AvrcpControllerStateMachineTest { mAvrcpStateMachine.dump(sb); Assert.assertEquals(sb.toString(), " mDevice: " + mTestDevice.toString() + "(null) name=AvrcpControllerStateMachine state=(null)\n"); + "(null) name=AvrcpControllerStateMachine state=(null)\n" + " isActive: false\n"); } /** Loading Loading @@ -625,7 +630,7 @@ public class AvrcpControllerStateMachineTest { eq(mTestAddress), eq(AvrcpControllerService.PASS_THRU_CMD_ID_PAUSE), eq(KEY_DOWN)); TestUtils.waitForLooperToFinishScheduledTask( A2dpSinkService.getA2dpSinkService().getMainLooper()); Assert.assertEquals(AudioManager.AUDIOFOCUS_NONE, A2dpSinkService.getFocusState()); Assert.assertEquals(AudioManager.AUDIOFOCUS_NONE, mA2dpSinkService.getFocusState()); } /** Loading @@ -641,8 +646,43 @@ public class AvrcpControllerStateMachineTest { PlaybackStateCompat.STATE_PLAYING); TestUtils.waitForLooperToFinishScheduledTask(mAvrcpStateMachine.getHandler().getLooper()); TestUtils.waitForLooperToFinishScheduledTask( A2dpSinkService.getA2dpSinkService().getMainLooper()); Assert.assertEquals(AudioManager.AUDIOFOCUS_GAIN, A2dpSinkService.getFocusState()); mA2dpSinkService.getMainLooper()); verify(mA2dpSinkService).requestAudioFocus(mTestDevice, true); } /** * Test that the correct device becomes active * * The first connected device is automatically active, additional ones are not. * After an explicit play command a device becomes active. */ @Test public void testActiveDeviceManagement() { // Setup structures and verify initial conditions final String rootName = "__ROOT__"; final String playerName = "Player 1"; byte[] secondTestAddress = new byte[]{00, 01, 02, 03, 04, 06}; BluetoothDevice secondTestDevice = mAdapter.getRemoteDevice(secondTestAddress); AvrcpControllerStateMachine secondAvrcpStateMachine = new AvrcpControllerStateMachine(secondTestDevice, mAvrcpControllerService); secondAvrcpStateMachine.start(); Assert.assertFalse(mAvrcpStateMachine.isActive()); // Connect device 1 and 2 and verify first one is set as active setUpConnectedState(true, true); secondAvrcpStateMachine.connect(StackEvent.connectionStateChanged(true, true)); Assert.assertTrue(mAvrcpStateMachine.isActive()); Assert.assertFalse(secondAvrcpStateMachine.isActive()); // Request the second device to play an item and verify active device switched BrowseTree.BrowseNode results = mAvrcpStateMachine.findNode(rootName); Assert.assertEquals(rootName + mTestDevice.toString(), results.getID()); BrowseTree.BrowseNode playerNodes = mAvrcpStateMachine.findNode(results.getID()); secondAvrcpStateMachine.playItem(playerNodes); TestUtils.waitForLooperToFinishScheduledTask(secondAvrcpStateMachine.getHandler() .getLooper()); Assert.assertFalse(mAvrcpStateMachine.isActive()); Assert.assertTrue(secondAvrcpStateMachine.isActive()); } /** Loading Loading
jni/com_android_bluetooth_a2dp_sink.cpp +24 −0 Original line number Diff line number Diff line Loading @@ -218,6 +218,29 @@ static void informAudioTrackGainNative(JNIEnv* env, jobject object, sBluetoothA2dpInterface->set_audio_track_gain((float)gain); } static jboolean setActiveDeviceNative(JNIEnv* env, jobject object, jbyteArray address) { if (!sBluetoothA2dpInterface) return JNI_FALSE; ALOGI("%s: sBluetoothA2dpInterface: %p", __func__, sBluetoothA2dpInterface); jbyte* addr = env->GetByteArrayElements(address, NULL); if (!addr) { jniThrowIOException(env, EINVAL); return JNI_FALSE; } RawAddress rawAddress; rawAddress.FromOctets((uint8_t*)addr); bt_status_t status = sBluetoothA2dpInterface->set_active_device(rawAddress); if (status != BT_STATUS_SUCCESS) { ALOGE("Failed sending passthru command, status: %d", status); } env->ReleaseByteArrayElements(address, addr, 0); return (status == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE; } static JNINativeMethod sMethods[] = { {"classInitNative", "()V", (void*)classInitNative}, {"initNative", "()V", (void*)initNative}, Loading @@ -226,6 +249,7 @@ static JNINativeMethod sMethods[] = { {"disconnectA2dpNative", "([B)Z", (void*)disconnectA2dpNative}, {"informAudioFocusStateNative", "(I)V", (void*)informAudioFocusStateNative}, {"informAudioTrackGainNative", "(F)V", (void*)informAudioTrackGainNative}, {"setActiveDeviceNative", "([B)Z", (void*)setActiveDeviceNative}, }; int register_com_android_bluetooth_a2dp_sink(JNIEnv* env) { Loading
src/com/android/bluetooth/a2dpsink/A2dpSinkService.java +22 −0 Original line number Diff line number Diff line Loading @@ -75,6 +75,17 @@ public class A2dpSinkService extends ProfileService { return sService; } /** * Testing API to inject a mockA2dpSinkService. * @hide */ @VisibleForTesting public static void setA2dpSinkService(A2dpSinkService service) { sService = service; sService.mA2dpSinkStreamHandler = new A2dpSinkStreamHandler(sService, sService); } public A2dpSinkService() { mAdapter = BluetoothAdapter.getDefaultAdapter(); } Loading Loading @@ -404,6 +415,17 @@ public class A2dpSinkService extends ProfileService { native boolean disconnectA2dpNative(byte[] address); /** * set A2DP state machine as the active device * the active device is the only one that will receive passthrough commands and the only one * that will have its audio decoded * * @hide * @param address * @return active device request has been scheduled */ public native boolean setActiveDeviceNative(byte[] address); /** * inform A2DP decoder of the current audio focus * Loading
src/com/android/bluetooth/avrcpcontroller/AvrcpControllerService.java +17 −0 Original line number Diff line number Diff line Loading @@ -149,6 +149,23 @@ public class AvrcpControllerService extends ProfileService { } } void playItem(String parentMediaId) { if (DBG) Log.d(TAG, "playItem(" + parentMediaId + ")"); // Check if the requestedNode is a player rather than a song BrowseTree.BrowseNode requestedNode = sBrowseTree.findBrowseNodeByID(parentMediaId); if (requestedNode == null) { for (AvrcpControllerStateMachine stateMachine : mDeviceStateMap.values()) { // Check each state machine for the song and then play it requestedNode = stateMachine.findNode(parentMediaId); if (requestedNode != null) { if (DBG) Log.d(TAG, "Found a node"); stateMachine.playItem(requestedNode); break; } } } } /*Java API*/ /** Loading
src/com/android/bluetooth/avrcpcontroller/AvrcpControllerStateMachine.java +81 −16 Original line number Diff line number Diff line Loading @@ -39,6 +39,7 @@ import com.android.bluetooth.btservice.MetricsLogger; import com.android.bluetooth.btservice.ProfileService; import com.android.bluetooth.statemachine.State; import com.android.bluetooth.statemachine.StateMachine; import com.android.internal.annotations.VisibleForTesting; import java.util.ArrayList; import java.util.List; Loading Loading @@ -101,6 +102,7 @@ class AvrcpControllerStateMachine extends StateMachine { private static final byte NOTIFICATION_RSP_TYPE_INTERIM = 0x00; private static final byte NOTIFICATION_RSP_TYPE_CHANGED = 0x01; private static BluetoothDevice sActiveDevice; private final AudioManager mAudioManager; private final boolean mIsVolumeFixed; Loading Loading @@ -206,6 +208,42 @@ class AvrcpControllerStateMachine extends StateMachine { public void dump(StringBuilder sb) { ProfileService.println(sb, "mDevice: " + mDevice.getAddress() + "(" + mDevice.getName() + ") " + this.toString()); ProfileService.println(sb, "isActive: " + isActive()); } @VisibleForTesting boolean isActive() { return mDevice == sActiveDevice; } /* * requestActive * * Set the current device active if nothing an already connected device isn't playing */ private boolean requestActive() { if (sActiveDevice == null || BluetoothMediaBrowserService.getPlaybackState() != PlaybackStateCompat.STATE_PLAYING) { return setActive(); } return false; } /* * setActive * * Set this state machine as the active device and update media browse service */ private boolean setActive() { if (DBG) Log.d(TAG, "setActive" + mDevice); if (A2dpSinkService.getA2dpSinkService().setActiveDeviceNative(mDeviceAddress)) { sActiveDevice = mDevice; BluetoothMediaBrowserService.addressedPlayerChanged(mSessionCallbacks); BluetoothMediaBrowserService.notifyChanged(mAddressedPlayer.getPlaybackState()); BluetoothMediaBrowserService.notifyChanged(mBrowseTree.mNowPlayingNode); } return mDevice == sActiveDevice; } @Override Loading @@ -232,7 +270,9 @@ class AvrcpControllerStateMachine extends StateMachine { mAddressedPlayer.setPlayStatus(PlaybackStateCompat.STATE_ERROR); mAddressedPlayer.updateCurrentTrack(null); mBrowseTree.mNowPlayingNode.setCached(false); if (isActive()) { BluetoothMediaBrowserService.notifyChanged(mBrowseTree.mNowPlayingNode); } mService.sBrowseTree.mRootNode.removeChild( mBrowseTree.mRootNode); BluetoothMediaBrowserService.notifyChanged(mService Loading @@ -244,12 +284,21 @@ class AvrcpControllerStateMachine extends StateMachine { BluetoothMediaBrowserService.notifyChanged(node); } private void notifyChanged(PlaybackStateCompat state) { if (isActive()) { BluetoothMediaBrowserService.notifyChanged(state); } } void requestContents(BrowseTree.BrowseNode node) { sendMessage(MESSAGE_GET_FOLDER_ITEMS, node); logD("Fetching " + node); } public void playItem(BrowseTree.BrowseNode node) { sendMessage(MESSAGE_PLAY_ITEM, node); } void nowPlayingContentChanged() { mBrowseTree.mNowPlayingNode.setCached(false); sendMessage(MESSAGE_GET_FOLDER_ITEMS, mBrowseTree.mNowPlayingNode); Loading Loading @@ -297,6 +346,7 @@ class AvrcpControllerStateMachine extends StateMachine { @Override public void enter() { if (mMostRecentState == BluetoothProfile.STATE_CONNECTING) { requestActive(); BluetoothMediaBrowserService.addressedPlayerChanged(mSessionCallbacks); BluetoothMediaBrowserService.notifyChanged(mAddressedPlayer.getPlaybackState()); broadcastConnectionStateChanged(BluetoothProfile.STATE_CONNECTED); Loading Loading @@ -331,7 +381,7 @@ class AvrcpControllerStateMachine extends StateMachine { case MESSAGE_PLAY_ITEM: //Set Addressed Player playItem((BrowseTree.BrowseNode) msg.obj); processPlayItem((BrowseTree.BrowseNode) msg.obj); return true; case MSG_AVRCP_PASSTHRU: Loading @@ -348,12 +398,15 @@ class AvrcpControllerStateMachine extends StateMachine { case MESSAGE_PROCESS_TRACK_CHANGED: mAddressedPlayer.updateCurrentTrack((MediaMetadata) msg.obj); if (isActive()) { BluetoothMediaBrowserService.trackChanged((MediaMetadata) msg.obj); } return true; case MESSAGE_PROCESS_PLAY_STATUS_CHANGED: mAddressedPlayer.setPlayStatus(msg.arg1); BluetoothMediaBrowserService.notifyChanged(mAddressedPlayer.getPlaybackState()); BluetoothMediaBrowserService.notifyChanged( mAddressedPlayer.getPlaybackState()); if (mAddressedPlayer.getPlaybackState().getState() == PlaybackStateCompat.STATE_PLAYING && A2dpSinkService.getFocusState() == AudioManager.AUDIOFOCUS_NONE) { Loading @@ -369,10 +422,11 @@ class AvrcpControllerStateMachine extends StateMachine { case MESSAGE_PROCESS_PLAY_POS_CHANGED: if (msg.arg2 != -1) { mAddressedPlayer.setPlayTime(msg.arg2); if (isActive()) { BluetoothMediaBrowserService.notifyChanged( mAddressedPlayer.getPlaybackState()); } } return true; case MESSAGE_PROCESS_ADDRESSED_PLAYER_CHANGED: Loading @@ -392,13 +446,13 @@ class AvrcpControllerStateMachine extends StateMachine { case MESSAGE_PROCESS_SUPPORTED_APPLICATION_SETTINGS: mAddressedPlayer.setSupportedPlayerApplicationSettings( (PlayerApplicationSettings) msg.obj); BluetoothMediaBrowserService.notifyChanged(mAddressedPlayer.getPlaybackState()); notifyChanged(mAddressedPlayer.getPlaybackState()); return true; case MESSAGE_PROCESS_CURRENT_APPLICATION_SETTINGS: mAddressedPlayer.setCurrentPlayerApplicationSettings( (PlayerApplicationSettings) msg.obj); BluetoothMediaBrowserService.notifyChanged(mAddressedPlayer.getPlaybackState()); notifyChanged(mAddressedPlayer.getPlaybackState()); return true; case MESSAGE_PROCESS_AVAILABLE_PLAYER_CHANGED: Loading @@ -415,7 +469,8 @@ class AvrcpControllerStateMachine extends StateMachine { } private void playItem(BrowseTree.BrowseNode node) { private void processPlayItem(BrowseTree.BrowseNode node) { setActive(); if (node == null) { Log.w(TAG, "Invalid item to play"); } else { Loading Loading @@ -722,8 +777,11 @@ class AvrcpControllerStateMachine extends StateMachine { @Override public void enter() { onBrowsingDisconnected(); if (isActive()) { sActiveDevice = null; BluetoothMediaBrowserService.trackChanged(null); BluetoothMediaBrowserService.addressedPlayerChanged(null); } broadcastConnectionStateChanged(BluetoothProfile.STATE_DISCONNECTING); transitionTo(mDisconnected); } Loading Loading @@ -856,7 +914,14 @@ class AvrcpControllerStateMachine extends StateMachine { // Play the item if possible. onPrepare(); BrowseTree.BrowseNode node = mBrowseTree.findBrowseNodeByID(mediaId); if (node != null) { // node was found on this bluetooth device sendMessage(MESSAGE_PLAY_ITEM, node); } else { // node was not found on this device, pause here, and play on another device sendMessage(MSG_AVRCP_PASSTHRU, AvrcpControllerService.PASS_THRU_CMD_ID_PAUSE); mService.playItem(mediaId); } } @Override Loading
tests/unit/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerStateMachineTest.java +46 −6 Original line number Diff line number Diff line Loading @@ -63,8 +63,8 @@ public class AvrcpControllerStateMachineTest { private static final int CONNECT_TIMEOUT_TEST_MILLIS = 1000; private static final int KEY_DOWN = 0; private static final int KEY_UP = 1; private AvrcpControllerStateMachine mAvrcpStateMachine; private BluetoothAdapter mAdapter; private AvrcpControllerStateMachine mAvrcpControllerStateMachine; private Context mTargetContext; private BluetoothDevice mTestDevice; private ArgumentCaptor<Intent> mIntentArgument = ArgumentCaptor.forClass(Intent.class); Loading @@ -83,11 +83,12 @@ public class AvrcpControllerStateMachineTest { private AudioManager mAudioManager; @Mock private AvrcpControllerService mAvrcpControllerService; @Mock private A2dpSinkService mA2dpSinkService; @Mock private Resources mMockResources; AvrcpControllerStateMachine mAvrcpStateMachine; @Before public void setUp() throws Exception { Loading @@ -107,9 +108,12 @@ public class AvrcpControllerStateMachineTest { TestUtils.clearAdapterService(mAvrcpAdapterService); TestUtils.setAdapterService(mA2dpAdapterService); TestUtils.startService(mA2dpServiceRule, A2dpSinkService.class); when(mA2dpSinkService.setActiveDeviceNative(any())).thenReturn(true); when(mMockResources.getBoolean(R.bool.a2dp_sink_automatically_request_audio_focus)) .thenReturn(true); doReturn(mMockResources).when(mAvrcpControllerService).getResources(); A2dpSinkService.setA2dpSinkService(mA2dpSinkService); doReturn(15).when(mAudioManager).getStreamMaxVolume(anyInt()); doReturn(8).when(mAudioManager).getStreamVolume(anyInt()); doReturn(true).when(mAudioManager).isVolumeFixed(); Loading Loading @@ -247,7 +251,8 @@ public class AvrcpControllerStateMachineTest { mAvrcpStateMachine.dump(sb); Assert.assertEquals(sb.toString(), " mDevice: " + mTestDevice.toString() + "(null) name=AvrcpControllerStateMachine state=(null)\n"); + "(null) name=AvrcpControllerStateMachine state=(null)\n" + " isActive: false\n"); } /** Loading Loading @@ -625,7 +630,7 @@ public class AvrcpControllerStateMachineTest { eq(mTestAddress), eq(AvrcpControllerService.PASS_THRU_CMD_ID_PAUSE), eq(KEY_DOWN)); TestUtils.waitForLooperToFinishScheduledTask( A2dpSinkService.getA2dpSinkService().getMainLooper()); Assert.assertEquals(AudioManager.AUDIOFOCUS_NONE, A2dpSinkService.getFocusState()); Assert.assertEquals(AudioManager.AUDIOFOCUS_NONE, mA2dpSinkService.getFocusState()); } /** Loading @@ -641,8 +646,43 @@ public class AvrcpControllerStateMachineTest { PlaybackStateCompat.STATE_PLAYING); TestUtils.waitForLooperToFinishScheduledTask(mAvrcpStateMachine.getHandler().getLooper()); TestUtils.waitForLooperToFinishScheduledTask( A2dpSinkService.getA2dpSinkService().getMainLooper()); Assert.assertEquals(AudioManager.AUDIOFOCUS_GAIN, A2dpSinkService.getFocusState()); mA2dpSinkService.getMainLooper()); verify(mA2dpSinkService).requestAudioFocus(mTestDevice, true); } /** * Test that the correct device becomes active * * The first connected device is automatically active, additional ones are not. * After an explicit play command a device becomes active. */ @Test public void testActiveDeviceManagement() { // Setup structures and verify initial conditions final String rootName = "__ROOT__"; final String playerName = "Player 1"; byte[] secondTestAddress = new byte[]{00, 01, 02, 03, 04, 06}; BluetoothDevice secondTestDevice = mAdapter.getRemoteDevice(secondTestAddress); AvrcpControllerStateMachine secondAvrcpStateMachine = new AvrcpControllerStateMachine(secondTestDevice, mAvrcpControllerService); secondAvrcpStateMachine.start(); Assert.assertFalse(mAvrcpStateMachine.isActive()); // Connect device 1 and 2 and verify first one is set as active setUpConnectedState(true, true); secondAvrcpStateMachine.connect(StackEvent.connectionStateChanged(true, true)); Assert.assertTrue(mAvrcpStateMachine.isActive()); Assert.assertFalse(secondAvrcpStateMachine.isActive()); // Request the second device to play an item and verify active device switched BrowseTree.BrowseNode results = mAvrcpStateMachine.findNode(rootName); Assert.assertEquals(rootName + mTestDevice.toString(), results.getID()); BrowseTree.BrowseNode playerNodes = mAvrcpStateMachine.findNode(results.getID()); secondAvrcpStateMachine.playItem(playerNodes); TestUtils.waitForLooperToFinishScheduledTask(secondAvrcpStateMachine.getHandler() .getLooper()); Assert.assertFalse(mAvrcpStateMachine.isActive()); Assert.assertTrue(secondAvrcpStateMachine.isActive()); } /** Loading