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