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