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