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

Commit 98fa3a8e authored by Sal Savage's avatar Sal Savage Committed by Joseph Pirozzo
Browse files

Move silent player to A2DPSink, get button routing only if focus is granted

Originally AVRCP had the silent media player. The media player would
play each time we thought we wanted to play, regardless of whether or
not A2DPSink was actually granted audio focus. This meant stealing the
media button session when we didnt want to. Additionaly, A2DPSink never
notified AVRCP controller of lost focus, so we couldn't destroy the
player and signal to MSS that we no longer wanted to be considered. This
allows us to destroy the player when focus is lost as well.

Media button routing requires a MediaSession and a MediaPlayer in the
same process. This only continues to work because all the BT profiles do
in fact run from the same process. This will need to live in AVRCP
again if that is ever not the case.

Bug: b/128922658
Test: Pair a device with automotive hardware, initiate playback from
media center. Verify that adb shell dumpsys media_session shows BT as
the media button session. Use adb shell input keyevent MEDIA_* commands
to verify that button events get routed to BT.

Change-Id: I8f8d11b29cbc5243b43434143148960b5a806b99
(cherry picked from commit ec49615a)
parent c8b09c84
Loading
Loading
Loading
Loading
+64 −0
Original line number Diff line number Diff line
@@ -23,6 +23,7 @@ import android.media.AudioAttributes;
import android.media.AudioFocusRequest;
import android.media.AudioManager;
import android.media.AudioManager.OnAudioFocusChangeListener;
import android.media.MediaPlayer;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
@@ -86,6 +87,16 @@ public class A2dpSinkStreamHandler extends Handler {
    // Keep track of the relevant audio focus (None, Transient, Gain)
    private int mAudioFocus = AudioManager.AUDIOFOCUS_NONE;

    // In order for Bluetooth 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 the Bluetooth process media events. This allows AVRCP
    // controller to create a MediaSession and handle the events if it would like. The player and
    // session requirement is a restriction currently imposed by the media framework code and could
    // be reconsidered in the future.
    private MediaPlayer mMediaPlayer = null;

    // Focus changes when we are currently holding focus.
    private OnAudioFocusChangeListener mAudioFocusListener = new OnAudioFocusChangeListener() {
        @Override
@@ -223,12 +234,17 @@ public class A2dpSinkStreamHandler extends Handler {
     * Utility functions.
     */
    private void requestAudioFocusIfNone() {
        if (DBG) Log.d(TAG, "requestAudioFocusIfNone()");
        if (mAudioFocus == AudioManager.AUDIOFOCUS_NONE) {
            requestAudioFocus();
        }
        // On the off change mMediaPlayer errors out and dies, we want to make sure we retry this.
        // This function immediately exits if we have a MediaPlayer object.
        requestMediaKeyFocus();
    }

    private synchronized int requestAudioFocus() {
        if (DBG) Log.d(TAG, "requestAudioFocus()");
        // Bluetooth A2DP may carry Music, Audio Books, Navigation, or other sounds so mark content
        // type unknown.
        AudioAttributes streamAttributes =
@@ -252,13 +268,61 @@ public class A2dpSinkStreamHandler extends Handler {
        return focusRequestStatus;
    }

    /**
     * Creates a MediaPlayer that plays a silent audio sample so that MediaSessionService will be
     * aware of the fact that Bluetooth is playing audio.
     *
     * This allows the MediaSession in AVRCP Controller to be routed media key events, if we've
     * chosen to use it.
     */
    private synchronized void requestMediaKeyFocus() {
        if (DBG) Log.d(TAG, "requestMediaKeyFocus()");

        if (mMediaPlayer != null) return;

        AudioAttributes attrs = new AudioAttributes.Builder()
                .setUsage(AudioAttributes.USAGE_MEDIA)
                .build();

        mMediaPlayer = MediaPlayer.create(mContext, R.raw.silent, attrs,
                mAudioManager.generateAudioSessionId());
        if (mMediaPlayer == null) {
            Log.e(TAG, "Failed to initialize media player. You may not get media key events");
            return;
        }

        mMediaPlayer.setLooping(false);
        mMediaPlayer.setOnErrorListener((mp, what, extra) -> {
            Log.e(TAG, "Silent media player error: " + what + ", " + extra);
            releaseMediaKeyFocus();
            return false;
        });

        mMediaPlayer.start();
    }

    private synchronized void abandonAudioFocus() {
        if (DBG) Log.d(TAG, "abandonAudioFocus()");
        stopFluorideStreaming();
        releaseMediaKeyFocus();
        mAudioManager.abandonAudioFocus(mAudioFocusListener);
        mAudioFocus = AudioManager.AUDIOFOCUS_NONE;
    }

    /**
     * Destroys the silent audio sample MediaPlayer, notifying MediaSessionService of the fact
     * we're no longer playing audio.
     */
    private synchronized void releaseMediaKeyFocus() {
        if (DBG) Log.d(TAG, "releaseMediaKeyFocus()");
        if (mMediaPlayer == null) {
            return;
        }
        mMediaPlayer.stop();
        mMediaPlayer.release();
        mMediaPlayer = null;
    }

    private void startFluorideStreaming() {
        mA2dpSinkSm.informAudioFocusStateNative(STATE_FOCUS_GRANTED);
        mA2dpSinkSm.informAudioTrackGainNative(1.0f);
+0 −76
Original line number Diff line number Diff line
@@ -23,10 +23,7 @@ import android.content.BroadcastReceiver;
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;
@@ -99,15 +96,8 @@ public class BluetoothMediaBrowserService extends MediaBrowserService {

    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;
@@ -186,9 +176,6 @@ public class BluetoothMediaBrowserService extends MediaBrowserService {
        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);
@@ -222,71 +209,10 @@ public class BluetoothMediaBrowserService extends MediaBrowserService {
        if (DBG) Log.d(TAG, "onDestroy");
        setBluetoothMediaBrowserService(null);
        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()
     *  Routine to get direct access to MediaBrowserService from within the same process.
@@ -399,7 +325,6 @@ public class BluetoothMediaBrowserService extends MediaBrowserService {
            if (DBG) Log.d(TAG, "onPrepare");
            if (mA2dpSinkService != null) {
                mA2dpSinkService.requestAudioFocus(mA2dpDevice, true);
                getMediaKeyFocus();
            }
        }

@@ -425,7 +350,6 @@ public class BluetoothMediaBrowserService extends MediaBrowserService {
                // Play the item if possible.
                if (mA2dpSinkService != null) {
                    mA2dpSinkService.requestAudioFocus(mA2dpDevice, true);
                    getMediaKeyFocus();
                }
                mAvrcpCtrlSrvc.fetchAttrAndPlayItem(mA2dpDevice, mediaId);
            }