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