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