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.bluetooth.BluetoothHeadsetClientCall;
21 import android.content.Context;
22 import android.content.pm.PackageManager;
23 import android.media.AudioAttributes;
24 import android.media.AudioFocusRequest;
25 import android.media.AudioManager;
26 import android.media.AudioManager.OnAudioFocusChangeListener;
27 import android.media.MediaPlayer;
28 import android.os.Handler;
29 import android.os.Message;
30 import android.support.v4.media.session.PlaybackStateCompat;
31 import android.util.Log;
32 
33 import com.android.bluetooth.R;
34 import com.android.bluetooth.avrcpcontroller.BluetoothMediaBrowserService;
35 import com.android.bluetooth.hfpclient.HeadsetClientService;
36 
37 import java.util.List;
38 
39 /**
40  * Bluetooth A2DP SINK Streaming Handler.
41  *
42  * This handler defines how the stack behaves once the A2DP connection is established and both
43  * devices are ready for streaming. For simplification we assume that the connection can either
44  * stream music immediately (i.e. data packets coming in or have potential to come in) or it cannot
45  * stream (i.e. Idle and Open states are treated alike). See Fig 4-1 of GAVDP Spec 1.0.
46  *
47  * Note: There are several different audio tracks that a connected phone may like to transmit over
48  * the A2DP stream including Music, Navigation, Assistant, and Notifications.  Music is the only
49  * track that is almost always accompanied with an AVRCP play/pause command.
50  *
51  * Streaming is initiated by either an explicit play command from user interaction or audio coming
52  * from the phone.  Streaming is terminated when either the user pauses the audio, the audio stream
53  * from the phone ends, the phone disconnects, or audio focus is lost.  During playback if there is
54  * a change to audio focus playback may be temporarily paused and then resumed when focus is
55  * restored.
56  */
57 public class A2dpSinkStreamHandler extends Handler {
58     private static final String TAG = "A2dpSinkStreamHandler";
59     private static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG);
60 
61     // Configuration Variables
62     private static final int DEFAULT_DUCK_PERCENT = 25;
63     private static final int SETTLE_TIMEOUT = 400;
64 
65     // Incoming events.
66     public static final int SRC_STR_START = 0; // Audio stream from remote device started
67     public static final int SRC_STR_STOP = 1; // Audio stream from remote device stopped
68     public static final int SNK_PLAY = 2; // Play command was generated from local device
69     public static final int SNK_PAUSE = 3; // Pause command was generated from local device
70     public static final int SRC_PLAY = 4; // Play command was generated from remote device
71     public static final int SRC_PAUSE = 5; // Pause command was generated from remote device
72     public static final int DISCONNECT = 6; // Remote device was disconnected
73     public static final int AUDIO_FOCUS_CHANGE = 7; // Audio focus callback with associated change
74     public static final int REQUEST_FOCUS = 8; // Request focus when the media service is active
75     public static final int DELAYED_PAUSE = 9; // If a call just started allow stack time to settle
76 
77     // Used to indicate focus lost
78     private static final int STATE_FOCUS_LOST = 0;
79     // Used to inform bluedroid that focus is granted
80     private static final int STATE_FOCUS_GRANTED = 1;
81 
82     // Private variables.
83     private A2dpSinkService mA2dpSinkService;
84     private Context mContext;
85     private AudioManager mAudioManager;
86     // Keep track if the remote device is providing audio
87     private boolean mStreamAvailable = false;
88     private boolean mSentPause = false;
89     // Keep track of the relevant audio focus (None, Transient, Gain)
90     private int mAudioFocus = AudioManager.AUDIOFOCUS_NONE;
91 
92     // In order for Bluetooth to be considered as an audio source capable of receiving media key
93     // events (In the eyes of MediaSessionService), we need an active MediaPlayer in addition to a
94     // MediaSession. Because of this, the media player below plays an incredibly short, silent audio
95     // sample so that MediaSessionService and AudioPlaybackStateMonitor will believe that we're the
96     // current active player and send the Bluetooth process media events. This allows AVRCP
97     // controller to create a MediaSession and handle the events if it would like. The player and
98     // session requirement is a restriction currently imposed by the media framework code and could
99     // be reconsidered in the future.
100     private MediaPlayer mMediaPlayer = null;
101 
102     // Focus changes when we are currently holding focus.
103     private OnAudioFocusChangeListener mAudioFocusListener = new OnAudioFocusChangeListener() {
104         @Override
105         public void onAudioFocusChange(int focusChange) {
106             if (DBG) {
107                 Log.d(TAG, "onAudioFocusChangeListener focuschange " + focusChange);
108             }
109             A2dpSinkStreamHandler.this.obtainMessage(AUDIO_FOCUS_CHANGE, focusChange)
110                     .sendToTarget();
111         }
112     };
113 
A2dpSinkStreamHandler(A2dpSinkService a2dpSinkService, Context context)114     public A2dpSinkStreamHandler(A2dpSinkService a2dpSinkService, Context context) {
115         mA2dpSinkService = a2dpSinkService;
116         mContext = context;
117         mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
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 = mContext.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(mContext, 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         mA2dpSinkService.informAudioFocusStateNative(STATE_FOCUS_GRANTED);
346         mA2dpSinkService.informAudioTrackGainNative(1.0f);
347         requestMediaKeyFocus();
348     }
349 
stopFluorideStreaming()350     private void stopFluorideStreaming() {
351         releaseMediaKeyFocus();
352         mA2dpSinkService.informAudioFocusStateNative(STATE_FOCUS_LOST);
353     }
354 
setFluorideAudioTrackGain(float gain)355     private void setFluorideAudioTrackGain(float gain) {
356         mA2dpSinkService.informAudioTrackGainNative(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<BluetoothHeadsetClientCall> currentCalls =
376                     headsetClientService.getCurrentCalls(targetDevice);
377             if (currentCalls == null) return false;
378             return currentCalls.size() > 0;
379         }
380         return false;
381     }
382 
getAudioFocus()383     synchronized int getAudioFocus() {
384         return mAudioFocus;
385     }
386 
isIotDevice()387     private boolean isIotDevice() {
388         return mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_EMBEDDED);
389     }
390 
isTvDevice()391     private boolean isTvDevice() {
392         return mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_LEANBACK);
393     }
394 
shouldRequestFocus()395     private boolean shouldRequestFocus() {
396         return mContext.getResources()
397                 .getBoolean(R.bool.a2dp_sink_automatically_request_audio_focus);
398     }
399 
400 }
401