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