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