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