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