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