A2dpSinkStreamHandler.java revision b8bd002d207c8025cd298521d1ecd7515ac33b44
1/*
2 * Copyright (C) 2016 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.bluetooth.a2dpsink;
18
19import android.content.Context;
20import android.media.AudioAttributes;
21import android.media.AudioManager;
22import android.media.AudioManager.OnAudioFocusChangeListener;
23import android.os.Handler;
24import android.os.Message;
25import android.util.Log;
26
27import com.android.bluetooth.avrcpcontroller.AvrcpControllerService;
28import com.android.bluetooth.R;
29
30/**
31 * Bluetooth A2DP SINK Streaming Handler.
32 *
33 * This handler defines how the stack behaves once the A2DP connection is established and both
34 * devices are ready for streaming. For simplification we assume that the connection can either
35 * stream music immediately (i.e. data packets coming in or have potential to come in) or it cannot
36 * stream (i.e. Idle and Open states are treated alike). See Fig 4-1 of GAVDP Spec 1.0.
37 *
38 * Note: There are several different audio tracks that a connected phone may like to transmit over
39 * the A2DP stream including Music, Navigation, Assistant, and Notifications.  Music is the only
40 * track that is almost always accompanied with an AVRCP play/pause command.
41 *
42 * Streaming is initiated by either an explicit play command from user interaction or audio coming
43 * from the phone.  Streaming is terminated when either the user pauses the audio, the audio stream
44 * from the phone ends, the phone disconnects, or audio focus is lost.  During playback if there is
45 * a change to audio focus playback may be temporarily paused and then resumed when focus is
46 * restored.
47 */
48public class A2dpSinkStreamHandler extends Handler {
49    private static final boolean DBG = false;
50    private static final String TAG = "A2dpSinkStreamHandler";
51
52    // Configuration Variables
53    private static final int DEFAULT_DUCK_PERCENT = 25;
54
55    // Incoming events.
56    public static final int SRC_STR_START = 0; // Audio stream from remote device started
57    public static final int SRC_STR_STOP = 1; // Audio stream from remote device stopped
58    public static final int SNK_PLAY = 2; // Play command was generated from local device
59    public static final int SNK_PAUSE = 3; // Pause command was generated from local device
60    public static final int SRC_PLAY = 4; // Play command was generated from remote device
61    public static final int SRC_PAUSE = 5; // Pause command was generated from remote device
62    public static final int DISCONNECT = 6; // Remote device was disconnected
63    public static final int AUDIO_FOCUS_CHANGE = 7; // Audio focus callback with associated change
64
65    // Used to indicate focus lost
66    private static final int STATE_FOCUS_LOST = 0;
67    // Used to inform bluedroid that focus is granted
68    private static final int STATE_FOCUS_GRANTED = 1;
69
70    // Private variables.
71    private A2dpSinkStateMachine mA2dpSinkSm;
72    private Context mContext;
73    private AudioManager mAudioManager;
74    // Keep track if the remote device is providing audio
75    private boolean mStreamAvailable = false;
76    private boolean mSentPause = false;
77    // Keep track of the relevant audio focus (None, Transient, Gain)
78    private int mAudioFocus = AudioManager.AUDIOFOCUS_NONE;
79
80    // Focus changes when we are currently holding focus.
81    private OnAudioFocusChangeListener mAudioFocusListener = new OnAudioFocusChangeListener() {
82        public void onAudioFocusChange(int focusChange) {
83            if (DBG) {
84                Log.d(TAG, "onAudioFocusChangeListener focuschange " + focusChange);
85            }
86            A2dpSinkStreamHandler.this.obtainMessage(AUDIO_FOCUS_CHANGE, focusChange)
87                    .sendToTarget();
88        }
89    };
90
91    public A2dpSinkStreamHandler(A2dpSinkStateMachine a2dpSinkSm, Context context) {
92        mA2dpSinkSm = a2dpSinkSm;
93        mContext = context;
94        mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
95    }
96
97    @Override
98    public void handleMessage(Message message) {
99        if (DBG) {
100            Log.d(TAG, " process message: " + message.what);
101            Log.d(TAG, " audioFocus =  " + mAudioFocus);
102        }
103        switch (message.what) {
104            case SRC_STR_START:
105                // Audio stream has started, stop it if we don't have focus.
106                mStreamAvailable = true;
107                if (mAudioFocus == AudioManager.AUDIOFOCUS_NONE) {
108                    sendAvrcpPause();
109                } else {
110                    startAvrcpUpdates();
111                }
112                break;
113
114            case SRC_STR_STOP:
115                // Audio stream has stopped, maintain focus but stop avrcp updates.
116                mStreamAvailable = false;
117                stopAvrcpUpdates();
118                break;
119
120            case SNK_PLAY:
121                // Local play command, gain focus and start avrcp updates.
122                if (mAudioFocus == AudioManager.AUDIOFOCUS_NONE) {
123                    requestAudioFocus();
124                }
125                startAvrcpUpdates();
126                break;
127
128            case SNK_PAUSE:
129                // Local pause command, maintain focus but stop avrcp updates.
130                stopAvrcpUpdates();
131                break;
132
133            case SRC_PLAY:
134                // Remote play command, if we have audio focus update avrcp, otherwise send pause.
135                if (mAudioFocus == AudioManager.AUDIOFOCUS_NONE) {
136                    sendAvrcpPause();
137                } else {
138                    startAvrcpUpdates();
139                }
140                break;
141
142            case SRC_PAUSE:
143                // Remote pause command, stop avrcp updates.
144                stopAvrcpUpdates();
145                break;
146
147            case DISCONNECT:
148                // Remote device has disconnected, restore everything to default state.
149                sendAvrcpPause();
150                stopAvrcpUpdates();
151                abandonAudioFocus();
152                mSentPause = false;
153                break;
154
155            case AUDIO_FOCUS_CHANGE:
156                // message.obj is the newly granted audio focus.
157                switch ((int) message.obj) {
158                    case AudioManager.AUDIOFOCUS_GAIN:
159                        // Begin playing audio, if we paused the remote, send a play now.
160                        startAvrcpUpdates();
161                        startFluorideStreaming();
162                        if (mSentPause) {
163                            sendAvrcpPlay();
164                            mSentPause = false;
165                        }
166                        break;
167
168                    case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
169                        // Make the volume duck.
170                        int duckPercent = mContext.getResources().getInteger(
171                                R.integer.a2dp_sink_duck_percent);
172                        if (duckPercent < 0 || duckPercent > 100) {
173                            Log.e(TAG, "Invalid duck percent using default.");
174                            duckPercent = DEFAULT_DUCK_PERCENT;
175                        }
176                        float duckRatio = (duckPercent / 100.0f);
177                        if (DBG) {
178                            Log.d(TAG, "Setting reduce gain on transient loss gain=" + duckRatio);
179                        }
180                        setFluorideAudioTrackGain(duckRatio);
181                        break;
182
183                    case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
184                        // Temporary loss of focus, if we are actively streaming pause the remote
185                        // and make sure we resume playback when we regain focus.
186                        if (mStreamAvailable) {
187                            sendAvrcpPause();
188                            mSentPause = true;
189                        }
190                        stopFluorideStreaming();
191                        break;
192
193                    case AudioManager.AUDIOFOCUS_LOSS:
194                        // Permanent loss of focus probably due to another audio app, abandon focus
195                        // and stop playback.
196                        mAudioFocus = AudioManager.AUDIOFOCUS_NONE;
197                        abandonAudioFocus();
198                        sendAvrcpPause();
199                        break;
200                }
201                break;
202
203            default:
204                Log.w(TAG, "Received unexpected event: " + message.what);
205        }
206    }
207
208    /**
209     * Utility functions.
210     */
211    private int requestAudioFocus() {
212        int focusRequestStatus = mAudioManager.requestAudioFocus(
213                mAudioFocusListener, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN);
214        // If the request is granted begin streaming immediately and schedule an upgrade.
215        if (focusRequestStatus == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
216            startAvrcpUpdates();
217            startFluorideStreaming();
218            mAudioFocus = AudioManager.AUDIOFOCUS_GAIN;
219        }
220        return focusRequestStatus;
221    }
222
223
224    private void abandonAudioFocus() {
225        stopFluorideStreaming();
226        mAudioManager.abandonAudioFocus(mAudioFocusListener);
227        mAudioFocus = AudioManager.AUDIOFOCUS_NONE;
228    }
229
230    private void startFluorideStreaming() {
231        mA2dpSinkSm.informAudioFocusStateNative(STATE_FOCUS_GRANTED);
232        mA2dpSinkSm.informAudioTrackGainNative(1.0f);
233    }
234
235    private void stopFluorideStreaming() {
236        mA2dpSinkSm.informAudioFocusStateNative(STATE_FOCUS_LOST);
237    }
238
239    private void setFluorideAudioTrackGain(float gain) {
240        mA2dpSinkSm.informAudioTrackGainNative(gain);
241    }
242
243    private void startAvrcpUpdates() {
244        // Since AVRCP gets started after A2DP we may need to request it later in cycle.
245        AvrcpControllerService avrcpService = AvrcpControllerService.getAvrcpControllerService();
246
247        if (DBG) {
248            Log.d(TAG, "startAvrcpUpdates");
249        }
250        if (avrcpService != null && avrcpService.getConnectedDevices().size() == 1) {
251            avrcpService.startAvrcpUpdates();
252        } else {
253            Log.e(TAG, "startAvrcpUpdates failed because of connection.");
254        }
255    }
256
257    private void stopAvrcpUpdates() {
258        // Since AVRCP gets started after A2DP we may need to request it later in cycle.
259        AvrcpControllerService avrcpService = AvrcpControllerService.getAvrcpControllerService();
260
261        if (DBG) {
262            Log.d(TAG, "stopAvrcpUpdates");
263        }
264        if (avrcpService != null && avrcpService.getConnectedDevices().size() == 1) {
265            avrcpService.stopAvrcpUpdates();
266        } else {
267            Log.e(TAG, "stopAvrcpUpdates failed because of connection.");
268        }
269    }
270
271    private void sendAvrcpPause() {
272        // Since AVRCP gets started after A2DP we may need to request it later in cycle.
273        AvrcpControllerService avrcpService = AvrcpControllerService.getAvrcpControllerService();
274
275        if (DBG) {
276            Log.d(TAG, "sendAvrcpPause");
277        }
278        if (avrcpService != null && avrcpService.getConnectedDevices().size() == 1) {
279            if (DBG) {
280                Log.d(TAG, "Pausing AVRCP.");
281            }
282            avrcpService.sendPassThroughCmd(avrcpService.getConnectedDevices().get(0),
283                    AvrcpControllerService.PASS_THRU_CMD_ID_PAUSE,
284                    AvrcpControllerService.KEY_STATE_PRESSED);
285            avrcpService.sendPassThroughCmd(avrcpService.getConnectedDevices().get(0),
286                    AvrcpControllerService.PASS_THRU_CMD_ID_PAUSE,
287                    AvrcpControllerService.KEY_STATE_RELEASED);
288        } else {
289            Log.e(TAG, "Passthrough not sent, connection un-available.");
290        }
291    }
292
293    private void sendAvrcpPlay() {
294        // Since AVRCP gets started after A2DP we may need to request it later in cycle.
295        AvrcpControllerService avrcpService = AvrcpControllerService.getAvrcpControllerService();
296
297        if (DBG) {
298            Log.d(TAG, "sendAvrcpPlay");
299        }
300        if (avrcpService != null && avrcpService.getConnectedDevices().size() == 1) {
301            if (DBG) {
302                Log.d(TAG, "Playing AVRCP.");
303            }
304            avrcpService.sendPassThroughCmd(avrcpService.getConnectedDevices().get(0),
305                    AvrcpControllerService.PASS_THRU_CMD_ID_PLAY,
306                    AvrcpControllerService.KEY_STATE_PRESSED);
307            avrcpService.sendPassThroughCmd(avrcpService.getConnectedDevices().get(0),
308                    AvrcpControllerService.PASS_THRU_CMD_ID_PLAY,
309                    AvrcpControllerService.KEY_STATE_RELEASED);
310        } else {
311            Log.e(TAG, "Passthrough not sent, connection un-available.");
312        }
313    }
314}
315