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