A2dpSinkStreamHandler.java revision 673e4dc38242dcdfcd294e720dd392d8b1fd68c1
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 17package com.android.bluetooth.a2dpsink; 18 19import android.bluetooth.BluetoothDevice; 20import android.content.Context; 21import android.media.AudioAttributes; 22import android.media.AudioManager; 23import android.media.AudioManager.OnAudioFocusChangeListener; 24import android.os.Handler; 25import android.os.Message; 26import android.util.Log; 27 28import com.android.bluetooth.avrcpcontroller.AvrcpControllerService; 29import com.android.bluetooth.R; 30 31import java.util.List; 32 33/** 34 * Bluetooth A2DP SINK Streaming Handler. 35 * 36 * This handler defines how the stack behaves once the A2DP connection is established and both 37 * devices are ready for streaming. For simplification we assume that the connection can either 38 * stream music immediately (i.e. data packets coming in or have potential to come in) or it cannot 39 * stream (i.e. Idle and Open states are treated alike). See Fig 4-1 of GAVDP Spec 1.0. 40 * 41 * Note: There are several different audio tracks that a connected phone may like to transmit over 42 * the A2DP stream including Music, Navigation, Assistant, and Notifications. Music is the only 43 * track that is almost always accompanied with an AVRCP play/pause command. 44 * 45 * Streaming is initiated by either an explicit play command from user interaction or audio coming 46 * from the phone. Streaming is terminated when either the user pauses the audio, the audio stream 47 * from the phone ends, the phone disconnects, or audio focus is lost. During playback if there is 48 * a change to audio focus playback may be temporarily paused and then resumed when focus is 49 * restored. 50 */ 51public class A2dpSinkStreamHandler extends Handler { 52 private static final boolean DBG = false; 53 private static final String TAG = "A2dpSinkStreamHandler"; 54 55 // Configuration Variables 56 private static final int DEFAULT_DUCK_PERCENT = 25; 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 68 // Used to indicate focus lost 69 private static final int STATE_FOCUS_LOST = 0; 70 // Used to inform bluedroid that focus is granted 71 private static final int STATE_FOCUS_GRANTED = 1; 72 73 // Private variables. 74 private A2dpSinkStateMachine mA2dpSinkSm; 75 private Context mContext; 76 private AudioManager mAudioManager; 77 // Keep track if the remote device is providing audio 78 private boolean mStreamAvailable = false; 79 private boolean mSentPause = false; 80 // Keep track of the relevant audio focus (None, Transient, Gain) 81 private int mAudioFocus = AudioManager.AUDIOFOCUS_NONE; 82 83 // Focus changes when we are currently holding focus. 84 private OnAudioFocusChangeListener mAudioFocusListener = new OnAudioFocusChangeListener() { 85 public void onAudioFocusChange(int focusChange) { 86 if (DBG) { 87 Log.d(TAG, "onAudioFocusChangeListener focuschange " + focusChange); 88 } 89 A2dpSinkStreamHandler.this.obtainMessage(AUDIO_FOCUS_CHANGE, focusChange) 90 .sendToTarget(); 91 } 92 }; 93 94 public A2dpSinkStreamHandler(A2dpSinkStateMachine a2dpSinkSm, Context context) { 95 mA2dpSinkSm = a2dpSinkSm; 96 mContext = context; 97 mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); 98 } 99 100 @Override 101 public void handleMessage(Message message) { 102 if (DBG) { 103 Log.d(TAG, " process message: " + message.what); 104 Log.d(TAG, " audioFocus = " + mAudioFocus); 105 } 106 switch (message.what) { 107 case SRC_STR_START: 108 // Audio stream has started, stop it if we don't have focus. 109 mStreamAvailable = true; 110 if (mAudioFocus == AudioManager.AUDIOFOCUS_NONE) { 111 sendAvrcpPause(); 112 } else { 113 startAvrcpUpdates(); 114 } 115 break; 116 117 case SRC_STR_STOP: 118 // Audio stream has stopped, maintain focus but stop avrcp updates. 119 mStreamAvailable = false; 120 stopAvrcpUpdates(); 121 break; 122 123 case SNK_PLAY: 124 // Local play command, gain focus and start avrcp updates. 125 if (mAudioFocus == AudioManager.AUDIOFOCUS_NONE) { 126 requestAudioFocus(); 127 } 128 startAvrcpUpdates(); 129 break; 130 131 case SNK_PAUSE: 132 // Local pause command, maintain focus but stop avrcp updates. 133 stopAvrcpUpdates(); 134 break; 135 136 case SRC_PLAY: 137 // Remote play command, if we have audio focus update avrcp, otherwise send pause. 138 if (mAudioFocus == AudioManager.AUDIOFOCUS_NONE) { 139 sendAvrcpPause(); 140 } else { 141 startAvrcpUpdates(); 142 } 143 break; 144 145 case SRC_PAUSE: 146 // Remote pause command, stop avrcp updates. 147 stopAvrcpUpdates(); 148 break; 149 150 case DISCONNECT: 151 // Remote device has disconnected, restore everything to default state. 152 sendAvrcpPause(); 153 stopAvrcpUpdates(); 154 abandonAudioFocus(); 155 mSentPause = false; 156 break; 157 158 case AUDIO_FOCUS_CHANGE: 159 // message.obj is the newly granted audio focus. 160 switch ((int) message.obj) { 161 case AudioManager.AUDIOFOCUS_GAIN: 162 // Begin playing audio, if we paused the remote, send a play now. 163 startAvrcpUpdates(); 164 startFluorideStreaming(); 165 if (mSentPause) { 166 sendAvrcpPlay(); 167 mSentPause = false; 168 } 169 break; 170 171 case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK: 172 // Make the volume duck. 173 int duckPercent = mContext.getResources().getInteger( 174 R.integer.a2dp_sink_duck_percent); 175 if (duckPercent < 0 || duckPercent > 100) { 176 Log.e(TAG, "Invalid duck percent using default."); 177 duckPercent = DEFAULT_DUCK_PERCENT; 178 } 179 float duckRatio = (duckPercent / 100.0f); 180 if (DBG) { 181 Log.d(TAG, "Setting reduce gain on transient loss gain=" + duckRatio); 182 } 183 setFluorideAudioTrackGain(duckRatio); 184 break; 185 186 case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT: 187 // Temporary loss of focus, if we are actively streaming pause the remote 188 // and make sure we resume playback when we regain focus. 189 if (mStreamAvailable) { 190 sendAvrcpPause(); 191 mSentPause = true; 192 } 193 stopFluorideStreaming(); 194 break; 195 196 case AudioManager.AUDIOFOCUS_LOSS: 197 // Permanent loss of focus probably due to another audio app, abandon focus 198 // and stop playback. 199 mAudioFocus = AudioManager.AUDIOFOCUS_NONE; 200 abandonAudioFocus(); 201 sendAvrcpPause(); 202 break; 203 } 204 break; 205 206 default: 207 Log.w(TAG, "Received unexpected event: " + message.what); 208 } 209 } 210 211 /** 212 * Utility functions. 213 */ 214 private int requestAudioFocus() { 215 int focusRequestStatus = mAudioManager.requestAudioFocus( 216 mAudioFocusListener, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN); 217 // If the request is granted begin streaming immediately and schedule an upgrade. 218 if (focusRequestStatus == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) { 219 startAvrcpUpdates(); 220 startFluorideStreaming(); 221 mAudioFocus = AudioManager.AUDIOFOCUS_GAIN; 222 } 223 return focusRequestStatus; 224 } 225 226 227 private void abandonAudioFocus() { 228 stopFluorideStreaming(); 229 mAudioManager.abandonAudioFocus(mAudioFocusListener); 230 mAudioFocus = AudioManager.AUDIOFOCUS_NONE; 231 } 232 233 private void startFluorideStreaming() { 234 mA2dpSinkSm.informAudioFocusStateNative(STATE_FOCUS_GRANTED); 235 mA2dpSinkSm.informAudioTrackGainNative(1.0f); 236 } 237 238 private void stopFluorideStreaming() { 239 mA2dpSinkSm.informAudioFocusStateNative(STATE_FOCUS_LOST); 240 } 241 242 private void setFluorideAudioTrackGain(float gain) { 243 mA2dpSinkSm.informAudioTrackGainNative(gain); 244 } 245 246 private void startAvrcpUpdates() { 247 // Since AVRCP gets started after A2DP we may need to request it later in cycle. 248 AvrcpControllerService avrcpService = AvrcpControllerService.getAvrcpControllerService(); 249 250 if (DBG) { 251 Log.d(TAG, "startAvrcpUpdates"); 252 } 253 if (avrcpService != null && avrcpService.getConnectedDevices().size() == 1) { 254 avrcpService.startAvrcpUpdates(); 255 } else { 256 Log.e(TAG, "startAvrcpUpdates failed because of connection."); 257 } 258 } 259 260 private void stopAvrcpUpdates() { 261 // Since AVRCP gets started after A2DP we may need to request it later in cycle. 262 AvrcpControllerService avrcpService = AvrcpControllerService.getAvrcpControllerService(); 263 264 if (DBG) { 265 Log.d(TAG, "stopAvrcpUpdates"); 266 } 267 if (avrcpService != null && avrcpService.getConnectedDevices().size() == 1) { 268 avrcpService.stopAvrcpUpdates(); 269 } else { 270 Log.e(TAG, "stopAvrcpUpdates failed because of connection."); 271 } 272 } 273 274 private void sendAvrcpPause() { 275 // Since AVRCP gets started after A2DP we may need to request it later in cycle. 276 AvrcpControllerService avrcpService = AvrcpControllerService.getAvrcpControllerService(); 277 278 if (DBG) { 279 Log.d(TAG, "sendAvrcpPause"); 280 } 281 if (avrcpService != null) { 282 List<BluetoothDevice> connectedDevices = avrcpService.getConnectedDevices(); 283 if (!connectedDevices.isEmpty()) { 284 BluetoothDevice targetDevice = connectedDevices.get(0); 285 if (DBG) { 286 Log.d(TAG, "Pausing AVRCP."); 287 } 288 avrcpService.sendPassThroughCmd(targetDevice, 289 AvrcpControllerService.PASS_THRU_CMD_ID_PAUSE, 290 AvrcpControllerService.KEY_STATE_PRESSED); 291 avrcpService.sendPassThroughCmd(targetDevice, 292 AvrcpControllerService.PASS_THRU_CMD_ID_PAUSE, 293 AvrcpControllerService.KEY_STATE_RELEASED); 294 } 295 } else { 296 Log.e(TAG, "Passthrough not sent, connection un-available."); 297 } 298 } 299 300 private void sendAvrcpPlay() { 301 // Since AVRCP gets started after A2DP we may need to request it later in cycle. 302 AvrcpControllerService avrcpService = AvrcpControllerService.getAvrcpControllerService(); 303 304 if (DBG) { 305 Log.d(TAG, "sendAvrcpPlay"); 306 } 307 if (avrcpService != null) { 308 List<BluetoothDevice> connectedDevices = avrcpService.getConnectedDevices(); 309 if (!connectedDevices.isEmpty()) { 310 BluetoothDevice targetDevice = connectedDevices.get(0); 311 if (DBG) { 312 Log.d(TAG, "Playing AVRCP."); 313 } 314 avrcpService.sendPassThroughCmd(targetDevice, 315 AvrcpControllerService.PASS_THRU_CMD_ID_PLAY, 316 AvrcpControllerService.KEY_STATE_PRESSED); 317 avrcpService.sendPassThroughCmd(targetDevice, 318 AvrcpControllerService.PASS_THRU_CMD_ID_PLAY, 319 AvrcpControllerService.KEY_STATE_RELEASED); 320 } 321 } else { 322 Log.e(TAG, "Passthrough not sent, connection un-available."); 323 } 324 } 325} 326