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