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.os.Handler; 27 import android.os.Message; 28 import android.util.Log; 29 30 import com.android.bluetooth.R; 31 import com.android.bluetooth.avrcpcontroller.AvrcpControllerService; 32 33 import java.util.List; 34 35 /** 36 * Bluetooth A2DP SINK Streaming Handler. 37 * 38 * This handler defines how the stack behaves once the A2DP connection is established and both 39 * devices are ready for streaming. For simplification we assume that the connection can either 40 * stream music immediately (i.e. data packets coming in or have potential to come in) or it cannot 41 * stream (i.e. Idle and Open states are treated alike). See Fig 4-1 of GAVDP Spec 1.0. 42 * 43 * Note: There are several different audio tracks that a connected phone may like to transmit over 44 * the A2DP stream including Music, Navigation, Assistant, and Notifications. Music is the only 45 * track that is almost always accompanied with an AVRCP play/pause command. 46 * 47 * Streaming is initiated by either an explicit play command from user interaction or audio coming 48 * from the phone. Streaming is terminated when either the user pauses the audio, the audio stream 49 * from the phone ends, the phone disconnects, or audio focus is lost. During playback if there is 50 * a change to audio focus playback may be temporarily paused and then resumed when focus is 51 * restored. 52 */ 53 public class A2dpSinkStreamHandler extends Handler { 54 private static final boolean DBG = false; 55 private static final String TAG = "A2dpSinkStreamHandler"; 56 57 // Configuration Variables 58 private static final int DEFAULT_DUCK_PERCENT = 25; 59 private static final int SETTLE_TIMEOUT = 1000; 60 61 // Incoming events. 62 public static final int SRC_STR_START = 0; // Audio stream from remote device started 63 public static final int SRC_STR_STOP = 1; // Audio stream from remote device stopped 64 public static final int SNK_PLAY = 2; // Play command was generated from local device 65 public static final int SNK_PAUSE = 3; // Pause command was generated from local device 66 public static final int SRC_PLAY = 4; // Play command was generated from remote device 67 public static final int SRC_PAUSE = 5; // Pause command was generated from remote device 68 public static final int DISCONNECT = 6; // Remote device was disconnected 69 public static final int AUDIO_FOCUS_CHANGE = 7; // Audio focus callback with associated change 70 public static final int REQUEST_FOCUS = 8; // Request focus when the media service is active 71 public static final int DELAYED_RESUME = 9; // If a call just ended allow stack time to settle 72 73 // Used to indicate focus lost 74 private static final int STATE_FOCUS_LOST = 0; 75 // Used to inform bluedroid that focus is granted 76 private static final int STATE_FOCUS_GRANTED = 1; 77 78 // Private variables. 79 private A2dpSinkStateMachine mA2dpSinkSm; 80 private Context mContext; 81 private AudioManager mAudioManager; 82 // Keep track if the remote device is providing audio 83 private boolean mStreamAvailable = false; 84 private boolean mSentPause = false; 85 // Keep track of the relevant audio focus (None, Transient, Gain) 86 private int mAudioFocus = AudioManager.AUDIOFOCUS_NONE; 87 88 // Focus changes when we are currently holding focus. 89 private OnAudioFocusChangeListener mAudioFocusListener = new OnAudioFocusChangeListener() { 90 @Override 91 public void onAudioFocusChange(int focusChange) { 92 if (DBG) { 93 Log.d(TAG, "onAudioFocusChangeListener focuschange " + focusChange); 94 } 95 A2dpSinkStreamHandler.this.obtainMessage(AUDIO_FOCUS_CHANGE, focusChange) 96 .sendToTarget(); 97 } 98 }; 99 100 public A2dpSinkStreamHandler(A2dpSinkStateMachine a2dpSinkSm, Context context) { 101 mA2dpSinkSm = a2dpSinkSm; 102 mContext = context; 103 mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); 104 } 105 106 @Override 107 public void handleMessage(Message message) { 108 if (DBG) { 109 Log.d(TAG, " process message: " + message.what); 110 Log.d(TAG, " audioFocus = " + mAudioFocus); 111 } 112 switch (message.what) { 113 case SRC_STR_START: 114 mStreamAvailable = true; 115 // Always request audio focus if on TV. 116 if (isTvDevice()) { 117 if (mAudioFocus == AudioManager.AUDIOFOCUS_NONE) { 118 requestAudioFocus(); 119 } 120 } 121 // Audio stream has started, stop it if we don't have focus. 122 if (mAudioFocus == AudioManager.AUDIOFOCUS_NONE) { 123 sendAvrcpPause(); 124 } else { 125 startAvrcpUpdates(); 126 } 127 break; 128 129 case SRC_STR_STOP: 130 // Audio stream has stopped, maintain focus but stop avrcp updates. 131 mStreamAvailable = false; 132 stopAvrcpUpdates(); 133 break; 134 135 case SNK_PLAY: 136 // Local play command, gain focus and start avrcp updates. 137 if (mAudioFocus == AudioManager.AUDIOFOCUS_NONE) { 138 requestAudioFocus(); 139 } 140 startAvrcpUpdates(); 141 break; 142 143 case SNK_PAUSE: 144 // Local pause command, maintain focus but stop avrcp updates. 145 stopAvrcpUpdates(); 146 break; 147 148 case SRC_PLAY: 149 // Remote play command. 150 // If is an iot device gain focus and start avrcp updates. 151 if (isIotDevice() || isTvDevice()) { 152 if (mAudioFocus == AudioManager.AUDIOFOCUS_NONE) { 153 requestAudioFocus(); 154 } 155 startAvrcpUpdates(); 156 break; 157 } 158 // Otherwise, pause if we don't have focus 159 if (mAudioFocus == AudioManager.AUDIOFOCUS_NONE) { 160 sendAvrcpPause(); 161 } else { 162 startAvrcpUpdates(); 163 } 164 break; 165 166 case SRC_PAUSE: 167 // Remote pause command, stop avrcp updates. 168 stopAvrcpUpdates(); 169 break; 170 171 case REQUEST_FOCUS: 172 if (mAudioFocus == AudioManager.AUDIOFOCUS_NONE) { 173 requestAudioFocus(); 174 } 175 break; 176 177 case DISCONNECT: 178 // Remote device has disconnected, restore everything to default state. 179 stopAvrcpUpdates(); 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 // Begin playing audio, if we paused the remote, send a play now. 188 startAvrcpUpdates(); 189 startFluorideStreaming(); 190 if (mSentPause) { 191 sendMessageDelayed(obtainMessage(DELAYED_RESUME), SETTLE_TIMEOUT); 192 } 193 break; 194 195 case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK: 196 // Make the volume duck. 197 int duckPercent = mContext.getResources() 198 .getInteger(R.integer.a2dp_sink_duck_percent); 199 if (duckPercent < 0 || duckPercent > 100) { 200 Log.e(TAG, "Invalid duck percent using default."); 201 duckPercent = DEFAULT_DUCK_PERCENT; 202 } 203 float duckRatio = (duckPercent / 100.0f); 204 if (DBG) { 205 Log.d(TAG, "Setting reduce gain on transient loss gain=" + duckRatio); 206 } 207 setFluorideAudioTrackGain(duckRatio); 208 break; 209 210 case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT: 211 // Temporary loss of focus, if we are actively streaming pause the remote 212 // and make sure we resume playback when we regain focus. 213 if (mStreamAvailable) { 214 sendAvrcpPause(); 215 mSentPause = true; 216 } 217 stopFluorideStreaming(); 218 break; 219 220 case AudioManager.AUDIOFOCUS_LOSS: 221 // Permanent loss of focus probably due to another audio app, abandon focus 222 // and stop playback. 223 abandonAudioFocus(); 224 sendAvrcpPause(); 225 break; 226 } 227 break; 228 229 case DELAYED_RESUME: 230 // Resume playback after source and sink states settle. 231 sendAvrcpPlay(); 232 mSentPause = false; 233 break; 234 235 236 default: 237 Log.w(TAG, "Received unexpected event: " + message.what); 238 } 239 } 240 241 /** 242 * Utility functions. 243 */ 244 private synchronized int 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().setUsage(AudioAttributes.USAGE_MEDIA) 249 .setContentType(AudioAttributes.CONTENT_TYPE_UNKNOWN) 250 .build(); 251 // Bluetooth ducking is handled at the native layer so tell the Audio Manger to notify the 252 // focus change listener via .setWillPauseWhenDucked(). 253 AudioFocusRequest focusRequest = 254 new AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN).setAudioAttributes( 255 streamAttributes) 256 .setWillPauseWhenDucked(true) 257 .setOnAudioFocusChangeListener(mAudioFocusListener, this) 258 .build(); 259 int focusRequestStatus = mAudioManager.requestAudioFocus(focusRequest); 260 // If the request is granted begin streaming immediately and schedule an upgrade. 261 if (focusRequestStatus == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) { 262 startAvrcpUpdates(); 263 startFluorideStreaming(); 264 mAudioFocus = AudioManager.AUDIOFOCUS_GAIN; 265 } 266 return focusRequestStatus; 267 } 268 269 270 private synchronized void abandonAudioFocus() { 271 stopFluorideStreaming(); 272 mAudioManager.abandonAudioFocus(mAudioFocusListener); 273 mAudioFocus = AudioManager.AUDIOFOCUS_NONE; 274 } 275 276 private void startFluorideStreaming() { 277 mA2dpSinkSm.informAudioFocusStateNative(STATE_FOCUS_GRANTED); 278 mA2dpSinkSm.informAudioTrackGainNative(1.0f); 279 } 280 281 private void stopFluorideStreaming() { 282 mA2dpSinkSm.informAudioFocusStateNative(STATE_FOCUS_LOST); 283 } 284 285 private void setFluorideAudioTrackGain(float gain) { 286 mA2dpSinkSm.informAudioTrackGainNative(gain); 287 } 288 289 private void startAvrcpUpdates() { 290 // Since AVRCP gets started after A2DP we may need to request it later in cycle. 291 AvrcpControllerService avrcpService = AvrcpControllerService.getAvrcpControllerService(); 292 293 if (DBG) { 294 Log.d(TAG, "startAvrcpUpdates"); 295 } 296 if (avrcpService != null && avrcpService.getConnectedDevices().size() == 1) { 297 avrcpService.startAvrcpUpdates(); 298 } else { 299 Log.e(TAG, "startAvrcpUpdates failed because of connection."); 300 } 301 } 302 303 private void stopAvrcpUpdates() { 304 // Since AVRCP gets started after A2DP we may need to request it later in cycle. 305 AvrcpControllerService avrcpService = AvrcpControllerService.getAvrcpControllerService(); 306 307 if (DBG) { 308 Log.d(TAG, "stopAvrcpUpdates"); 309 } 310 if (avrcpService != null && avrcpService.getConnectedDevices().size() == 1) { 311 avrcpService.stopAvrcpUpdates(); 312 } else { 313 Log.e(TAG, "stopAvrcpUpdates failed because of connection."); 314 } 315 } 316 317 private void sendAvrcpPause() { 318 // Since AVRCP gets started after A2DP we may need to request it later in cycle. 319 AvrcpControllerService avrcpService = AvrcpControllerService.getAvrcpControllerService(); 320 321 if (DBG) { 322 Log.d(TAG, "sendAvrcpPause"); 323 } 324 if (avrcpService != null) { 325 List<BluetoothDevice> connectedDevices = avrcpService.getConnectedDevices(); 326 if (!connectedDevices.isEmpty()) { 327 BluetoothDevice targetDevice = connectedDevices.get(0); 328 if (DBG) { 329 Log.d(TAG, "Pausing AVRCP."); 330 } 331 avrcpService.sendPassThroughCmd(targetDevice, 332 AvrcpControllerService.PASS_THRU_CMD_ID_PAUSE, 333 AvrcpControllerService.KEY_STATE_PRESSED); 334 avrcpService.sendPassThroughCmd(targetDevice, 335 AvrcpControllerService.PASS_THRU_CMD_ID_PAUSE, 336 AvrcpControllerService.KEY_STATE_RELEASED); 337 } 338 } else { 339 Log.e(TAG, "Passthrough not sent, connection un-available."); 340 } 341 } 342 343 private void sendAvrcpPlay() { 344 // Since AVRCP gets started after A2DP we may need to request it later in cycle. 345 AvrcpControllerService avrcpService = AvrcpControllerService.getAvrcpControllerService(); 346 347 if (DBG) { 348 Log.d(TAG, "sendAvrcpPlay"); 349 } 350 if (avrcpService != null) { 351 List<BluetoothDevice> connectedDevices = avrcpService.getConnectedDevices(); 352 if (!connectedDevices.isEmpty()) { 353 BluetoothDevice targetDevice = connectedDevices.get(0); 354 if (DBG) { 355 Log.d(TAG, "Playing AVRCP."); 356 } 357 avrcpService.sendPassThroughCmd(targetDevice, 358 AvrcpControllerService.PASS_THRU_CMD_ID_PLAY, 359 AvrcpControllerService.KEY_STATE_PRESSED); 360 avrcpService.sendPassThroughCmd(targetDevice, 361 AvrcpControllerService.PASS_THRU_CMD_ID_PLAY, 362 AvrcpControllerService.KEY_STATE_RELEASED); 363 } 364 } else { 365 Log.e(TAG, "Passthrough not sent, connection un-available."); 366 } 367 } 368 369 synchronized int getAudioFocus() { 370 return mAudioFocus; 371 } 372 373 private boolean isIotDevice() { 374 return mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_EMBEDDED); 375 } 376 377 private boolean isTvDevice() { 378 return mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_LEANBACK); 379 } 380 381 } 382