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