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