• 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.pm.PackageManager;
21 import android.media.AudioAttributes;
22 import android.media.AudioFocusRequest;
23 import android.media.AudioManager;
24 import android.media.AudioManager.OnAudioFocusChangeListener;
25 import android.media.MediaPlayer;
26 import android.os.Handler;
27 import android.os.Message;
28 import android.support.v4.media.session.PlaybackStateCompat;
29 import android.util.Log;
30 
31 import com.android.bluetooth.R;
32 import com.android.bluetooth.avrcpcontroller.BluetoothMediaBrowserService;
33 import com.android.bluetooth.hfpclient.HeadsetClientService;
34 import com.android.bluetooth.hfpclient.HfpClientCall;
35 
36 import java.util.List;
37 
38 /**
39  * Bluetooth A2DP SINK Streaming Handler.
40  *
41  * This handler defines how the stack behaves once the A2DP connection is established and both
42  * devices are ready for streaming. For simplification we assume that the connection can either
43  * stream music immediately (i.e. data packets coming in or have potential to come in) or it cannot
44  * stream (i.e. Idle and Open states are treated alike). See Fig 4-1 of GAVDP Spec 1.0.
45  *
46  * Note: There are several different audio tracks that a connected phone may like to transmit over
47  * the A2DP stream including Music, Navigation, Assistant, and Notifications.  Music is the only
48  * track that is almost always accompanied with an AVRCP play/pause command.
49  *
50  * Streaming is initiated by either an explicit play command from user interaction or audio coming
51  * from the phone.  Streaming is terminated when either the user pauses the audio, the audio stream
52  * from the phone ends, the phone disconnects, or audio focus is lost.  During playback if there is
53  * a change to audio focus playback may be temporarily paused and then resumed when focus is
54  * restored.
55  */
56 public class A2dpSinkStreamHandler extends Handler {
57     private static final String TAG = "A2dpSinkStreamHandler";
58     private static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG);
59 
60     // Configuration Variables
61     private static final int DEFAULT_DUCK_PERCENT = 25;
62     private static final int SETTLE_TIMEOUT = 400;
63 
64     // Incoming events.
65     public static final int SRC_STR_START = 0; // Audio stream from remote device started
66     public static final int SRC_STR_STOP = 1; // Audio stream from remote device stopped
67     public static final int SNK_PLAY = 2; // Play command was generated from local device
68     public static final int SNK_PAUSE = 3; // Pause command was generated from local device
69     public static final int SRC_PLAY = 4; // Play command was generated from remote device
70     public static final int SRC_PAUSE = 5; // Pause command was generated from remote device
71     public static final int DISCONNECT = 6; // Remote device was disconnected
72     public static final int AUDIO_FOCUS_CHANGE = 7; // Audio focus callback with associated change
73     public static final int REQUEST_FOCUS = 8; // Request focus when the media service is active
74     public static final int DELAYED_PAUSE = 9; // If a call just started allow stack time to settle
75 
76     // Used to indicate focus lost
77     private static final int STATE_FOCUS_LOST = 0;
78     // Used to inform bluedroid that focus is granted
79     private static final int STATE_FOCUS_GRANTED = 1;
80 
81     // Private variables.
82     private A2dpSinkService mA2dpSinkService;
83     private A2dpSinkNativeInterface mNativeInterface;
84     private AudioManager mAudioManager;
85     // Keep track if the remote device is providing audio
86     private boolean mStreamAvailable = false;
87     private boolean mSentPause = false;
88     // Keep track of the relevant audio focus (None, Transient, Gain)
89     private int mAudioFocus = AudioManager.AUDIOFOCUS_NONE;
90 
91     // In order for Bluetooth to be considered as an audio source capable of receiving media key
92     // events (In the eyes of MediaSessionService), we need an active MediaPlayer in addition to a
93     // MediaSession. Because of this, the media player below plays an incredibly short, silent audio
94     // sample so that MediaSessionService and AudioPlaybackStateMonitor will believe that we're the
95     // current active player and send the Bluetooth process media events. This allows AVRCP
96     // controller to create a MediaSession and handle the events if it would like. The player and
97     // session requirement is a restriction currently imposed by the media framework code and could
98     // be reconsidered in the future.
99     private MediaPlayer mMediaPlayer = null;
100 
101     // Focus changes when we are currently holding focus.
102     private OnAudioFocusChangeListener mAudioFocusListener = new OnAudioFocusChangeListener() {
103         @Override
104         public void onAudioFocusChange(int focusChange) {
105             if (DBG) {
106                 Log.d(TAG, "onAudioFocusChangeListener focuschange " + focusChange);
107             }
108             A2dpSinkStreamHandler.this.obtainMessage(AUDIO_FOCUS_CHANGE, focusChange)
109                     .sendToTarget();
110         }
111     };
112 
A2dpSinkStreamHandler(A2dpSinkService a2dpSinkService, A2dpSinkNativeInterface nativeInterface)113     public A2dpSinkStreamHandler(A2dpSinkService a2dpSinkService,
114             A2dpSinkNativeInterface nativeInterface) {
115         mA2dpSinkService = a2dpSinkService;
116         mNativeInterface = nativeInterface;
117         mAudioManager = mA2dpSinkService.getSystemService(AudioManager.class);
118     }
119 
120     /**
121      * Safely clean up this stream handler object
122      */
cleanup()123     public void cleanup() {
124         abandonAudioFocus();
125         removeCallbacksAndMessages(null);
126     }
127 
requestAudioFocus(boolean request)128     void requestAudioFocus(boolean request) {
129         obtainMessage(REQUEST_FOCUS, request).sendToTarget();
130     }
131 
getFocusState()132     int getFocusState() {
133         return mAudioFocus;
134     }
135 
isPlaying()136     boolean isPlaying() {
137         return (mStreamAvailable
138                 && (mAudioFocus == AudioManager.AUDIOFOCUS_GAIN
139                 || mAudioFocus == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK));
140     }
141 
142     @Override
handleMessage(Message message)143     public void handleMessage(Message message) {
144         if (DBG) {
145             Log.d(TAG, " process message: " + message.what);
146             Log.d(TAG, " audioFocus =  " + mAudioFocus);
147         }
148         switch (message.what) {
149             case SRC_STR_START:
150                 mStreamAvailable = true;
151                 if (isTvDevice() || shouldRequestFocus()) {
152                     requestAudioFocusIfNone();
153                 }
154                 break;
155 
156             case SRC_STR_STOP:
157                 // Audio stream has stopped, maintain focus but stop avrcp updates.
158                 break;
159 
160             case SNK_PLAY:
161                 // Local play command, gain focus and start avrcp updates.
162                 requestAudioFocusIfNone();
163                 break;
164 
165             case SNK_PAUSE:
166                 mStreamAvailable = false;
167                 // Local pause command, maintain focus but stop avrcp updates.
168                 break;
169 
170             case SRC_PLAY:
171                 mStreamAvailable = true;
172                 // Remote play command.
173                 if (isIotDevice() || isTvDevice() || shouldRequestFocus()) {
174                     requestAudioFocusIfNone();
175                     break;
176                 }
177                 break;
178 
179             case SRC_PAUSE:
180                 mStreamAvailable = false;
181                 // Remote pause command, stop avrcp updates.
182                 break;
183 
184             case REQUEST_FOCUS:
185                 requestAudioFocusIfNone();
186                 break;
187 
188             case DISCONNECT:
189                 // Remote device has disconnected, restore everything to default state.
190                 mSentPause = false;
191                 break;
192 
193             case AUDIO_FOCUS_CHANGE:
194                 mAudioFocus = (int) message.obj;
195                 // message.obj is the newly granted audio focus.
196                 switch (mAudioFocus) {
197                     case AudioManager.AUDIOFOCUS_GAIN:
198                         removeMessages(DELAYED_PAUSE);
199                         // Begin playing audio, if we paused the remote, send a play now.
200                         startFluorideStreaming();
201                         if (mSentPause) {
202                             sendAvrcpPlay();
203                             mSentPause = false;
204                         }
205                         break;
206 
207                     case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
208                         // Make the volume duck.
209                         int duckPercent = mA2dpSinkService.getResources()
210                                 .getInteger(R.integer.a2dp_sink_duck_percent);
211                         if (duckPercent < 0 || duckPercent > 100) {
212                             Log.e(TAG, "Invalid duck percent using default.");
213                             duckPercent = DEFAULT_DUCK_PERCENT;
214                         }
215                         float duckRatio = (duckPercent / 100.0f);
216                         if (DBG) {
217                             Log.d(TAG, "Setting reduce gain on transient loss gain=" + duckRatio);
218                         }
219                         setFluorideAudioTrackGain(duckRatio);
220                         break;
221 
222                     case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
223                         // Temporary loss of focus, if we are actively streaming pause the remote
224                         // and make sure we resume playback when we regain focus.
225                         sendMessageDelayed(obtainMessage(DELAYED_PAUSE), SETTLE_TIMEOUT);
226                         setFluorideAudioTrackGain(0);
227                         break;
228 
229                     case AudioManager.AUDIOFOCUS_LOSS:
230                         // Permanent loss of focus probably due to another audio app, abandon focus
231                         // and stop playback.
232                         abandonAudioFocus();
233                         sendAvrcpPause();
234                         break;
235                 }
236                 break;
237 
238             case DELAYED_PAUSE:
239                 if (BluetoothMediaBrowserService.getPlaybackState()
240                             == PlaybackStateCompat.STATE_PLAYING && !inCallFromStreamingDevice()) {
241                     sendAvrcpPause();
242                     mSentPause = true;
243                     mStreamAvailable = false;
244                 }
245                 break;
246 
247             default:
248                 Log.w(TAG, "Received unexpected event: " + message.what);
249         }
250     }
251 
252     /**
253      * Utility functions.
254      */
requestAudioFocusIfNone()255     private void requestAudioFocusIfNone() {
256         if (DBG) Log.d(TAG, "requestAudioFocusIfNone()");
257         if (mAudioFocus != AudioManager.AUDIOFOCUS_GAIN) {
258             requestAudioFocus();
259         }
260     }
261 
requestAudioFocus()262     private synchronized int requestAudioFocus() {
263         if (DBG) Log.d(TAG, "requestAudioFocus()");
264         // Bluetooth A2DP may carry Music, Audio Books, Navigation, or other sounds so mark content
265         // type unknown.
266         AudioAttributes streamAttributes =
267                 new AudioAttributes.Builder().setUsage(AudioAttributes.USAGE_MEDIA)
268                         .setContentType(AudioAttributes.CONTENT_TYPE_UNKNOWN)
269                         .build();
270         // Bluetooth ducking is handled at the native layer at the request of AudioManager.
271         AudioFocusRequest focusRequest =
272                 new AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN).setAudioAttributes(
273                         streamAttributes)
274                         .setOnAudioFocusChangeListener(mAudioFocusListener, this)
275                         .build();
276         int focusRequestStatus = mAudioManager.requestAudioFocus(focusRequest);
277         // If the request is granted begin streaming immediately and schedule an upgrade.
278         if (focusRequestStatus == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
279             startFluorideStreaming();
280             mAudioFocus = AudioManager.AUDIOFOCUS_GAIN;
281         }
282         return focusRequestStatus;
283     }
284 
285     /**
286      * Plays a silent audio sample so that MediaSessionService will be aware of the fact that
287      * Bluetooth is playing audio.
288      *
289      * Creates a new MediaPlayer if one does not already exist. Repeat calls to this function are
290      * safe and will result in the silent audio sample again.
291      *
292      * This allows the MediaSession in AVRCP Controller to be routed media key events, if we've
293      * chosen to use it.
294      */
requestMediaKeyFocus()295     private synchronized void requestMediaKeyFocus() {
296         if (DBG) Log.d(TAG, "requestMediaKeyFocus()");
297 
298         if (mMediaPlayer == null) {
299             AudioAttributes attrs = new AudioAttributes.Builder()
300                     .setUsage(AudioAttributes.USAGE_MEDIA)
301                     .build();
302 
303             mMediaPlayer = MediaPlayer.create(mA2dpSinkService, R.raw.silent, attrs,
304                     mAudioManager.generateAudioSessionId());
305             if (mMediaPlayer == null) {
306                 Log.e(TAG, "Failed to initialize media player. You may not get media key events");
307                 return;
308             }
309 
310             mMediaPlayer.setLooping(false);
311             mMediaPlayer.setOnErrorListener((mp, what, extra) -> {
312                 Log.e(TAG, "Silent media player error: " + what + ", " + extra);
313                 releaseMediaKeyFocus();
314                 return false;
315             });
316         }
317 
318         mMediaPlayer.start();
319         BluetoothMediaBrowserService.setActive(true);
320     }
321 
abandonAudioFocus()322     private synchronized void abandonAudioFocus() {
323         if (DBG) Log.d(TAG, "abandonAudioFocus()");
324         stopFluorideStreaming();
325         mAudioManager.abandonAudioFocus(mAudioFocusListener);
326         mAudioFocus = AudioManager.AUDIOFOCUS_NONE;
327     }
328 
329     /**
330      * Destroys the silent audio sample MediaPlayer, notifying MediaSessionService of the fact
331      * we're no longer playing audio.
332      */
releaseMediaKeyFocus()333     private synchronized void releaseMediaKeyFocus() {
334         if (DBG) Log.d(TAG, "releaseMediaKeyFocus()");
335         if (mMediaPlayer == null) {
336             return;
337         }
338         BluetoothMediaBrowserService.setActive(false);
339         mMediaPlayer.stop();
340         mMediaPlayer.release();
341         mMediaPlayer = null;
342     }
343 
startFluorideStreaming()344     private void startFluorideStreaming() {
345         mNativeInterface.informAudioFocusState(STATE_FOCUS_GRANTED);
346         mNativeInterface.informAudioTrackGain(1.0f);
347         requestMediaKeyFocus();
348     }
349 
stopFluorideStreaming()350     private void stopFluorideStreaming() {
351         releaseMediaKeyFocus();
352         mNativeInterface.informAudioFocusState(STATE_FOCUS_LOST);
353     }
354 
setFluorideAudioTrackGain(float gain)355     private void setFluorideAudioTrackGain(float gain) {
356         mNativeInterface.informAudioTrackGain(gain);
357     }
358 
sendAvrcpPause()359     private void sendAvrcpPause() {
360         BluetoothMediaBrowserService.pause();
361     }
362 
sendAvrcpPlay()363     private void sendAvrcpPlay() {
364         BluetoothMediaBrowserService.play();
365     }
366 
inCallFromStreamingDevice()367     private boolean inCallFromStreamingDevice() {
368         BluetoothDevice targetDevice = null;
369         List<BluetoothDevice> connectedDevices = mA2dpSinkService.getConnectedDevices();
370         if (!connectedDevices.isEmpty()) {
371             targetDevice = connectedDevices.get(0);
372         }
373         HeadsetClientService headsetClientService = HeadsetClientService.getHeadsetClientService();
374         if (targetDevice != null && headsetClientService != null) {
375             List<HfpClientCall> currentCalls =
376                     headsetClientService.getCurrentCalls(targetDevice);
377             if (currentCalls == null) return false;
378             return currentCalls.size() > 0;
379         }
380         return false;
381     }
382 
isIotDevice()383     private boolean isIotDevice() {
384         return mA2dpSinkService.getPackageManager().hasSystemFeature(
385                 PackageManager.FEATURE_EMBEDDED);
386     }
387 
isTvDevice()388     private boolean isTvDevice() {
389         return mA2dpSinkService.getPackageManager().hasSystemFeature(
390                 PackageManager.FEATURE_LEANBACK);
391     }
392 
shouldRequestFocus()393     private boolean shouldRequestFocus() {
394         return mA2dpSinkService.getResources()
395                 .getBoolean(R.bool.a2dp_sink_automatically_request_audio_focus);
396     }
397 
398 }
399