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