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 17package com.android.bluetooth.hfp; 18 19import android.bluetooth.BluetoothDevice; 20import android.bluetooth.BluetoothHeadset; 21import android.bluetooth.BluetoothProfile; 22import android.bluetooth.IBluetoothHeadset; 23import android.content.BroadcastReceiver; 24import android.content.Context; 25import android.content.Intent; 26import android.content.IntentFilter; 27import android.content.pm.PackageManager; 28import android.media.AudioManager; 29import android.os.Handler; 30import android.os.Message; 31import android.provider.Settings; 32import android.util.Log; 33import com.android.bluetooth.btservice.ProfileService; 34import com.android.bluetooth.Utils; 35import java.util.ArrayList; 36import java.util.List; 37import java.util.Iterator; 38import java.util.Map; 39 40/** 41 * Provides Bluetooth Headset and Handsfree profile, as a service in 42 * the Bluetooth application. 43 * @hide 44 */ 45public 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 53 protected String getName() { 54 return TAG; 55 } 56 57 public IProfileServiceBinder initBinder() { 58 return new BluetoothHeadsetBinder(this); 59 } 60 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 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 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 125 public BluetoothHeadsetBinder(HeadsetService svc) { 126 mService = svc; 127 } 128 public boolean cleanup() { 129 mService = null; 130 return true; 131 } 132 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 145 public boolean connect(BluetoothDevice device) { 146 HeadsetService service = getService(); 147 if (service == null) return false; 148 return service.connect(device); 149 } 150 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 158 public List<BluetoothDevice> getConnectedDevices() { 159 HeadsetService service = getService(); 160 if (service == null) return new ArrayList<BluetoothDevice>(0); 161 return service.getConnectedDevices(); 162 } 163 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 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 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 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 188 public boolean startVoiceRecognition(BluetoothDevice device) { 189 HeadsetService service = getService(); 190 if (service == null) return false; 191 return service.startVoiceRecognition(device); 192 } 193 194 public boolean stopVoiceRecognition(BluetoothDevice device) { 195 HeadsetService service = getService(); 196 if (service == null) return false; 197 return service.stopVoiceRecognition(device); 198 } 199 200 public boolean isAudioOn() { 201 HeadsetService service = getService(); 202 if (service == null) return false; 203 return service.isAudioOn(); 204 } 205 206 public boolean isAudioConnected(BluetoothDevice device) { 207 HeadsetService service = getService(); 208 if (service == null) return false; 209 return service.isAudioConnected(device); 210 } 211 212 public int getBatteryUsageHint(BluetoothDevice device) { 213 HeadsetService service = getService(); 214 if (service == null) return 0; 215 return service.getBatteryUsageHint(device); 216 } 217 218 public boolean acceptIncomingConnect(BluetoothDevice device) { 219 HeadsetService service = getService(); 220 if (service == null) return false; 221 return service.acceptIncomingConnect(device); 222 } 223 224 public boolean rejectIncomingConnect(BluetoothDevice device) { 225 HeadsetService service = getService(); 226 if (service == null) return false; 227 return service.rejectIncomingConnect(device); 228 } 229 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 236 public boolean connectAudio() { 237 HeadsetService service = getService(); 238 if (service == null) return false; 239 return service.connectAudio(); 240 } 241 242 public boolean disconnectAudio() { 243 HeadsetService service = getService(); 244 if (service == null) return false; 245 return service.disconnectAudio(); 246 } 247 248 public void setAudioRouteAllowed(boolean allowed) { 249 HeadsetService service = getService(); 250 if (service == null) return; 251 service.setAudioRouteAllowed(allowed); 252 } 253 254 public boolean getAudioRouteAllowed() { 255 HeadsetService service = getService(); 256 if (service != null) { 257 return service.getAudioRouteAllowed(); 258 } 259 260 return false; 261 } 262 263 public boolean startScoUsingVirtualVoiceCall(BluetoothDevice device) { 264 HeadsetService service = getService(); 265 if (service == null) return false; 266 return service.startScoUsingVirtualVoiceCall(device); 267 } 268 269 public boolean stopScoUsingVirtualVoiceCall(BluetoothDevice device) { 270 HeadsetService service = getService(); 271 if (service == null) return false; 272 return service.stopScoUsingVirtualVoiceCall(device); 273 } 274 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 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 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 299 public boolean enableWBS() { 300 HeadsetService service = getService(); 301 if (service == null) return false; 302 return service.enableWBS(); 303 } 304 305 public boolean disableWBS() { 306 HeadsetService service = getService(); 307 if (service == null) return false; 308 return service.disableWBS(); 309 } 310 311 public void bindResponse(int ind_id, boolean ind_status) { 312 HeadsetService service = getService(); 313 if (service == null) return; 314 service.bindResponse(ind_id, ind_status); 315 } 316 }; 317 318 //API methods 319 public static synchronized HeadsetService getHeadsetService(){ 320 if (sHeadsetService != null && sHeadsetService.isAvailable()) { 321 if (DBG) Log.d(TAG, "getHeadsetService(): returning " + sHeadsetService); 322 return sHeadsetService; 323 } 324 if (DBG) { 325 if (sHeadsetService == null) { 326 Log.d(TAG, "getHeadsetService(): service is NULL"); 327 } else if (!(sHeadsetService.isAvailable())) { 328 Log.d(TAG,"getHeadsetService(): service is not available"); 329 } 330 } 331 return null; 332 } 333 334 private static synchronized void setHeadsetService(HeadsetService instance) { 335 if (instance != null && instance.isAvailable()) { 336 if (DBG) Log.d(TAG, "setHeadsetService(): set to: " + sHeadsetService); 337 sHeadsetService = instance; 338 } else { 339 if (DBG) { 340 if (sHeadsetService == null) { 341 Log.d(TAG, "setHeadsetService(): service not available"); 342 } else if (!sHeadsetService.isAvailable()) { 343 Log.d(TAG,"setHeadsetService(): service is cleaning up"); 344 } 345 } 346 } 347 } 348 349 private static synchronized void clearHeadsetService() { 350 sHeadsetService = null; 351 } 352 353 public boolean connect(BluetoothDevice device) { 354 enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, 355 "Need BLUETOOTH ADMIN permission"); 356 357 if (getPriority(device) == BluetoothProfile.PRIORITY_OFF) { 358 return false; 359 } 360 361 int connectionState = mStateMachine.getConnectionState(device); 362 Log.d(TAG,"connectionState = " + connectionState); 363 if (connectionState == BluetoothProfile.STATE_CONNECTED || 364 connectionState == BluetoothProfile.STATE_CONNECTING) { 365 return false; 366 } 367 368 mStateMachine.sendMessage(HeadsetStateMachine.CONNECT, device); 369 return true; 370 } 371 372 boolean disconnect(BluetoothDevice device) { 373 enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, 374 "Need BLUETOOTH ADMIN permission"); 375 int connectionState = mStateMachine.getConnectionState(device); 376 if (connectionState != BluetoothProfile.STATE_CONNECTED && 377 connectionState != BluetoothProfile.STATE_CONNECTING) { 378 return false; 379 } 380 381 mStateMachine.sendMessage(HeadsetStateMachine.DISCONNECT, device); 382 return true; 383 } 384 385 public List<BluetoothDevice> getConnectedDevices() { 386 enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); 387 return mStateMachine.getConnectedDevices(); 388 } 389 390 private List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) { 391 enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); 392 return mStateMachine.getDevicesMatchingConnectionStates(states); 393 } 394 395 int getConnectionState(BluetoothDevice device) { 396 enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); 397 return mStateMachine.getConnectionState(device); 398 } 399 400 public boolean setPriority(BluetoothDevice device, int priority) { 401 enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, 402 "Need BLUETOOTH_ADMIN permission"); 403 Settings.Global.putInt(getContentResolver(), 404 Settings.Global.getBluetoothHeadsetPriorityKey(device.getAddress()), 405 priority); 406 if (DBG) Log.d(TAG, "Saved priority " + device + " = " + priority); 407 return true; 408 } 409 410 public int getPriority(BluetoothDevice device) { 411 enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, 412 "Need BLUETOOTH_ADMIN permission"); 413 int priority = Settings.Global.getInt(getContentResolver(), 414 Settings.Global.getBluetoothHeadsetPriorityKey(device.getAddress()), 415 BluetoothProfile.PRIORITY_UNDEFINED); 416 return priority; 417 } 418 419 boolean startVoiceRecognition(BluetoothDevice device) { 420 enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); 421 int connectionState = mStateMachine.getConnectionState(device); 422 if (connectionState != BluetoothProfile.STATE_CONNECTED && 423 connectionState != BluetoothProfile.STATE_CONNECTING) { 424 return false; 425 } 426 mStateMachine.sendMessage(HeadsetStateMachine.VOICE_RECOGNITION_START); 427 return true; 428 } 429 430 boolean stopVoiceRecognition(BluetoothDevice device) { 431 enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); 432 // It seem that we really need to check the AudioOn state. 433 // But since we allow startVoiceRecognition in STATE_CONNECTED and 434 // STATE_CONNECTING state, we do these 2 in this method 435 int connectionState = mStateMachine.getConnectionState(device); 436 if (connectionState != BluetoothProfile.STATE_CONNECTED && 437 connectionState != BluetoothProfile.STATE_CONNECTING) { 438 return false; 439 } 440 mStateMachine.sendMessage(HeadsetStateMachine.VOICE_RECOGNITION_STOP); 441 // TODO is this return correct when the voice recognition is not on? 442 return true; 443 } 444 445 boolean isAudioOn() { 446 enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); 447 return mStateMachine.isAudioOn(); 448 } 449 450 boolean isAudioConnected(BluetoothDevice device) { 451 enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); 452 return mStateMachine.isAudioConnected(device); 453 } 454 455 int getBatteryUsageHint(BluetoothDevice device) { 456 // TODO(BT) ask for BT stack support? 457 return 0; 458 } 459 460 boolean acceptIncomingConnect(BluetoothDevice device) { 461 // TODO(BT) remove it if stack does access control 462 return false; 463 } 464 465 boolean rejectIncomingConnect(BluetoothDevice device) { 466 // TODO(BT) remove it if stack does access control 467 return false; 468 } 469 470 int getAudioState(BluetoothDevice device) { 471 return mStateMachine.getAudioState(device); 472 } 473 474 public void setAudioRouteAllowed(boolean allowed) { 475 mStateMachine.setAudioRouteAllowed(allowed); 476 } 477 478 public boolean getAudioRouteAllowed() { 479 return mStateMachine.getAudioRouteAllowed(); 480 } 481 482 boolean connectAudio() { 483 // TODO(BT) BLUETOOTH or BLUETOOTH_ADMIN permission 484 enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); 485 if (!mStateMachine.isConnected()) { 486 return false; 487 } 488 if (mStateMachine.isAudioOn()) { 489 return false; 490 } 491 mStateMachine.sendMessage(HeadsetStateMachine.CONNECT_AUDIO); 492 return true; 493 } 494 495 boolean disconnectAudio() { 496 // TODO(BT) BLUETOOTH or BLUETOOTH_ADMIN permission 497 enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); 498 if (!mStateMachine.isAudioOn()) { 499 return false; 500 } 501 mStateMachine.sendMessage(HeadsetStateMachine.DISCONNECT_AUDIO); 502 return true; 503 } 504 505 boolean startScoUsingVirtualVoiceCall(BluetoothDevice device) { 506 /* Do not ignore request if HSM state is still Disconnected or 507 Pending, it will be processed when transitioned to Connected */ 508 mStateMachine.sendMessage(HeadsetStateMachine.VIRTUAL_CALL_START, device); 509 return true; 510 } 511 512 boolean stopScoUsingVirtualVoiceCall(BluetoothDevice device) { 513 int connectionState = mStateMachine.getConnectionState(device); 514 if (connectionState != BluetoothProfile.STATE_CONNECTED && 515 connectionState != BluetoothProfile.STATE_CONNECTING) { 516 return false; 517 } 518 mStateMachine.sendMessage(HeadsetStateMachine.VIRTUAL_CALL_STOP, device); 519 return true; 520 } 521 522 private void phoneStateChanged(int numActive, int numHeld, int callState, 523 String number, int type) { 524 enforceCallingOrSelfPermission(MODIFY_PHONE_STATE, null); 525 Message msg = mStateMachine.obtainMessage(HeadsetStateMachine.CALL_STATE_CHANGED); 526 msg.obj = new HeadsetCallState(numActive, numHeld, callState, number, type); 527 msg.arg1 = 0; // false 528 mStateMachine.sendMessage(msg); 529 } 530 531 private void clccResponse(int index, int direction, int status, int mode, boolean mpty, 532 String number, int type) { 533 enforceCallingOrSelfPermission(MODIFY_PHONE_STATE, null); 534 mStateMachine.sendMessage(HeadsetStateMachine.SEND_CCLC_RESPONSE, 535 new HeadsetClccResponse(index, direction, status, mode, mpty, number, type)); 536 } 537 538 private boolean sendVendorSpecificResultCode(BluetoothDevice device, 539 String command, 540 String arg) { 541 enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); 542 int connectionState = mStateMachine.getConnectionState(device); 543 if (connectionState != BluetoothProfile.STATE_CONNECTED) { 544 return false; 545 } 546 // Currently we support only "+ANDROID". 547 if (!command.equals(BluetoothHeadset.VENDOR_RESULT_CODE_COMMAND_ANDROID)) { 548 Log.w(TAG, "Disallowed unsolicited result code command: " + command); 549 return false; 550 } 551 mStateMachine.sendMessage(HeadsetStateMachine.SEND_VENDOR_SPECIFIC_RESULT_CODE, 552 new HeadsetVendorSpecificResultCode(device, command, arg)); 553 return true; 554 } 555 556 boolean enableWBS() { 557 // TODO(BT) BLUETOOTH or BLUETOOTH_ADMIN permission 558 enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); 559 if (!mStateMachine.isConnected()) { 560 return false; 561 } 562 if (mStateMachine.isAudioOn()) { 563 return false; 564 } 565 566 for (BluetoothDevice device: getConnectedDevices()) { 567 mStateMachine.sendMessage(HeadsetStateMachine.ENABLE_WBS,device); 568 } 569 570 return true; 571 } 572 573 boolean disableWBS() { 574 // TODO(BT) BLUETOOTH or BLUETOOTH_ADMIN permission 575 enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); 576 if (!mStateMachine.isConnected()) { 577 return false; 578 } 579 if (mStateMachine.isAudioOn()) { 580 return false; 581 } 582 for (BluetoothDevice device: getConnectedDevices()) { 583 mStateMachine.sendMessage(HeadsetStateMachine.DISABLE_WBS,device); 584 } 585 return true; 586 } 587 588 private boolean bindResponse(int ind_id, boolean ind_status) { 589 for (BluetoothDevice device: getConnectedDevices()) { 590 int connectionState = mStateMachine.getConnectionState(device); 591 if (connectionState != BluetoothProfile.STATE_CONNECTED && 592 connectionState != BluetoothProfile.STATE_CONNECTING) { 593 continue; 594 } 595 if (DBG) Log.d("Bind Response sent for", device.getAddress()); 596 Message msg = mStateMachine.obtainMessage(HeadsetStateMachine.BIND_RESPONSE); 597 msg.obj = device; 598 msg.arg1 = ind_id; 599 msg.arg2 = (ind_status == true) ? 1 : 0; 600 mStateMachine.sendMessage(msg); 601 return true; 602 } 603 return false; 604 } 605 606 @Override 607 public void dump(StringBuilder sb) { 608 super.dump(sb); 609 if (mStateMachine != null) { 610 mStateMachine.dump(sb); 611 } 612 } 613} 614