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