Play a short/silent WAV file to ensure we get routed media key events
Bug: b/112431289
Test: Pair BT phone and play music. Use adb shell input keyevent
to send all of 126/play, 127/pause, 86/stop, 88/prev, 87/next,
164/mute, 25/volume-down, 24/volume-up. Stream and functions behave as
expected. Additionally, check that dumpsys media_session shows bluetooth
as both the MediaButtonSession AND a valid audio playback source.
Change-Id: Ic9d096f4b372f3fd45448d01d953e8d38e7b2278
Merged-In: Ic9d096f4b372f3fd45448d01d953e8d38e7b2278
diff --git a/res/raw/silent.wav b/res/raw/silent.wav
new file mode 100755
index 0000000..d6d93ef
--- /dev/null
+++ b/res/raw/silent.wav
Binary files differ
diff --git a/src/com/android/bluetooth/avrcpcontroller/BluetoothMediaBrowserService.java b/src/com/android/bluetooth/avrcpcontroller/BluetoothMediaBrowserService.java
index 323dbfb..4dfb8d4 100644
--- a/src/com/android/bluetooth/avrcpcontroller/BluetoothMediaBrowserService.java
+++ b/src/com/android/bluetooth/avrcpcontroller/BluetoothMediaBrowserService.java
@@ -23,7 +23,10 @@
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.media.AudioAttributes;
+import android.media.AudioManager;
import android.media.MediaMetadata;
+import android.media.MediaPlayer;
import android.media.browse.MediaBrowser;
import android.media.browse.MediaBrowser.MediaItem;
import android.media.session.MediaController;
@@ -95,8 +98,16 @@
"com.android.bluetooth.avrcpcontroller.CUSTOM_ACTION_GET_PLAY_STATUS_NATIVE";
private static BluetoothMediaBrowserService sBluetoothMediaBrowserService;
+
+ // In order to be considered as an audio source capable of receiving media key events (In the
+ // eyes of MediaSessionService), we need an active MediaPlayer in addition to a MediaSession.
+ // Because of this, the media player below plays an incredibly short, silent audio sample so
+ // that MediaSessionService and AudioPlaybackStateMonitor will believe that we're the current
+ // active player and send us media events. This is a restriction currently imposed by the media
+ // framework code and could be reconsidered in the future.
private MediaSession mSession;
private MediaMetadata mA2dpMetadata;
+ private MediaPlayer mMediaPlayer;
private AvrcpControllerService mAvrcpCtrlSrvc;
private boolean mBrowseConnected = false;
@@ -158,23 +169,37 @@
}
}
+ /**
+ * Initialize this BluetoothMediaBrowserService, creating our MediaSession, MediaPlayer and
+ * MediaMetaData, and setting up mechanisms to talk with the AvrcpControllerService.
+ */
@Override
public void onCreate() {
if (DBG) Log.d(TAG, "onCreate");
super.onCreate();
+ // Create and configure the MediaSession
mSession = new MediaSession(this, TAG);
- setSessionToken(mSession.getSessionToken());
mSession.setCallback(mSessionCallbacks);
mSession.setFlags(MediaSession.FLAG_HANDLES_MEDIA_BUTTONS
| MediaSession.FLAG_HANDLES_TRANSPORT_CONTROLS);
- mSession.setActive(true);
mSession.setQueueTitle(getString(R.string.bluetooth_a2dp_sink_queue_name));
mSession.setQueue(mMediaQueue);
+
+ // Create and setup the MediaPlayer
+ initMediaPlayer();
+
+ // Associate the held MediaSession with this browser and activate it
+ setSessionToken(mSession.getSessionToken());
+ mSession.setActive(true);
+
+ // Internal handler to process events and requests
mAvrcpCommandQueue = new AvrcpCommandQueueHandler(Looper.getMainLooper(), this);
+ // Set the initial Media state (sets current playback state and media meta data)
refreshInitialPlayingState();
+ // Set up communication with the controller service
IntentFilter filter = new IntentFilter();
filter.addAction(BluetoothAvrcpController.ACTION_CONNECTION_STATE_CHANGED);
filter.addAction(AvrcpControllerService.ACTION_BROWSE_CONNECTION_STATE_CHANGED);
@@ -185,19 +210,82 @@
synchronized (this) {
mParentIdToRequestMap.clear();
}
- setBluetoothMediaBrowserService(this);
+ setBluetoothMediaBrowserService(this);
}
+ /**
+ * Clean up this instance in the reverse order that we created it.
+ */
@Override
public void onDestroy() {
if (DBG) Log.d(TAG, "onDestroy");
setBluetoothMediaBrowserService(null);
- mSession.release();
unregisterReceiver(mBtReceiver);
+ destroyMediaPlayer();
+ mSession.release();
super.onDestroy();
}
+ /**
+ * Initializes the silent MediaPlayer object which aids in receiving media key focus.
+ *
+ * The created MediaPlayer is already prepared and will release and stop itself on error. All
+ * you need to do is start() it.
+ */
+ private void initMediaPlayer() {
+ if (DBG) Log.d(TAG, "initMediaPlayer()");
+
+ // Parameters for create
+ AudioAttributes attrs = new AudioAttributes.Builder()
+ .setUsage(AudioAttributes.USAGE_MEDIA)
+ .build();
+ AudioManager am = getSystemService(AudioManager.class);
+
+ // Create our player object. Returns a prepared player on success, null on failure
+ mMediaPlayer = MediaPlayer.create(this, R.raw.silent, attrs, am.generateAudioSessionId());
+ if (mMediaPlayer == null) {
+ Log.e(TAG, "Failed to initialize media player. You may not get media key events");
+ return;
+ }
+
+ // Set other player attributes
+ mMediaPlayer.setLooping(false);
+ mMediaPlayer.setOnErrorListener((mp, what, extra) -> {
+ Log.e(TAG, "Silent media player error: " + what + ", " + extra);
+ destroyMediaPlayer();
+ return false;
+ });
+ }
+
+ /**
+ * Safely tears down our local MediaPlayer
+ */
+ private void destroyMediaPlayer() {
+ if (DBG) Log.d(TAG, "destroyMediaPlayer()");
+ if (mMediaPlayer == null) {
+ return;
+ }
+ mMediaPlayer.stop();
+ mMediaPlayer.release();
+ mMediaPlayer = null;
+ }
+
+ /**
+ * Uses the internal MediaPlayer to play a silent, short audio sample so that AudioService will
+ * treat us as the active MediaSession/MediaPlayer combo and properly route us media key events.
+ *
+ * If the MediaPlayer failed to initialize properly, this call will fail gracefully and log the
+ * failed attempt. Media keys will not be routed.
+ */
+ private void getMediaKeyFocus() {
+ if (DBG) Log.d(TAG, "getMediaKeyFocus()");
+ if (mMediaPlayer == null) {
+ Log.w(TAG, "Media player is null. Can't get media key focus. Media keys may not route");
+ return;
+ }
+ mMediaPlayer.start();
+ }
/**
* getBluetoothMediaBrowserService()
@@ -311,6 +399,7 @@
if (DBG) Log.d(TAG, "onPrepare");
if (mA2dpSinkService != null) {
mA2dpSinkService.requestAudioFocus(mA2dpDevice, true);
+ getMediaKeyFocus();
}
}
@@ -336,6 +425,7 @@
// Play the item if possible.
if (mA2dpSinkService != null) {
mA2dpSinkService.requestAudioFocus(mA2dpDevice, true);
+ getMediaKeyFocus();
}
mAvrcpCtrlSrvc.fetchAttrAndPlayItem(mA2dpDevice, mediaId);
}