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