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

Commit 2aba892f authored by Joseph Pirozzo's avatar Joseph Pirozzo Committed by android-build-merger
Browse files

Merge "A2DP Audio Focus" am: 70a3dc94

am: ace93a95

Change-Id: I32089ea4180d5feccbd3b1e2caa3096911ea7600
parents 4a8ea522 ace93a95
Loading
Loading
Loading
Loading
+12 −15
Original line number Diff line number Diff line
@@ -24,7 +24,7 @@
 *                           |    ^
 *                 CONNECTED |    | CONNECT
 *                           V    |
 *                        (Connected -- See A2dpSinkStreamingStateMachine)
 *                        (Connected -- See A2dpSinkStreamHandler)
 */
package com.android.bluetooth.a2dpsink;

@@ -120,7 +120,7 @@ final class A2dpSinkStateMachine extends StateMachine {
    private BluetoothDevice mTargetDevice = null;
    private BluetoothDevice mIncomingDevice = null;
    private BluetoothDevice mPlayingDevice = null;
    private A2dpSinkStreamingStateMachine mStreaming = null;
    private A2dpSinkStreamHandler mStreaming = null;

    private final HashMap<BluetoothDevice,BluetoothAudioConfig> mAudioConfigs
            = new HashMap<BluetoothDevice,BluetoothAudioConfig>();
@@ -164,11 +164,8 @@ final class A2dpSinkStateMachine extends StateMachine {
            Log.d("A2dpSinkStateMachine", "Quit");
        }
        synchronized (A2dpSinkStateMachine.this) {
            if (mStreaming != null) {
                mStreaming.doQuit();
                mStreaming = null;
        }
        }
        quitNow();
    }

@@ -499,10 +496,9 @@ final class A2dpSinkStateMachine extends StateMachine {
            synchronized (A2dpSinkStateMachine.this) {
                if (mStreaming == null) {
                    if(DBG) {
                        log("Creating New A2dpSinkStreamingStateMachine");
                        log("Creating New A2dpSinkStreamHandler");
                    }
                    mStreaming = A2dpSinkStreamingStateMachine.make(A2dpSinkStateMachine.this,
                            mContext);
                    mStreaming = new A2dpSinkStreamHandler(A2dpSinkStateMachine.this, mContext);
                }
            }
        }
@@ -533,7 +529,7 @@ final class A2dpSinkStateMachine extends StateMachine {

                    synchronized (A2dpSinkStateMachine.this) {
                        mTargetDevice = device;
                        mStreaming.sendMessage(A2dpSinkStreamingStateMachine.DISCONNECT);
                        mStreaming.obtainMessage(A2dpSinkStreamHandler.DISCONNECT).sendToTarget();
                        transitionTo(mPending);
                    }
                }
@@ -553,7 +549,7 @@ final class A2dpSinkStateMachine extends StateMachine {
                        break;
                    }
                    mPlayingDevice = null;
                    mStreaming.sendMessage(A2dpSinkStreamingStateMachine.DISCONNECT);
                    mStreaming.obtainMessage(A2dpSinkStreamHandler.DISCONNECT).sendToTarget();
                    transitionTo(mPending);
                }
                break;
@@ -578,12 +574,12 @@ final class A2dpSinkStateMachine extends StateMachine {

                case EVENT_AVRCP_CT_PLAY:
                case EVENT_AVRCP_TG_PLAY:
                    mStreaming.sendMessage(A2dpSinkStreamingStateMachine.ACT_PLAY);
                    mStreaming.obtainMessage(A2dpSinkStreamHandler.ACT_PLAY).sendToTarget();
                    break;

                case EVENT_AVRCP_CT_PAUSE:
                case EVENT_AVRCP_TG_PAUSE:
                    mStreaming.sendMessage(A2dpSinkStreamingStateMachine.ACT_PAUSE);
                    mStreaming.obtainMessage(A2dpSinkStreamHandler.ACT_PAUSE).sendToTarget();
                    break;


@@ -606,7 +602,8 @@ final class A2dpSinkStateMachine extends StateMachine {
                                                 BluetoothProfile.STATE_CONNECTED);
                        synchronized (A2dpSinkStateMachine.this) {
                            // Take care of existing audio focus in the streaming state machine.
                            mStreaming.sendMessage(A2dpSinkStreamingStateMachine.DISCONNECT);
                            mStreaming.obtainMessage(A2dpSinkStreamHandler.DISCONNECT)
                                    .sendToTarget();
                            mCurrentDevice = null;
                            transitionTo(mDisconnected);
                        }
@@ -629,11 +626,11 @@ final class A2dpSinkStateMachine extends StateMachine {
            log(" processAudioStateEvent in state " + state);
            switch (state) {
                case AUDIO_STATE_STARTED:
                    mStreaming.sendMessage(A2dpSinkStreamingStateMachine.SRC_STR_START);
                    mStreaming.obtainMessage(A2dpSinkStreamHandler.SRC_STR_START).sendToTarget();
                    break;
                case AUDIO_STATE_REMOTE_SUSPEND:
                case AUDIO_STATE_STOPPED:
                    mStreaming.sendMessage(A2dpSinkStreamingStateMachine.SRC_STR_STOP);
                    mStreaming.obtainMessage(A2dpSinkStreamHandler.SRC_STR_STOP).sendToTarget();
                    break;
                default:
                  loge("Audio State Device: " + device + " bad state: " + state);
+310 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2016 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.bluetooth.a2dpsink;

import android.content.Context;
import android.media.AudioAttributes;
import android.media.AudioManager;
import android.media.AudioManager.OnAudioFocusChangeListener;
import android.os.Handler;
import android.os.Message;
import android.util.Log;

import com.android.bluetooth.avrcpcontroller.AvrcpControllerService;
import com.android.bluetooth.R;

/**
 * Bluetooth A2DP SINK Streaming Handler.
 *
 * This handler defines how the stack behaves once the A2DP connection is established and both
 * devices are ready for streaming. For simplification we assume that the connection can either
 * stream music immediately (i.e. data packets coming in or have potential to come in) or it cannot
 * stream (i.e. Idle and Open states are treated alike). See Fig 4-1 of GAVDP Spec 1.0.
 *
 * Note: There are several different audio tracks that a connected phone may like to transmit over
 * the A2DP stream including Music, Navigation, Assistant, and Notifications.  Music is the only
 * track that is almost always accompanied with an AVRCP play/pause command.  The following handler
 * is configurable at compile time through the PLAY_WITHOUT_AVRCP_COMMAND flag to allow all of these
 * audio tracks to be played trough without an explicit play command.
 *
 * Streaming is initiated by either an explicit play command from user interaction or audio coming
 * from the phone.  Streaming is terminated when either the user pauses the audio, the audio stream
 * from the phone ends, the phone disconnects, or audio focus is lost.  During playback if there is
 * a change to audio focus playback may be temporarily paused and then resumed when focus is
 * restored.
 */
final class A2dpSinkStreamHandler extends Handler {
    private static final boolean DBG = false;
    private static final String TAG = "A2dpSinkStreamHandler";

    // Configuration Variables
    private static final int DEFAULT_DUCK_PERCENT = 25;
    // Allows any audio to stream from phone without requiring AVRCP play command,
    // this lets navigation and other non music streams through.
    private static final boolean PLAY_WITHOUT_AVRCP_COMMAND = true;

    // Incoming events.
    public static final int SRC_STR_START = 0;
    public static final int SRC_STR_STOP = 1;
    public static final int ACT_PLAY = 2;
    public static final int ACT_PAUSE = 3;
    public static final int DISCONNECT = 4;
    public static final int UPGRADE_FOCUS = 5;
    public static final int AUDIO_FOCUS_CHANGE = 7;

    // Used to indicate focus lost
    private static final int STATE_FOCUS_LOST = 0;
    // Used to inform bluedroid that focus is granted
    private static final int STATE_FOCUS_GRANTED = 1;
    // Timeout in milliseconds before upgrading a transient audio focus to full focus;
    // This allows notifications and other intermittent sounds from impacting other sources.
    private static final int TRANSIENT_FOCUS_DELAY = 10000; // 10 seconds

    // Private variables.
    private A2dpSinkStateMachine mA2dpSinkSm;
    private Context mContext;
    private AudioManager mAudioManager;
    private AudioAttributes mStreamAttributes;
    // Keep track if play was requested
    private boolean playRequested = false;
    // Keep track if the remote device is providing audio
    private boolean streamAvailable = false;
    // Keep track of the relevant audio focus (None, Transient, Gain)
    private int audioFocus = AudioManager.AUDIOFOCUS_NONE;

    // Focus changes when we are currently holding focus.
    private OnAudioFocusChangeListener mAudioFocusListener = new OnAudioFocusChangeListener() {
        public void onAudioFocusChange(int focusChange) {
            if (DBG) {
                Log.d(TAG, "onAudioFocusChangeListener focuschange " + focusChange);
            }
            A2dpSinkStreamHandler.this.obtainMessage(AUDIO_FOCUS_CHANGE, focusChange)
                    .sendToTarget();
        }
    };

    public A2dpSinkStreamHandler(A2dpSinkStateMachine a2dpSinkSm, Context context) {
        mA2dpSinkSm = a2dpSinkSm;
        mContext = context;
        mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
        mStreamAttributes = new AudioAttributes.Builder()
                                    .setUsage(AudioAttributes.USAGE_MEDIA)
                                    .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
                                    .build();
    }

    @Override
    public void handleMessage(Message message) {
        if (DBG) {
            Log.d(TAG, " process message: " + message.what);
            Log.d(TAG, " audioFocus =  " + audioFocus + " playRequested = " + playRequested);
        }
        switch (message.what) {
            case SRC_STR_START:
                streamAvailable = true;
                if ((playRequested || PLAY_WITHOUT_AVRCP_COMMAND)
                        && audioFocus == AudioManager.AUDIOFOCUS_NONE) {
                    requestAudioFocus();
                }
                break;

            case SRC_STR_STOP:
                streamAvailable = false;
                if (audioFocus != AudioManager.AUDIOFOCUS_NONE) {
                    abandonAudioFocus();
                }
                break;

            case ACT_PLAY:
                playRequested = true;
                startAvrcpUpdates();
                if (streamAvailable && audioFocus == AudioManager.AUDIOFOCUS_NONE) {
                    requestAudioFocus();
                }
                break;

            case ACT_PAUSE:
                playRequested = false;
                stopAvrcpUpdates();
                break;

            case DISCONNECT:
                playRequested = false;
                sendAvrcpPause();
                stopAvrcpUpdates();
                stopFluorideStreaming();
                abandonAudioFocus();
                break;

            case UPGRADE_FOCUS:
                upgradeAudioFocus();
                break;

            case AUDIO_FOCUS_CHANGE:
                // message.obj is the newly granted audio focus.
                switch ((int) message.obj) {
                    case AudioManager.AUDIOFOCUS_GAIN_TRANSIENT:
                        setFluorideAudioTrackGain(1.0f);
                        sendMessageDelayed(obtainMessage(UPGRADE_FOCUS), TRANSIENT_FOCUS_DELAY);
                        // Begin playing audio
                        if (audioFocus == AudioManager.AUDIOFOCUS_NONE) {
                            audioFocus = (int) message.obj;
                            startAvrcpUpdates();
                            startFluorideStreaming();
                        }
                        break;

                    case AudioManager.AUDIOFOCUS_GAIN:
                        setFluorideAudioTrackGain(1.0f);
                        // Begin playing audio
                        if (audioFocus == AudioManager.AUDIOFOCUS_NONE) {
                            audioFocus = (int) message.obj;
                            startAvrcpUpdates();
                            startFluorideStreaming();
                        }
                        break;

                    case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
                        // Make the volume duck.
                        int duckPercent = mContext.getResources().getInteger(
                                R.integer.a2dp_sink_duck_percent);
                        if (duckPercent < 0 || duckPercent > 100) {
                            Log.e(TAG, "Invalid duck percent using default.");
                            duckPercent = DEFAULT_DUCK_PERCENT;
                        }
                        float duckRatio = (duckPercent / 100.0f);
                        if (DBG) {
                            Log.d(TAG, "Setting reduce gain on transient loss gain=" + duckRatio);
                        }
                        setFluorideAudioTrackGain(duckRatio);
                        removeMessages(UPGRADE_FOCUS);
                        break;

                    case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
                        stopFluorideStreaming();
                        removeMessages(UPGRADE_FOCUS);
                        break;

                    case AudioManager.AUDIOFOCUS_LOSS:
                        abandonAudioFocus();
                        sendAvrcpPause();
                        stopAvrcpUpdates();
                        stopFluorideStreaming();
                        break;
                }
                break;

            default:
                Log.w(TAG, "Received unexpected event: " + message.what);
        }
    }

    /**
     * Utility functions.
     */
    private int requestAudioFocus() {
        int focusRequestStatus = mAudioManager.requestAudioFocus(mAudioFocusListener,
                mStreamAttributes, AudioManager.AUDIOFOCUS_GAIN_TRANSIENT,
                AudioManager.AUDIOFOCUS_FLAG_DELAY_OK);
        // If the request is granted begin streaming immediately and schedule an upgrade.
        if (focusRequestStatus == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
            startAvrcpUpdates();
            setFluorideAudioTrackGain(1.0f);
            startFluorideStreaming();
            audioFocus = AudioManager.AUDIOFOCUS_GAIN_TRANSIENT;
            sendMessageDelayed(obtainMessage(UPGRADE_FOCUS), TRANSIENT_FOCUS_DELAY);
        }
        return focusRequestStatus;
    }

    private boolean upgradeAudioFocus() {
        return (mAudioManager.requestAudioFocus(mAudioFocusListener, AudioManager.STREAM_MUSIC,
                        AudioManager.AUDIOFOCUS_GAIN)
                == AudioManager.AUDIOFOCUS_REQUEST_GRANTED);
    }

    private void abandonAudioFocus() {
        removeMessages(UPGRADE_FOCUS);
        stopAvrcpUpdates();
        stopFluorideStreaming();
        mAudioManager.abandonAudioFocus(mAudioFocusListener);
        audioFocus = AudioManager.AUDIOFOCUS_NONE;
    }

    private void startFluorideStreaming() {
        mA2dpSinkSm.informAudioFocusStateNative(STATE_FOCUS_GRANTED);
        mA2dpSinkSm.informAudioTrackGainNative(1.0f);
    }

    private void stopFluorideStreaming() {
        mA2dpSinkSm.informAudioFocusStateNative(STATE_FOCUS_LOST);
    }

    private void setFluorideAudioTrackGain(float gain) {
        mA2dpSinkSm.informAudioTrackGainNative(gain);
    }

    private void startAvrcpUpdates() {
        // Since AVRCP gets started after A2DP we may need to request it later in cycle.
        AvrcpControllerService avrcpService = AvrcpControllerService.getAvrcpControllerService();

        if (DBG) {
            Log.d(TAG, "startAvrcpUpdates");
        }
        if (avrcpService != null && avrcpService.getConnectedDevices().size() == 1) {
            avrcpService.startAvrcpUpdates();
        } else {
            Log.e(TAG, "startAvrcpUpdates failed because of connection.");
        }
    }

    private void stopAvrcpUpdates() {
        // Since AVRCP gets started after A2DP we may need to request it later in cycle.
        AvrcpControllerService avrcpService = AvrcpControllerService.getAvrcpControllerService();

        if (DBG) {
            Log.d(TAG, "stopAvrcpUpdates");
        }
        if (avrcpService != null && avrcpService.getConnectedDevices().size() == 1) {
            avrcpService.stopAvrcpUpdates();
        } else {
            Log.e(TAG, "stopAvrcpUpdates failed because of connection.");
        }
    }

    private void sendAvrcpPause() {
        // Since AVRCP gets started after A2DP we may need to request it later in cycle.
        AvrcpControllerService avrcpService = AvrcpControllerService.getAvrcpControllerService();

        if (DBG) {
            Log.d(TAG, "sendAvrcpPause");
        }
        if (avrcpService != null && avrcpService.getConnectedDevices().size() == 1) {
            if (DBG) {
                Log.d(TAG, "Pausing AVRCP.");
            }
            avrcpService.sendPassThroughCmd(avrcpService.getConnectedDevices().get(0),
                    AvrcpControllerService.PASS_THRU_CMD_ID_PAUSE,
                    AvrcpControllerService.KEY_STATE_PRESSED);
            avrcpService.sendPassThroughCmd(avrcpService.getConnectedDevices().get(0),
                    AvrcpControllerService.PASS_THRU_CMD_ID_PAUSE,
                    AvrcpControllerService.KEY_STATE_RELEASED);
        } else {
            Log.e(TAG, "Passthrough not sent, connection un-available.");
        }
    }
}
+0 −566

File deleted.

Preview size limit exceeded, changes collapsed.