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