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