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