• 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.Context;
20 import android.media.AudioAttributes;
21 import android.media.AudioManager;
22 import android.media.AudioManager.OnAudioFocusChangeListener;
23 import android.os.Handler;
24 import android.os.Message;
25 import android.util.Log;
26 
27 import com.android.bluetooth.avrcpcontroller.AvrcpControllerService;
28 import com.android.bluetooth.R;
29 
30 /**
31  * Bluetooth A2DP SINK Streaming Handler.
32  *
33  * This handler defines how the stack behaves once the A2DP connection is established and both
34  * devices are ready for streaming. For simplification we assume that the connection can either
35  * stream music immediately (i.e. data packets coming in or have potential to come in) or it cannot
36  * stream (i.e. Idle and Open states are treated alike). See Fig 4-1 of GAVDP Spec 1.0.
37  *
38  * Note: There are several different audio tracks that a connected phone may like to transmit over
39  * the A2DP stream including Music, Navigation, Assistant, and Notifications.  Music is the only
40  * track that is almost always accompanied with an AVRCP play/pause command.
41  *
42  * Streaming is initiated by either an explicit play command from user interaction or audio coming
43  * from the phone.  Streaming is terminated when either the user pauses the audio, the audio stream
44  * from the phone ends, the phone disconnects, or audio focus is lost.  During playback if there is
45  * a change to audio focus playback may be temporarily paused and then resumed when focus is
46  * restored.
47  */
48 public class A2dpSinkStreamHandler extends Handler {
49     private static final boolean DBG = false;
50     private static final String TAG = "A2dpSinkStreamHandler";
51 
52     // Configuration Variables
53     private static final int DEFAULT_DUCK_PERCENT = 25;
54 
55     // Incoming events.
56     public static final int SRC_STR_START = 0; // Audio stream from remote device started
57     public static final int SRC_STR_STOP = 1; // Audio stream from remote device stopped
58     public static final int SNK_PLAY = 2; // Play command was generated from local device
59     public static final int SNK_PAUSE = 3; // Pause command was generated from local device
60     public static final int SRC_PLAY = 4; // Play command was generated from remote device
61     public static final int SRC_PAUSE = 5; // Pause command was generated from remote device
62     public static final int DISCONNECT = 6; // Remote device was disconnected
63     public static final int AUDIO_FOCUS_CHANGE = 7; // Audio focus callback with associated change
64 
65     // Used to indicate focus lost
66     private static final int STATE_FOCUS_LOST = 0;
67     // Used to inform bluedroid that focus is granted
68     private static final int STATE_FOCUS_GRANTED = 1;
69 
70     // Private variables.
71     private A2dpSinkStateMachine mA2dpSinkSm;
72     private Context mContext;
73     private AudioManager mAudioManager;
74     // Keep track if the remote device is providing audio
75     private boolean mStreamAvailable = false;
76     private boolean mSentPause = false;
77     // Keep track of the relevant audio focus (None, Transient, Gain)
78     private int mAudioFocus = AudioManager.AUDIOFOCUS_NONE;
79 
80     // Focus changes when we are currently holding focus.
81     private OnAudioFocusChangeListener mAudioFocusListener = new OnAudioFocusChangeListener() {
82         public void onAudioFocusChange(int focusChange) {
83             if (DBG) {
84                 Log.d(TAG, "onAudioFocusChangeListener focuschange " + focusChange);
85             }
86             A2dpSinkStreamHandler.this.obtainMessage(AUDIO_FOCUS_CHANGE, focusChange)
87                     .sendToTarget();
88         }
89     };
90 
A2dpSinkStreamHandler(A2dpSinkStateMachine a2dpSinkSm, Context context)91     public A2dpSinkStreamHandler(A2dpSinkStateMachine a2dpSinkSm, Context context) {
92         mA2dpSinkSm = a2dpSinkSm;
93         mContext = context;
94         mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
95     }
96 
97     @Override
handleMessage(Message message)98     public void handleMessage(Message message) {
99         if (DBG) {
100             Log.d(TAG, " process message: " + message.what);
101             Log.d(TAG, " audioFocus =  " + mAudioFocus);
102         }
103         switch (message.what) {
104             case SRC_STR_START:
105                 // Audio stream has started, stop it if we don't have focus.
106                 mStreamAvailable = true;
107                 if (mAudioFocus == AudioManager.AUDIOFOCUS_NONE) {
108                     sendAvrcpPause();
109                 } else {
110                     startAvrcpUpdates();
111                 }
112                 break;
113 
114             case SRC_STR_STOP:
115                 // Audio stream has stopped, maintain focus but stop avrcp updates.
116                 mStreamAvailable = false;
117                 stopAvrcpUpdates();
118                 break;
119 
120             case SNK_PLAY:
121                 // Local play command, gain focus and start avrcp updates.
122                 if (mAudioFocus == AudioManager.AUDIOFOCUS_NONE) {
123                     requestAudioFocus();
124                 }
125                 startAvrcpUpdates();
126                 break;
127 
128             case SNK_PAUSE:
129                 // Local pause command, maintain focus but stop avrcp updates.
130                 stopAvrcpUpdates();
131                 break;
132 
133             case SRC_PLAY:
134                 // Remote play command, if we have audio focus update avrcp, otherwise send pause.
135                 if (mAudioFocus == AudioManager.AUDIOFOCUS_NONE) {
136                     sendAvrcpPause();
137                 } else {
138                     startAvrcpUpdates();
139                 }
140                 break;
141 
142             case SRC_PAUSE:
143                 // Remote pause command, stop avrcp updates.
144                 stopAvrcpUpdates();
145                 break;
146 
147             case DISCONNECT:
148                 // Remote device has disconnected, restore everything to default state.
149                 sendAvrcpPause();
150                 stopAvrcpUpdates();
151                 abandonAudioFocus();
152                 mSentPause = false;
153                 break;
154 
155             case AUDIO_FOCUS_CHANGE:
156                 // message.obj is the newly granted audio focus.
157                 switch ((int) message.obj) {
158                     case AudioManager.AUDIOFOCUS_GAIN:
159                         // Begin playing audio, if we paused the remote, send a play now.
160                         startAvrcpUpdates();
161                         startFluorideStreaming();
162                         if (mSentPause) {
163                             sendAvrcpPlay();
164                             mSentPause = false;
165                         }
166                         break;
167 
168                     case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
169                         // Make the volume duck.
170                         int duckPercent = mContext.getResources().getInteger(
171                                 R.integer.a2dp_sink_duck_percent);
172                         if (duckPercent < 0 || duckPercent > 100) {
173                             Log.e(TAG, "Invalid duck percent using default.");
174                             duckPercent = DEFAULT_DUCK_PERCENT;
175                         }
176                         float duckRatio = (duckPercent / 100.0f);
177                         if (DBG) {
178                             Log.d(TAG, "Setting reduce gain on transient loss gain=" + duckRatio);
179                         }
180                         setFluorideAudioTrackGain(duckRatio);
181                         break;
182 
183                     case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
184                         // Temporary loss of focus, if we are actively streaming pause the remote
185                         // and make sure we resume playback when we regain focus.
186                         if (mStreamAvailable) {
187                             sendAvrcpPause();
188                             mSentPause = true;
189                         }
190                         stopFluorideStreaming();
191                         break;
192 
193                     case AudioManager.AUDIOFOCUS_LOSS:
194                         // Permanent loss of focus probably due to another audio app, abandon focus
195                         // and stop playback.
196                         mAudioFocus = AudioManager.AUDIOFOCUS_NONE;
197                         abandonAudioFocus();
198                         sendAvrcpPause();
199                         break;
200                 }
201                 break;
202 
203             default:
204                 Log.w(TAG, "Received unexpected event: " + message.what);
205         }
206     }
207 
208     /**
209      * Utility functions.
210      */
requestAudioFocus()211     private int requestAudioFocus() {
212         int focusRequestStatus = mAudioManager.requestAudioFocus(
213                 mAudioFocusListener, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN);
214         // If the request is granted begin streaming immediately and schedule an upgrade.
215         if (focusRequestStatus == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
216             startAvrcpUpdates();
217             startFluorideStreaming();
218             mAudioFocus = AudioManager.AUDIOFOCUS_GAIN;
219         }
220         return focusRequestStatus;
221     }
222 
223 
abandonAudioFocus()224     private void abandonAudioFocus() {
225         stopFluorideStreaming();
226         mAudioManager.abandonAudioFocus(mAudioFocusListener);
227         mAudioFocus = AudioManager.AUDIOFOCUS_NONE;
228     }
229 
startFluorideStreaming()230     private void startFluorideStreaming() {
231         mA2dpSinkSm.informAudioFocusStateNative(STATE_FOCUS_GRANTED);
232         mA2dpSinkSm.informAudioTrackGainNative(1.0f);
233     }
234 
stopFluorideStreaming()235     private void stopFluorideStreaming() {
236         mA2dpSinkSm.informAudioFocusStateNative(STATE_FOCUS_LOST);
237     }
238 
setFluorideAudioTrackGain(float gain)239     private void setFluorideAudioTrackGain(float gain) {
240         mA2dpSinkSm.informAudioTrackGainNative(gain);
241     }
242 
startAvrcpUpdates()243     private void startAvrcpUpdates() {
244         // Since AVRCP gets started after A2DP we may need to request it later in cycle.
245         AvrcpControllerService avrcpService = AvrcpControllerService.getAvrcpControllerService();
246 
247         if (DBG) {
248             Log.d(TAG, "startAvrcpUpdates");
249         }
250         if (avrcpService != null && avrcpService.getConnectedDevices().size() == 1) {
251             avrcpService.startAvrcpUpdates();
252         } else {
253             Log.e(TAG, "startAvrcpUpdates failed because of connection.");
254         }
255     }
256 
stopAvrcpUpdates()257     private void stopAvrcpUpdates() {
258         // Since AVRCP gets started after A2DP we may need to request it later in cycle.
259         AvrcpControllerService avrcpService = AvrcpControllerService.getAvrcpControllerService();
260 
261         if (DBG) {
262             Log.d(TAG, "stopAvrcpUpdates");
263         }
264         if (avrcpService != null && avrcpService.getConnectedDevices().size() == 1) {
265             avrcpService.stopAvrcpUpdates();
266         } else {
267             Log.e(TAG, "stopAvrcpUpdates failed because of connection.");
268         }
269     }
270 
sendAvrcpPause()271     private void sendAvrcpPause() {
272         // Since AVRCP gets started after A2DP we may need to request it later in cycle.
273         AvrcpControllerService avrcpService = AvrcpControllerService.getAvrcpControllerService();
274 
275         if (DBG) {
276             Log.d(TAG, "sendAvrcpPause");
277         }
278         if (avrcpService != null && avrcpService.getConnectedDevices().size() == 1) {
279             if (DBG) {
280                 Log.d(TAG, "Pausing AVRCP.");
281             }
282             avrcpService.sendPassThroughCmd(avrcpService.getConnectedDevices().get(0),
283                     AvrcpControllerService.PASS_THRU_CMD_ID_PAUSE,
284                     AvrcpControllerService.KEY_STATE_PRESSED);
285             avrcpService.sendPassThroughCmd(avrcpService.getConnectedDevices().get(0),
286                     AvrcpControllerService.PASS_THRU_CMD_ID_PAUSE,
287                     AvrcpControllerService.KEY_STATE_RELEASED);
288         } else {
289             Log.e(TAG, "Passthrough not sent, connection un-available.");
290         }
291     }
292 
sendAvrcpPlay()293     private void sendAvrcpPlay() {
294         // Since AVRCP gets started after A2DP we may need to request it later in cycle.
295         AvrcpControllerService avrcpService = AvrcpControllerService.getAvrcpControllerService();
296 
297         if (DBG) {
298             Log.d(TAG, "sendAvrcpPlay");
299         }
300         if (avrcpService != null && avrcpService.getConnectedDevices().size() == 1) {
301             if (DBG) {
302                 Log.d(TAG, "Playing AVRCP.");
303             }
304             avrcpService.sendPassThroughCmd(avrcpService.getConnectedDevices().get(0),
305                     AvrcpControllerService.PASS_THRU_CMD_ID_PLAY,
306                     AvrcpControllerService.KEY_STATE_PRESSED);
307             avrcpService.sendPassThroughCmd(avrcpService.getConnectedDevices().get(0),
308                     AvrcpControllerService.PASS_THRU_CMD_ID_PLAY,
309                     AvrcpControllerService.KEY_STATE_RELEASED);
310         } else {
311             Log.e(TAG, "Passthrough not sent, connection un-available.");
312         }
313     }
314 }
315