HeadsetService.java revision df9d0fe584874f8cafa6ecedb3c3c054c9e99cba
1/* 2 * Copyright (C) 2012 Google Inc. 3 */ 4 5package com.android.bluetooth.hfp; 6 7import android.bluetooth.BluetoothDevice; 8import android.bluetooth.BluetoothHeadset; 9import android.bluetooth.BluetoothProfile; 10import android.bluetooth.IBluetoothHeadset; 11import android.content.BroadcastReceiver; 12import android.content.Context; 13import android.content.Intent; 14import android.content.IntentFilter; 15import android.content.pm.PackageManager; 16import android.media.AudioManager; 17import android.os.Handler; 18import android.os.Message; 19import android.provider.Settings; 20import android.util.Log; 21import com.android.bluetooth.btservice.ProfileService; 22import com.android.bluetooth.Utils; 23import java.util.ArrayList; 24import java.util.List; 25import java.util.Iterator; 26import java.util.Map; 27 28/** 29 * Provides Bluetooth Headset and Handsfree profile, as a service in 30 * the Bluetooth application. 31 * @hide 32 */ 33public class HeadsetService extends ProfileService { 34 private static final boolean DBG = false; 35 private static final String TAG = "HeadsetService"; 36 private static final String MODIFY_PHONE_STATE = android.Manifest.permission.MODIFY_PHONE_STATE; 37 38 private HeadsetStateMachine mStateMachine; 39 private static HeadsetService sHeadsetService; 40 41 protected String getName() { 42 return TAG; 43 } 44 45 public IProfileServiceBinder initBinder() { 46 return new BluetoothHeadsetBinder(this); 47 } 48 49 protected boolean start() { 50 mStateMachine = HeadsetStateMachine.make(this); 51 IntentFilter filter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED); 52 filter.addAction(AudioManager.VOLUME_CHANGED_ACTION); 53 filter.addAction(BluetoothDevice.ACTION_CONNECTION_ACCESS_REPLY); 54 try { 55 registerReceiver(mHeadsetReceiver, filter); 56 } catch (Exception e) { 57 Log.w(TAG,"Unable to register headset receiver",e); 58 } 59 setHeadsetService(this); 60 return true; 61 } 62 63 protected boolean stop() { 64 try { 65 unregisterReceiver(mHeadsetReceiver); 66 } catch (Exception e) { 67 Log.w(TAG,"Unable to unregister headset receiver",e); 68 } 69 mStateMachine.doQuit(); 70 return true; 71 } 72 73 protected boolean cleanup() { 74 if (mStateMachine != null) { 75 mStateMachine.cleanup(); 76 } 77 clearHeadsetService(); 78 return true; 79 } 80 81 private final BroadcastReceiver mHeadsetReceiver = new BroadcastReceiver() { 82 @Override 83 public void onReceive(Context context, Intent intent) { 84 String action = intent.getAction(); 85 if (action.equals(Intent.ACTION_BATTERY_CHANGED)) { 86 mStateMachine.sendMessage(HeadsetStateMachine.INTENT_BATTERY_CHANGED, intent); 87 } else if (action.equals(AudioManager.VOLUME_CHANGED_ACTION)) { 88 int streamType = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, -1); 89 if (streamType == AudioManager.STREAM_BLUETOOTH_SCO) { 90 mStateMachine.sendMessage(HeadsetStateMachine.INTENT_SCO_VOLUME_CHANGED, 91 intent); 92 } 93 } 94 else if (action.equals(BluetoothDevice.ACTION_CONNECTION_ACCESS_REPLY)) { 95 Log.v(TAG, "HeadsetService - Received BluetoothDevice.ACTION_CONNECTION_ACCESS_REPLY"); 96 mStateMachine.handleAccessPermissionResult(intent); 97 } 98 } 99 }; 100 101 /** 102 * Handlers for incoming service calls 103 */ 104 private static class BluetoothHeadsetBinder extends IBluetoothHeadset.Stub implements IProfileServiceBinder { 105 private HeadsetService mService; 106 107 public BluetoothHeadsetBinder(HeadsetService svc) { 108 mService = svc; 109 } 110 public boolean cleanup() { 111 mService = null; 112 return true; 113 } 114 115 private HeadsetService getService() { 116 if (!Utils.checkCaller()) { 117 Log.w(TAG,"Headset call not allowed for non-active user"); 118 return null; 119 } 120 121 if (mService != null && mService.isAvailable()) { 122 return mService; 123 } 124 return null; 125 } 126 127 public boolean connect(BluetoothDevice device) { 128 HeadsetService service = getService(); 129 if (service == null) return false; 130 return service.connect(device); 131 } 132 133 public boolean disconnect(BluetoothDevice device) { 134 HeadsetService service = getService(); 135 if (service == null) return false; 136 return service.disconnect(device); 137 } 138 139 public List<BluetoothDevice> getConnectedDevices() { 140 HeadsetService service = getService(); 141 if (service == null) return new ArrayList<BluetoothDevice>(0); 142 return service.getConnectedDevices(); 143 } 144 145 public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) { 146 HeadsetService service = getService(); 147 if (service == null) return new ArrayList<BluetoothDevice>(0); 148 return service.getDevicesMatchingConnectionStates(states); 149 } 150 151 public int getConnectionState(BluetoothDevice device) { 152 HeadsetService service = getService(); 153 if (service == null) return BluetoothProfile.STATE_DISCONNECTED; 154 return service.getConnectionState(device); 155 } 156 157 public boolean setPriority(BluetoothDevice device, int priority) { 158 HeadsetService service = getService(); 159 if (service == null) return false; 160 return service.setPriority(device, priority); 161 } 162 163 public int getPriority(BluetoothDevice device) { 164 HeadsetService service = getService(); 165 if (service == null) return BluetoothProfile.PRIORITY_UNDEFINED; 166 return service.getPriority(device); 167 } 168 169 public boolean startVoiceRecognition(BluetoothDevice device) { 170 HeadsetService service = getService(); 171 if (service == null) return false; 172 return service.startVoiceRecognition(device); 173 } 174 175 public boolean stopVoiceRecognition(BluetoothDevice device) { 176 HeadsetService service = getService(); 177 if (service == null) return false; 178 return service.stopVoiceRecognition(device); 179 } 180 181 public boolean isAudioOn() { 182 HeadsetService service = getService(); 183 if (service == null) return false; 184 return service.isAudioOn(); 185 } 186 187 public boolean isAudioConnected(BluetoothDevice device) { 188 HeadsetService service = getService(); 189 if (service == null) return false; 190 return service.isAudioConnected(device); 191 } 192 193 public int getBatteryUsageHint(BluetoothDevice device) { 194 HeadsetService service = getService(); 195 if (service == null) return 0; 196 return service.getBatteryUsageHint(device); 197 } 198 199 public boolean acceptIncomingConnect(BluetoothDevice device) { 200 HeadsetService service = getService(); 201 if (service == null) return false; 202 return service.acceptIncomingConnect(device); 203 } 204 205 public boolean rejectIncomingConnect(BluetoothDevice device) { 206 HeadsetService service = getService(); 207 if (service == null) return false; 208 return service.rejectIncomingConnect(device); 209 } 210 211 public int getAudioState(BluetoothDevice device) { 212 HeadsetService service = getService(); 213 if (service == null) return BluetoothHeadset.STATE_AUDIO_DISCONNECTED; 214 return service.getAudioState(device); 215 } 216 217 public boolean connectAudio() { 218 HeadsetService service = getService(); 219 if (service == null) return false; 220 return service.connectAudio(); 221 } 222 223 public boolean disconnectAudio() { 224 HeadsetService service = getService(); 225 if (service == null) return false; 226 return service.disconnectAudio(); 227 } 228 229 public boolean startScoUsingVirtualVoiceCall(BluetoothDevice device) { 230 HeadsetService service = getService(); 231 if (service == null) return false; 232 return service.startScoUsingVirtualVoiceCall(device); 233 } 234 235 public boolean stopScoUsingVirtualVoiceCall(BluetoothDevice device) { 236 HeadsetService service = getService(); 237 if (service == null) return false; 238 return service.stopScoUsingVirtualVoiceCall(device); 239 } 240 241 public void phoneStateChanged(int numActive, int numHeld, int callState, 242 String number, int type) { 243 HeadsetService service = getService(); 244 if (service == null) return; 245 service.phoneStateChanged(numActive, numHeld, callState, number, type); 246 } 247 248 public void roamChanged(boolean roam) { 249 HeadsetService service = getService(); 250 if (service == null) return; 251 service.roamChanged(roam); 252 } 253 254 public void clccResponse(int index, int direction, int status, int mode, boolean mpty, 255 String number, int type) { 256 HeadsetService service = getService(); 257 if (service == null) return; 258 service.clccResponse(index, direction, status, mode, mpty, number, type); 259 } 260 }; 261 262 //API methods 263 public static synchronized HeadsetService getHeadsetService(){ 264 if (sHeadsetService != null && sHeadsetService.isAvailable()) { 265 if (DBG) Log.d(TAG, "getHeadsetService(): returning " + sHeadsetService); 266 return sHeadsetService; 267 } 268 if (DBG) { 269 if (sHeadsetService == null) { 270 Log.d(TAG, "getHeadsetService(): service is NULL"); 271 } else if (!(sHeadsetService.isAvailable())) { 272 Log.d(TAG,"getHeadsetService(): service is not available"); 273 } 274 } 275 return null; 276 } 277 278 private static synchronized void setHeadsetService(HeadsetService instance) { 279 if (instance != null && instance.isAvailable()) { 280 if (DBG) Log.d(TAG, "setHeadsetService(): set to: " + sHeadsetService); 281 sHeadsetService = instance; 282 } else { 283 if (DBG) { 284 if (sHeadsetService == null) { 285 Log.d(TAG, "setHeadsetService(): service not available"); 286 } else if (!sHeadsetService.isAvailable()) { 287 Log.d(TAG,"setHeadsetService(): service is cleaning up"); 288 } 289 } 290 } 291 } 292 293 private static synchronized void clearHeadsetService() { 294 sHeadsetService = null; 295 } 296 297 public boolean connect(BluetoothDevice device) { 298 enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, 299 "Need BLUETOOTH ADMIN permission"); 300 301 if (getPriority(device) == BluetoothProfile.PRIORITY_OFF) { 302 return false; 303 } 304 305 int connectionState = mStateMachine.getConnectionState(device); 306 if (connectionState == BluetoothProfile.STATE_CONNECTED || 307 connectionState == BluetoothProfile.STATE_CONNECTING) { 308 return false; 309 } 310 311 mStateMachine.sendMessage(HeadsetStateMachine.CONNECT, device); 312 return true; 313 } 314 315 boolean disconnect(BluetoothDevice device) { 316 enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, 317 "Need BLUETOOTH ADMIN permission"); 318 int connectionState = mStateMachine.getConnectionState(device); 319 if (connectionState != BluetoothProfile.STATE_CONNECTED && 320 connectionState != BluetoothProfile.STATE_CONNECTING) { 321 return false; 322 } 323 324 mStateMachine.sendMessage(HeadsetStateMachine.DISCONNECT, device); 325 return true; 326 } 327 328 public List<BluetoothDevice> getConnectedDevices() { 329 enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); 330 return mStateMachine.getConnectedDevices(); 331 } 332 333 private List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) { 334 enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); 335 return mStateMachine.getDevicesMatchingConnectionStates(states); 336 } 337 338 int getConnectionState(BluetoothDevice device) { 339 enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); 340 return mStateMachine.getConnectionState(device); 341 } 342 343 public boolean setPriority(BluetoothDevice device, int priority) { 344 enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, 345 "Need BLUETOOTH_ADMIN permission"); 346 Settings.Global.putInt(getContentResolver(), 347 Settings.Global.getBluetoothHeadsetPriorityKey(device.getAddress()), 348 priority); 349 if (DBG) Log.d(TAG, "Saved priority " + device + " = " + priority); 350 return true; 351 } 352 353 public int getPriority(BluetoothDevice device) { 354 enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, 355 "Need BLUETOOTH_ADMIN permission"); 356 int priority = Settings.Global.getInt(getContentResolver(), 357 Settings.Global.getBluetoothHeadsetPriorityKey(device.getAddress()), 358 BluetoothProfile.PRIORITY_UNDEFINED); 359 return priority; 360 } 361 362 boolean startVoiceRecognition(BluetoothDevice device) { 363 enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); 364 int connectionState = mStateMachine.getConnectionState(device); 365 if (connectionState != BluetoothProfile.STATE_CONNECTED && 366 connectionState != BluetoothProfile.STATE_CONNECTING) { 367 return false; 368 } 369 mStateMachine.sendMessage(HeadsetStateMachine.VOICE_RECOGNITION_START); 370 return true; 371 } 372 373 boolean stopVoiceRecognition(BluetoothDevice device) { 374 enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); 375 // It seem that we really need to check the AudioOn state. 376 // But since we allow startVoiceRecognition in STATE_CONNECTED and 377 // STATE_CONNECTING state, we do these 2 in this method 378 int connectionState = mStateMachine.getConnectionState(device); 379 if (connectionState != BluetoothProfile.STATE_CONNECTED && 380 connectionState != BluetoothProfile.STATE_CONNECTING) { 381 return false; 382 } 383 mStateMachine.sendMessage(HeadsetStateMachine.VOICE_RECOGNITION_STOP); 384 // TODO is this return correct when the voice recognition is not on? 385 return true; 386 } 387 388 boolean isAudioOn() { 389 enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); 390 return mStateMachine.isAudioOn(); 391 } 392 393 boolean isAudioConnected(BluetoothDevice device) { 394 enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); 395 return mStateMachine.isAudioConnected(device); 396 } 397 398 int getBatteryUsageHint(BluetoothDevice device) { 399 // TODO(BT) ask for BT stack support? 400 return 0; 401 } 402 403 boolean acceptIncomingConnect(BluetoothDevice device) { 404 // TODO(BT) remove it if stack does access control 405 return false; 406 } 407 408 boolean rejectIncomingConnect(BluetoothDevice device) { 409 // TODO(BT) remove it if stack does access control 410 return false; 411 } 412 413 int getAudioState(BluetoothDevice device) { 414 return mStateMachine.getAudioState(device); 415 } 416 417 boolean connectAudio() { 418 // TODO(BT) BLUETOOTH or BLUETOOTH_ADMIN permission 419 enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); 420 if (!mStateMachine.isConnected()) { 421 return false; 422 } 423 if (mStateMachine.isAudioOn()) { 424 return false; 425 } 426 mStateMachine.sendMessage(HeadsetStateMachine.CONNECT_AUDIO); 427 return true; 428 } 429 430 boolean disconnectAudio() { 431 // TODO(BT) BLUETOOTH or BLUETOOTH_ADMIN permission 432 enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); 433 if (!mStateMachine.isAudioOn()) { 434 return false; 435 } 436 mStateMachine.sendMessage(HeadsetStateMachine.DISCONNECT_AUDIO); 437 return true; 438 } 439 440 boolean startScoUsingVirtualVoiceCall(BluetoothDevice device) { 441 int connectionState = mStateMachine.getConnectionState(device); 442 if (connectionState != BluetoothProfile.STATE_CONNECTED && 443 connectionState != BluetoothProfile.STATE_CONNECTING) { 444 return false; 445 } 446 mStateMachine.sendMessage(HeadsetStateMachine.VIRTUAL_CALL_START, device); 447 return true; 448 } 449 450 boolean stopScoUsingVirtualVoiceCall(BluetoothDevice device) { 451 int connectionState = mStateMachine.getConnectionState(device); 452 if (connectionState != BluetoothProfile.STATE_CONNECTED && 453 connectionState != BluetoothProfile.STATE_CONNECTING) { 454 return false; 455 } 456 mStateMachine.sendMessage(HeadsetStateMachine.VIRTUAL_CALL_STOP, device); 457 return true; 458 } 459 460 private void phoneStateChanged(int numActive, int numHeld, int callState, 461 String number, int type) { 462 enforceCallingOrSelfPermission(MODIFY_PHONE_STATE, null); 463 Message msg = mStateMachine.obtainMessage(HeadsetStateMachine.CALL_STATE_CHANGED); 464 msg.obj = new HeadsetCallState(numActive, numHeld, callState, number, type); 465 msg.arg1 = 0; // false 466 mStateMachine.sendMessage(msg); 467 } 468 469 private void roamChanged(boolean roam) { 470 enforceCallingOrSelfPermission(MODIFY_PHONE_STATE, null); 471 mStateMachine.sendMessage(HeadsetStateMachine.ROAM_CHANGED, roam); 472 } 473 474 private void clccResponse(int index, int direction, int status, int mode, boolean mpty, 475 String number, int type) { 476 enforceCallingOrSelfPermission(MODIFY_PHONE_STATE, null); 477 mStateMachine.sendMessage(HeadsetStateMachine.SEND_CCLC_RESPONSE, 478 new HeadsetClccResponse(index, direction, status, mode, mpty, number, type)); 479 } 480 481} 482