1 /* 2 * Copyright (C) 2014 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.BluetoothAudioConfig; 20 import android.bluetooth.BluetoothDevice; 21 import android.bluetooth.BluetoothProfile; 22 import android.bluetooth.IBluetoothA2dpSink; 23 import android.content.Intent; 24 import android.provider.Settings; 25 import android.util.Log; 26 27 import com.android.bluetooth.Utils; 28 import com.android.bluetooth.a2dpsink.mbs.A2dpMediaBrowserService; 29 import com.android.bluetooth.avrcpcontroller.AvrcpControllerService; 30 import com.android.bluetooth.btservice.ProfileService; 31 32 import java.util.ArrayList; 33 import java.util.List; 34 35 /** 36 * Provides Bluetooth A2DP Sink profile, as a service in the Bluetooth application. 37 * @hide 38 */ 39 public class A2dpSinkService extends ProfileService { 40 private static final boolean DBG = true; 41 private static final String TAG = "A2dpSinkService"; 42 43 private A2dpSinkStateMachine mStateMachine; 44 private static A2dpSinkService sA2dpSinkService; 45 46 @Override 47 protected IProfileServiceBinder initBinder() { 48 return new BluetoothA2dpSinkBinder(this); 49 } 50 51 @Override 52 protected boolean start() { 53 if (DBG) { 54 Log.d(TAG, "start()"); 55 } 56 // Start the media browser service. 57 Intent startIntent = new Intent(this, A2dpMediaBrowserService.class); 58 startService(startIntent); 59 mStateMachine = A2dpSinkStateMachine.make(this, this); 60 setA2dpSinkService(this); 61 return true; 62 } 63 64 @Override 65 protected boolean stop() { 66 if (DBG) { 67 Log.d(TAG, "stop()"); 68 } 69 setA2dpSinkService(null); 70 if (mStateMachine != null) { 71 mStateMachine.doQuit(); 72 } 73 Intent stopIntent = new Intent(this, A2dpMediaBrowserService.class); 74 stopService(stopIntent); 75 return true; 76 } 77 78 @Override 79 protected void cleanup() { 80 if (mStateMachine != null) { 81 mStateMachine.cleanup(); 82 } 83 } 84 85 //API Methods 86 87 public static synchronized A2dpSinkService getA2dpSinkService() { 88 if (sA2dpSinkService == null) { 89 Log.w(TAG, "getA2dpSinkService(): service is null"); 90 return null; 91 } 92 if (!sA2dpSinkService.isAvailable()) { 93 Log.w(TAG, "getA2dpSinkService(): service is not available "); 94 return null; 95 } 96 return sA2dpSinkService; 97 } 98 99 private static synchronized void setA2dpSinkService(A2dpSinkService instance) { 100 if (DBG) { 101 Log.d(TAG, "setA2dpSinkService(): set to: " + instance); 102 } 103 sA2dpSinkService = instance; 104 } 105 106 public boolean connect(BluetoothDevice device) { 107 enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH ADMIN permission"); 108 109 int connectionState = mStateMachine.getConnectionState(device); 110 if (connectionState == BluetoothProfile.STATE_CONNECTED 111 || connectionState == BluetoothProfile.STATE_CONNECTING) { 112 return false; 113 } 114 115 if (getPriority(device) == BluetoothProfile.PRIORITY_OFF) { 116 return false; 117 } 118 119 mStateMachine.sendMessage(A2dpSinkStateMachine.CONNECT, device); 120 return true; 121 } 122 123 boolean disconnect(BluetoothDevice device) { 124 enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH ADMIN permission"); 125 int connectionState = mStateMachine.getConnectionState(device); 126 if (connectionState != BluetoothProfile.STATE_CONNECTED 127 && connectionState != BluetoothProfile.STATE_CONNECTING) { 128 return false; 129 } 130 131 mStateMachine.sendMessage(A2dpSinkStateMachine.DISCONNECT, device); 132 return true; 133 } 134 135 public List<BluetoothDevice> getConnectedDevices() { 136 enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); 137 return mStateMachine.getConnectedDevices(); 138 } 139 140 List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) { 141 enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); 142 return mStateMachine.getDevicesMatchingConnectionStates(states); 143 } 144 145 int getConnectionState(BluetoothDevice device) { 146 enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); 147 return mStateMachine.getConnectionState(device); 148 } 149 150 public boolean setPriority(BluetoothDevice device, int priority) { 151 enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission"); 152 Settings.Global.putInt(getContentResolver(), 153 Settings.Global.getBluetoothA2dpSrcPriorityKey(device.getAddress()), priority); 154 if (DBG) { 155 Log.d(TAG, "Saved priority " + device + " = " + priority); 156 } 157 return true; 158 } 159 160 public int getPriority(BluetoothDevice device) { 161 enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission"); 162 int priority = Settings.Global.getInt(getContentResolver(), 163 Settings.Global.getBluetoothA2dpSrcPriorityKey(device.getAddress()), 164 BluetoothProfile.PRIORITY_UNDEFINED); 165 return priority; 166 } 167 168 /** 169 * Called by AVRCP controller to provide information about the last user intent on CT. 170 * 171 * If the user has pressed play in the last attempt then A2DP Sink component will grant focus to 172 * any incoming sound from the phone (and also retain focus for a few seconds before 173 * relinquishing. On the other hand if the user has pressed pause/stop then the A2DP sink 174 * component will take the focus away but also notify the stack to throw away incoming data. 175 */ 176 public void informAvrcpPassThroughCmd(BluetoothDevice device, int keyCode, int keyState) { 177 if (mStateMachine != null) { 178 if (keyCode == AvrcpControllerService.PASS_THRU_CMD_ID_PLAY 179 && keyState == AvrcpControllerService.KEY_STATE_RELEASED) { 180 mStateMachine.sendMessage(A2dpSinkStateMachine.EVENT_AVRCP_CT_PLAY); 181 } else if ((keyCode == AvrcpControllerService.PASS_THRU_CMD_ID_PAUSE 182 || keyCode == AvrcpControllerService.PASS_THRU_CMD_ID_STOP) 183 && keyState == AvrcpControllerService.KEY_STATE_RELEASED) { 184 mStateMachine.sendMessage(A2dpSinkStateMachine.EVENT_AVRCP_CT_PAUSE); 185 } 186 } 187 } 188 189 /** 190 * Called by AVRCP controller to provide information about the last user intent on TG. 191 * 192 * Tf the user has pressed pause on the TG then we can preempt streaming music. This is opposed 193 * to when the streaming stops abruptly (jitter) in which case we will wait for sometime before 194 * stopping playback. 195 */ 196 public void informTGStatePlaying(BluetoothDevice device, boolean isPlaying) { 197 if (mStateMachine != null) { 198 if (!isPlaying) { 199 mStateMachine.sendMessage(A2dpSinkStateMachine.EVENT_AVRCP_TG_PAUSE); 200 } else { 201 mStateMachine.sendMessage(A2dpSinkStateMachine.EVENT_AVRCP_TG_PLAY); 202 } 203 } 204 } 205 206 /** 207 * Called by AVRCP controller to establish audio focus. 208 * 209 * In order to perform streaming the A2DP sink must have audio focus. This interface allows the 210 * associated MediaSession to inform the sink of intent to play and then allows streaming to be 211 * started from either the source or the sink endpoint. 212 */ 213 public void requestAudioFocus(BluetoothDevice device, boolean request) { 214 if (mStateMachine != null) { 215 mStateMachine.sendMessage(A2dpSinkStateMachine.EVENT_REQUEST_FOCUS); 216 } 217 } 218 219 synchronized boolean isA2dpPlaying(BluetoothDevice device) { 220 enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); 221 if (DBG) { 222 Log.d(TAG, "isA2dpPlaying(" + device + ")"); 223 } 224 return mStateMachine.isPlaying(device); 225 } 226 227 BluetoothAudioConfig getAudioConfig(BluetoothDevice device) { 228 enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); 229 return mStateMachine.getAudioConfig(device); 230 } 231 232 //Binder object: Must be static class or memory leak may occur 233 private static class BluetoothA2dpSinkBinder extends IBluetoothA2dpSink.Stub 234 implements IProfileServiceBinder { 235 private A2dpSinkService mService; 236 237 private A2dpSinkService getService() { 238 if (!Utils.checkCaller()) { 239 Log.w(TAG, "A2dp call not allowed for non-active user"); 240 return null; 241 } 242 243 if (mService != null && mService.isAvailable()) { 244 return mService; 245 } 246 return null; 247 } 248 249 BluetoothA2dpSinkBinder(A2dpSinkService svc) { 250 mService = svc; 251 } 252 253 @Override 254 public void cleanup() { 255 mService = null; 256 } 257 258 @Override 259 public boolean connect(BluetoothDevice device) { 260 A2dpSinkService service = getService(); 261 if (service == null) { 262 return false; 263 } 264 return service.connect(device); 265 } 266 267 @Override 268 public boolean disconnect(BluetoothDevice device) { 269 A2dpSinkService service = getService(); 270 if (service == null) { 271 return false; 272 } 273 return service.disconnect(device); 274 } 275 276 @Override 277 public List<BluetoothDevice> getConnectedDevices() { 278 A2dpSinkService service = getService(); 279 if (service == null) { 280 return new ArrayList<BluetoothDevice>(0); 281 } 282 return service.getConnectedDevices(); 283 } 284 285 @Override 286 public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) { 287 A2dpSinkService service = getService(); 288 if (service == null) { 289 return new ArrayList<BluetoothDevice>(0); 290 } 291 return service.getDevicesMatchingConnectionStates(states); 292 } 293 294 @Override 295 public int getConnectionState(BluetoothDevice device) { 296 A2dpSinkService service = getService(); 297 if (service == null) { 298 return BluetoothProfile.STATE_DISCONNECTED; 299 } 300 return service.getConnectionState(device); 301 } 302 303 @Override 304 public boolean isA2dpPlaying(BluetoothDevice device) { 305 A2dpSinkService service = getService(); 306 if (service == null) { 307 return false; 308 } 309 return service.isA2dpPlaying(device); 310 } 311 312 @Override 313 public boolean setPriority(BluetoothDevice device, int priority) { 314 A2dpSinkService service = getService(); 315 if (service == null) { 316 return false; 317 } 318 return service.setPriority(device, priority); 319 } 320 321 @Override 322 public int getPriority(BluetoothDevice device) { 323 A2dpSinkService service = getService(); 324 if (service == null) { 325 return BluetoothProfile.PRIORITY_UNDEFINED; 326 } 327 return service.getPriority(device); 328 } 329 330 @Override 331 public BluetoothAudioConfig getAudioConfig(BluetoothDevice device) { 332 A2dpSinkService service = getService(); 333 if (service == null) { 334 return null; 335 } 336 return service.getAudioConfig(device); 337 } 338 } 339 340 ; 341 342 @Override 343 public void dump(StringBuilder sb) { 344 super.dump(sb); 345 if (mStateMachine != null) { 346 mStateMachine.dump(sb); 347 } 348 } 349 } 350